jeCheckDevelopers
Get an API key
Developers/API reference/Getting started

Getting started

The setup checklist that guides a new company through jeCheck (locations, employees, checklists, the mobile app, the first audit, who fixes violations, the audit plan) and booking a free call with a jeCheck manager. Steps are marked done automatically from company data; optional steps can be skipped.

GETGet setup progress

GET /onboarding

Steps in order with done (detected from company data — the demo location and demo checklist created at sign-up do not count), skipped and optional; done/total progress (skipped steps are excluded) and whether the Getting started screen is hidden (dismissed_at). Step keys: company, locations, people, checklist, app, audit, fixes, planner, call.

Token scoperead
Permission start.view
MCP toolonboarding_get · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/onboarding' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/onboarding",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'onboarding', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_get",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "steps": [
      {
        "key": "locations",
        "optional": false,
        "done": true,
        "skipped": false,
        "facts": 3
      }
    ],
    "done": 7,
    "total": 9,
    "complete": false,
    "dismissed_at": null,
    "active": true
  }
}

POSTSkip or restore an optional step

POST /onboarding/steps/{step}/skip

Only optional steps (planner, call) can be skipped. Returns the updated progress.

Token scopewrite
Permission start.view
MCP toolonboarding_steps_skip

Path parameters

step string Required
Step key.
plannercall

Request body

skip boolean
false — bring the step back. Default true.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view, is read-only
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/onboarding/steps/planner/skip' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "skip": true
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/steps/planner/skip', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "skip": true
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/onboarding/steps/planner/skip",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"skip":True},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'onboarding/steps/planner/skip', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'skip' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_steps_skip",
        "arguments": {
            "step": "planner",
            "skip": true
        }
    }
}
Response 200
{
  "data": {
    "steps": [
      {
        "key": "locations",
        "optional": false,
        "done": true,
        "skipped": false,
        "facts": 3
      }
    ],
    "done": 7,
    "total": 8,
    "complete": false,
    "dismissed_at": null,
    "active": true
  }
}

POSTHide the Getting started screen

POST /onboarding/dismiss

The home page shows the AI control brief instead. The screen stays available at /ui/start.

Token scopewrite
Permission start.view
MCP toolonboarding_dismiss

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view, is read-only
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/onboarding/dismiss' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/dismiss', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/onboarding/dismiss",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'onboarding/dismiss', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_dismiss",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "ok": true
  }
}

POSTShow the Getting started screen again

POST /onboarding/restore
Token scopewrite
Permission start.view
MCP toolonboarding_restore

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view, is read-only
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/onboarding/restore' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/restore', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/onboarding/restore",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'onboarding/restore', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_restore",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "ok": true
  }
}

GETFree time slots for a call with a manager

GET /onboarding/slots

Days ahead with free slots in the company time zone. Each slot lists the managers free at that time; pass start (and optionally manager_id) to book it.

Token scoperead
Permission start.view
MCP toolonboarding_slots · read-only

Query parameters

manager_id integer
Only this manager.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/onboarding/slots?manager_id=42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/slots?manager_id=42', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/onboarding/slots",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"manager_id":42},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'onboarding/slots', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'manager_id' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_slots",
        "arguments": {
            "manager_id": 42
        }
    }
}
Response 200
{
  "data": {
    "tz": "Asia/Almaty",
    "days": [
      {
        "date": "2026-11-12",
        "slots": [
          {
            "start": "2026-11-12T11:00:00+05:00",
            "time": "11:00",
            "managers": [
              1
            ]
          }
        ]
      }
    ]
  }
}

GETList the company's calls with managers

GET /onboarding/meetings

Upcoming and past calls (newest first), with the room link and status: booked, cancelled, done, no_show.

Token scoperead
Permission start.view
MCP toolonboarding_meetings_list · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/onboarding/meetings' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/meetings', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/onboarding/meetings",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'onboarding/meetings', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_meetings_list",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "data": [
      {
        "uuid": "0b9e6c2a-6f1e-4c55-9a0b-1f2e3d4c5b6a",
        "status": "booked",
        "topic": "setup",
        "comment": "We have 5 coffee shops",
        "phone": null,
        "starts_at": "2026-11-12T11:00:00+05:00",
        "ends_at": "2026-11-12T11:30:00+05:00",
        "provider": "zoom",
        "url": "https://zoom.us/j/1234567890",
        "manager": {
          "id": 1,
          "name": "Aliya Seitova",
          "title": "Customer success manager",
          "photo": null
        },
        "ics_url": "/ui/start/meetings/0b9e6c2a-6f1e-4c55-9a0b-1f2e3d4c5b6a/ics"
      }
    ]
  }
}

POSTBook a call with a manager

POST /onboarding/meetings

Books a free slot from GET /onboarding/slots. The room is the manager's permanent Zoom room; both sides get an email with a calendar invite, the client gets a reminder an hour before. A company has one active booking — booking again reschedules it.

Token scopewrite
Permission start.view
MCP toolonboarding_meetings_book

Request body

start date-time Required
Slot start from GET /onboarding/slots (ISO 8601 with offset).
manager_id integer
Preferred manager; by default the least busy free one.
provider string
Video service — currently only zoom.
zoom
topic string
Default setup.
setupdemopricingother
comment string
What to discuss. Maximum length 2000.
phone string
Maximum length 40.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view, is read-only
422422 — The slot is already taken
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/onboarding/meetings' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "start": "2026-11-03T09:00:00+05:00",
    "manager_id": 42,
    "provider": "zoom",
    "topic": "setup"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/meetings', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "start": "2026-11-03T09:00:00+05:00",
      "manager_id": 42,
      "provider": "zoom",
      "topic": "setup"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/onboarding/meetings",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"start":"2026-11-03T09:00:00+05:00","manager_id":42,"provider":"zoom","topic":"setup"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'onboarding/meetings', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'start' => '2026-11-03T09:00:00+05:00',
        'manager_id' => 42,
        'provider' => 'zoom',
        'topic' => 'setup',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_meetings_book",
        "arguments": {
            "start": "2026-11-03T09:00:00+05:00",
            "manager_id": 42,
            "provider": "zoom",
            "topic": "setup"
        }
    }
}
Response 201
{
  "data": {
    "meeting": {
      "uuid": "0b9e6c2a-6f1e-4c55-9a0b-1f2e3d4c5b6a",
      "status": "booked",
      "topic": "setup",
      "comment": "We have 5 coffee shops",
      "phone": null,
      "starts_at": "2026-11-12T11:00:00+05:00",
      "ends_at": "2026-11-12T11:30:00+05:00",
      "provider": "zoom",
      "url": "https://zoom.us/j/1234567890",
      "manager": {
        "id": 1,
        "name": "Aliya Seitova",
        "title": "Customer success manager",
        "photo": null
      },
      "ics_url": "/ui/start/meetings/0b9e6c2a-6f1e-4c55-9a0b-1f2e3d4c5b6a/ics"
    }
  }
}

POSTCancel a call

POST /onboarding/meetings/{uuid}/cancel

The manager gets an email; the calendar event is cancelled.

Token scopewrite
Permission start.view
MCP toolonboarding_meetings_cancel · destructive

Path parameters

uuid uuid Required

Request body

reason string
Maximum length 500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks start.view, is read-only
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/onboarding/meetings/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/onboarding/meetings/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/onboarding/meetings/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"reason":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'onboarding/meetings/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'reason' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "onboarding_meetings_cancel",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "ok": true
  }
}