Routines
Shift routines (SOPs without scores): routine templates with sections, steps and schedules; runs at locations with step marks, photos, completion and manager review. Visibility follows location permissions (routines.view, routines.fill, routines.control).
GETList routines
Routine templates of the company (the library), in display order, with today's progress across all locations.
| Token scope | read |
|---|---|
| Permission |
routines.view |
| MCP tool | routines_list · read-only |
Query parameters
page
integer
1. Minimum 1.
per_page
integer
25. Value 1–100.
active
boolean
true) or disabled (false) routines.
category
string
openingclosingcleaningshiftsafetymaintenanceserviceothertrigger_type
string
scheduleafteron_issuemanuallocation_id
integer
search
string
updated_since
date-time
Responses
| Status | Description |
|---|---|
| 200 | OK — a page of results with meta |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.view
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines?active=true' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines?active=true', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data, meta } = await res.json();import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/routines",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"active":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('GET', 'routines', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'active' => true,
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_list",
"arguments": {
"active": true
}
}
}{
"data": [
{
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}GETRetrieve a routine
Full routine: trigger, conditions, control options, sections and steps with settings and display conditions.
| Token scope | read |
|---|---|
| Permission |
routines.view |
| MCP tool | routines_get · read-only |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.view
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/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/routines/42",
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', 'routines/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_get",
"arguments": {
"id": 42
}
}
}{
"data": {
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit",
"trigger": {
"type": "schedule",
"repeat": "daily",
"windows": [
{
"from": "08:00",
"to": "09:00",
"label": "Morning"
}
]
},
"conditions": {
"date_from": null,
"date_to": null,
"skip_if_no_shift": false,
"manual_start": false
},
"options": {
"grace_minutes": 15,
"late_hours": 2,
"approval": "none",
"sequential": false,
"allow_na": true,
"photo_from_camera": true,
"issue_task": {
"mode": "critical",
"assignee": "manager",
"assignee_id": null,
"due_hours": 4,
"priority": "high"
}
},
"instruction_id": null,
"template_key": "restaurant.opening",
"sections": [
{
"id": 31,
"title": "Restrooms",
"position_ids": [],
"repeat_for": [
"Men",
"Women"
],
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"show_if": null,
"instruction_id": null
}
]
}
]
}
}POSTCreate a routine
Creates a routine with the same validation as the web builder. It is created disabled unless active is true; enabling lays out today's and tomorrow's runs (past windows are never created).
| Token scope | write |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_create |
Request body
title
string
Required description
string | null
category
string
openingclosingcleaningshiftsafetymaintenanceserviceothercolor
string
#f59e0b.
active
boolean
false: created disabled, no runs).
location_ids
array<integer> | null
null — all locations of the company.
position_ids
array<integer>
trigger
object
{type: schedule, repeat: daily|weekdays|monthly|dates|every_n_days, weekdays?, month_days?, dates?, every_days?, start_date?, windows: [{from: "08:00", to: "09:00", label?}] | interval: {from, to, every_minutes, duration_minutes}}, {type: after, routine_id, when: completed|ended, delay_minutes, duration_minutes}, {type: on_issue, routine_id, step_id?, duration_minutes}, {type: manual, duration_minutes}. Local time of the location.
conditions
object
{date_from, date_to, skip_if_no_shift, manual_start}.
options
object
{grace_minutes, late_hours, remind_open, remind_before, notify_missed, approval: none|manager, sequential, allow_na, photo_from_camera, issue_task: {mode: none|critical|all, assignee: manager|position|user|team, assignee_id, due_hours, priority}}.
instruction_id
integer | null
sections
array<object>
id
integer
title
string | null
position_ids
array<integer>
repeat_for
array<string>
steps
array<object>
Required id
integer
key
string
show_if.step_ref.
Maximum length 40. title
string
Required hint
string | null
type
string
check.
checknumberchoicetextphotosignaturescantimerinfosettings
object
unit, min, max, warn_min, warn_max; choice — options [{key, label, issue}]; photo — min_photos; scan — code; timer — minutes; text — placeholder.
photo
string
noneoptionalrequiredon_issuerequired
boolean
critical
boolean
show_if
object | null
instruction_id
integer | null
Responses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"title": "Replace the broken fridge seal",
"description": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
"category": "opening",
"color": "string"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"title": "Replace the broken fridge seal",
"description": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
"category": "opening",
"color": "string"
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/routines",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"title":"Replace the broken fridge seal","description":"Seal on fridge #2 is torn, temperature rises to +9 °C.","category":"opening","color":"string"},
)
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', 'routines', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'title' => 'Replace the broken fridge seal',
'description' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
'category' => 'opening',
'color' => 'string',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_create",
"arguments": {
"title": "Replace the broken fridge seal",
"description": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
"category": "opening",
"color": "string"
}
}
}{
"data": {
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit",
"trigger": {
"type": "schedule",
"repeat": "daily",
"windows": [
{
"from": "08:00",
"to": "09:00",
"label": "Morning"
}
]
},
"conditions": {
"date_from": null,
"date_to": null,
"skip_if_no_shift": false,
"manual_start": false
},
"options": {
"grace_minutes": 15,
"late_hours": 2,
"approval": "none",
"sequential": false,
"allow_na": true,
"photo_from_camera": true,
"issue_task": {
"mode": "critical",
"assignee": "manager",
"assignee_id": null,
"due_hours": 4,
"priority": "high"
}
},
"instruction_id": null,
"template_key": "restaurant.opening",
"sections": [
{
"id": 31,
"title": "Restrooms",
"position_ids": [],
"repeat_for": [
"Men",
"Women"
],
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"show_if": null,
"instruction_id": null
}
]
}
]
}
}PATCHUpdate a routine
Partial update: send only the fields to change. sections replaces the whole structure — pass existing ids of sections and steps to keep them. Future runs without marks are rebuilt.
| Token scope | write |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_update |
Path parameters
id
integer
Required Request body
title
string
description
string | null
category
string
openingclosingcleaningshiftsafetymaintenanceserviceothercolor
string
#f59e0b.
active
boolean
location_ids
array<integer> | null
null — all locations of the company.
position_ids
array<integer>
trigger
object
{type: schedule, repeat: daily|weekdays|monthly|dates|every_n_days, weekdays?, month_days?, dates?, every_days?, start_date?, windows: [{from: "08:00", to: "09:00", label?}] | interval: {from, to, every_minutes, duration_minutes}}, {type: after, routine_id, when: completed|ended, delay_minutes, duration_minutes}, {type: on_issue, routine_id, step_id?, duration_minutes}, {type: manual, duration_minutes}. Local time of the location.
conditions
object
{date_from, date_to, skip_if_no_shift, manual_start}.
options
object
{grace_minutes, late_hours, remind_open, remind_before, notify_missed, approval: none|manager, sequential, allow_na, photo_from_camera, issue_task: {mode: none|critical|all, assignee: manager|position|user|team, assignee_id, due_hours, priority}}.
instruction_id
integer | null
sections
array<object>
id
integer
title
string | null
position_ids
array<integer>
repeat_for
array<string>
steps
array<object>
Required id
integer
key
string
show_if.step_ref.
Maximum length 40. title
string
Required hint
string | null
type
string
check.
checknumberchoicetextphotosignaturescantimerinfosettings
object
unit, min, max, warn_min, warn_max; choice — options [{key, label, issue}]; photo — min_photos; scan — code; timer — minutes; text — placeholder.
photo
string
noneoptionalrequiredon_issuerequired
boolean
critical
boolean
show_if
object | null
instruction_id
integer | null
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X PATCH 'https://app.jecheck.io/api/ext/v1/routines/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"title": "Replace the broken fridge seal",
"description": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
"category": "opening",
"color": "string"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/42', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"title": "Replace the broken fridge seal",
"description": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
"category": "opening",
"color": "string"
}),
});
const { data } = await res.json();import os, requests
res = requests.patch(
"https://app.jecheck.io/api/ext/v1/routines/42",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"title":"Replace the broken fridge seal","description":"Seal on fridge #2 is torn, temperature rises to +9 °C.","category":"opening","color":"string"},
)
res.raise_for_status()
data = res.json()["data"]$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('PATCH', 'routines/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'title' => 'Replace the broken fridge seal',
'description' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
'category' => 'opening',
'color' => 'string',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_update",
"arguments": {
"id": 42,
"title": "Replace the broken fridge seal",
"description": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
"category": "opening",
"color": "string"
}
}
}{
"data": {
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit",
"trigger": {
"type": "schedule",
"repeat": "daily",
"windows": [
{
"from": "08:00",
"to": "09:00",
"label": "Morning"
}
]
},
"conditions": {
"date_from": null,
"date_to": null,
"skip_if_no_shift": false,
"manual_start": false
},
"options": {
"grace_minutes": 15,
"late_hours": 2,
"approval": "none",
"sequential": false,
"allow_na": true,
"photo_from_camera": true,
"issue_task": {
"mode": "critical",
"assignee": "manager",
"assignee_id": null,
"due_hours": 4,
"priority": "high"
}
},
"instruction_id": null,
"template_key": "restaurant.opening",
"sections": [
{
"id": 31,
"title": "Restrooms",
"position_ids": [],
"repeat_for": [
"Men",
"Women"
],
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"show_if": null,
"instruction_id": null
}
]
}
]
}
}POSTDuplicate a routine
Creates a disabled copy with the same structure and settings.
| Token scope | write |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_duplicate |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/42/duplicate' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/42/duplicate', {
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/routines/42/duplicate",
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', 'routines/42/duplicate', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_duplicate",
"arguments": {
"id": 42
}
}
}{
"data": {
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit",
"trigger": {
"type": "schedule",
"repeat": "daily",
"windows": [
{
"from": "08:00",
"to": "09:00",
"label": "Morning"
}
]
},
"conditions": {
"date_from": null,
"date_to": null,
"skip_if_no_shift": false,
"manual_start": false
},
"options": {
"grace_minutes": 15,
"late_hours": 2,
"approval": "none",
"sequential": false,
"allow_na": true,
"photo_from_camera": true,
"issue_task": {
"mode": "critical",
"assignee": "manager",
"assignee_id": null,
"due_hours": 4,
"priority": "high"
}
},
"instruction_id": null,
"template_key": "restaurant.opening",
"sections": [
{
"id": 31,
"title": "Restrooms",
"position_ids": [],
"repeat_for": [
"Men",
"Women"
],
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"show_if": null,
"instruction_id": null
}
]
}
]
}
}DELDelete a routine
Disables and deletes the routine; unstarted runs are removed, history is kept. Not allowed while other routines are chained to it (after / on_issue).
| Token scope | write |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_delete · destructive |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 409 | conflict — Other routines are chained to this one
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X DELETE 'https://app.jecheck.io/api/ext/v1/routines/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/42', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data } = await res.json();import os, requests
res = requests.delete(
"https://app.jecheck.io/api/ext/v1/routines/42",
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('DELETE', 'routines/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_delete",
"arguments": {
"id": 42
}
}
}{
"data": {
"deleted": true
}
}GETList routine templates
Ready-made routine packs by business type (restaurant, shop, hotel, …) that can be installed with routines.templates.install.
| Token scope | read |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_templates_list · read-only |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/templates' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/templates', {
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/routines/templates",
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', 'routines/templates', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_templates_list",
"arguments": {}
}
}{
"data": [
{
"key": "restaurant",
"title": "Restaurant / cafe",
"description": "Opening, cleaning, closing and deliveries",
"icon": "restaurant",
"routines": [
{
"key": "opening",
"title": "Opening the shift",
"category": "opening",
"trigger": "Daily, 08:00–09:00",
"trigger_type": "schedule",
"steps": 12,
"repeat": false
}
]
}
]
}POSTInstall a routine template
Installs a whole pack, or one routine of it (routine_key) together with its chain parent. Routines are created disabled.
| Token scope | write |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_templates_install |
Request body
pack
string
Required routines.templates.list.
Maximum length 60. routine_key
string
Responses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/templates/install' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"pack": "string",
"routine_key": "string"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/templates/install', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"pack": "string",
"routine_key": "string"
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/routines/templates/install",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"pack":"string","routine_key":"string"},
)
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', 'routines/templates/install', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'pack' => 'string',
'routine_key' => 'string',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_templates_install",
"arguments": {
"pack": "string",
"routine_key": "string"
}
}
}{
"data": [
{
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit"
}
]
}POSTCreate a routine from an audit checklist
Converts checklist questions into routine steps without scores. The checklist is not changed; the routine is created disabled.
| Token scope | write |
|---|---|
| Permission |
routines.manage |
| MCP tool | routines_from_checklist |
Path parameters
checklist_id
integer
Required Responses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/from-checklist/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/from-checklist/42', {
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/routines/from-checklist/42",
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', 'routines/from-checklist/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_from_checklist",
"arguments": {
"checklist_id": 42
}
}
}{
"data": {
"id": 12,
"uuid": "5b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "Opening the shift",
"description": "Everything that must be done before the first guest arrives.",
"category": "opening",
"color": "#f59e0b",
"active": true,
"trigger_type": "schedule",
"trigger_text": "Daily, 08:00–09:00",
"parent_routine_id": null,
"location_ids": null,
"position_ids": [],
"steps_count": 8,
"approval": false,
"manual_start": false,
"today": {
"done": 3,
"total": 4
},
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-11-01T12:30:00+05:00",
"url": "https://app.jecheck.io/ui/routines/12/edit",
"trigger": {
"type": "schedule",
"repeat": "daily",
"windows": [
{
"from": "08:00",
"to": "09:00",
"label": "Morning"
}
]
},
"conditions": {
"date_from": null,
"date_to": null,
"skip_if_no_shift": false,
"manual_start": false
},
"options": {
"grace_minutes": 15,
"late_hours": 2,
"approval": "none",
"sequential": false,
"allow_na": true,
"photo_from_camera": true,
"issue_task": {
"mode": "critical",
"assignee": "manager",
"assignee_id": null,
"due_hours": 4,
"priority": "high"
}
},
"instruction_id": null,
"template_key": "restaurant.opening",
"sections": [
{
"id": 31,
"title": "Restrooms",
"position_ids": [],
"repeat_for": [
"Men",
"Women"
],
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"show_if": null,
"instruction_id": null
}
]
}
]
}
}GETList routine runs
Runs at the locations visible to the token (routines.view), newest window first. overdue is computed: the due time passed but the run is not closed yet.
| Token scope | read |
|---|---|
| Permission |
routines.view |
| MCP tool | routines_runs_list · read-only |
Query parameters
page
integer
1. Minimum 1.
per_page
integer
25. Value 1–100.
location_ids
array<integer>
date
date
date_from
date
date_to
date
routine_id
integer
status
array<string>
user_id
integer
trigger_type
string
scheduleafteron_issuemanualupdated_since
date-time
Responses
| Status | Description |
|---|---|
| 200 | OK — a page of results with meta |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.view
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/runs?date=2026-11-01' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/runs?date=2026-11-01', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data, meta } = await res.json();import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/routines/runs",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"date":"2026-11-01"},
)
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', 'routines/runs', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'date' => '2026-11-01',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_runs_list",
"arguments": {
"date": "2026-11-01"
}
}
}{
"data": [
{
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}GETRetrieve a routine run
Run with sections, steps and marks (photos as signed links valid for 1 hour), required steps still missing and the actions the token may perform now (can).
| Token scope | read |
|---|---|
| Permission |
routines.view or routines.fill |
| MCP tool | routines_runs_get · read-only |
Path parameters
uuid
uuid
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.view / routines.fill
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b', {
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/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
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', 'routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_runs_get",
"arguments": {
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}GETGet the day board
All runs of a day by location with totals (what the "Daily schedule" screen shows), plus routines the token may start manually.
| Token scope | read |
|---|---|
| Permission |
routines.view |
| MCP tool | routines_board · read-only |
Query parameters
location_ids
array<integer>
date
date
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.view
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/board?date=2026-11-01' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/board?date=2026-11-01', {
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/routines/board",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"date":"2026-11-01"},
)
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', 'routines/board', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'date' => '2026-11-01',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_board",
"arguments": {
"date": "2026-11-01"
}
}
}{
"data": {
"day": "2026-11-03",
"summary": {
"total": 12,
"due": 8,
"done": 6,
"on_time": 5,
"in_progress": 1,
"review": 1,
"failed": 1,
"issues": 2,
"steps_total": 96,
"steps_done": 70,
"skipped": 0
},
"locations": [
{
"location": {
"id": 4609,
"name": "Abay Ave"
},
"timezone": "Asia/Almaty",
"summary": {
"total": 6,
"due": 4,
"done": 3,
"on_time": 3,
"in_progress": 1,
"review": 0,
"failed": 0,
"issues": 1,
"steps_total": 48,
"steps_done": 35,
"skipped": 0
},
"runs": [
{
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d"
}
]
}
],
"startable": [
{
"routine_id": 15,
"title": "Delivery acceptance",
"category": "safety",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"manual": true
}
]
}
}GETList my routine runs
Runs the acting user performs (permission routines.fill and the job position of the routine/section): today, yesterday's still workable and night windows opening within 2 hours; plus routines the user may start manually.
| Token scope | read |
|---|---|
| Permission |
routines.fill |
| MCP tool | routines_my · read-only |
Query parameters
location_id
integer
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.fill
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/my?location_id=12' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/my?location_id=12', {
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/routines/my",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"location_id":12},
)
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', 'routines/my', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'location_id' => 12,
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_my",
"arguments": {
"location_id": 12
}
}
}{
"data": {
"runs": [
{
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d"
}
],
"startable": [
{
"routine_id": 15,
"title": "Delivery acceptance",
"category": "safety",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"manual": true
}
]
}
}POSTStart a routine run
Starts a run now at a location. Performers may start routines with manual start allowed; routines.control at the location may start any routine out of schedule. Performers are notified.
| Token scope | write |
|---|---|
| Permission |
routines.fill or routines.control |
| MCP tool | routines_runs_start |
Path parameters
id
integer
Required Request body
location_id
integer
Required Responses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.fill / routines.control, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/42/start' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"location_id": 12
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/42/start', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"location_id": 12
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/routines/42/start",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"location_id":12},
)
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', 'routines/42/start', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'location_id' => 12,
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_runs_start",
"arguments": {
"id": 42,
"location_id": 12
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}POSTMark routine steps
Records step marks in a batch (offline-friendly, like the mobile app). Each mark: step_id, item (repeat object index, default 0), status (done default, issue — cannot do, reason required; na), value (string or number — measurement, choice key, text, scanned code), reason, comment, photos (paths from routines.media.upload), marked_at (device time), client_uuid — repeating the same client_uuid does not change the mark. Issues create tasks and chained runs as configured.
| Token scope | write |
|---|---|
| Permission |
routines.fill or routines.control |
| MCP tool | routines_runs_mark |
Path parameters
uuid
uuid
Required Request body
marks
array<object>
Required step_id
integer
Required item
integer
status
string
doneissuenareason
string
comment
string
photos
array<string>
marked_at
date-time
client_uuid
uuid
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.fill / routines.control, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/marks' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"marks": [
{
"step_id": 42,
"item": 42,
"status": "done"
}
]
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/marks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"marks": [
{
"step_id": 42,
"item": 42,
"status": "done"
}
]
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/marks",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"marks":[{"step_id":42,"item":42,"status":"done"}]},
)
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', 'routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/marks', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'marks' => [
[
'step_id' => 42,
'item' => 42,
'status' => 'done',
],
],
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_runs_mark",
"arguments": {
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"marks": [
{
"step_id": 42,
"item": 42,
"status": "done"
}
]
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}POSTComplete a routine run
Completes the run when all visible required steps are marked. With manager approval enabled, the run goes to review. Optional marks are recorded first in the same call.
| Token scope | write |
|---|---|
| Permission |
routines.fill or routines.control |
| MCP tool | routines_runs_complete |
Path parameters
uuid
uuid
Required Request body
marks
array<object>
routines.runs.mark.
Maximum items 300. step_id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.fill / routines.control, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Required steps are not marked
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/complete' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"marks": [
{
"step_id": 42
}
]
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/complete', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"marks": [
{
"step_id": 42
}
]
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/complete",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"marks":[{"step_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('POST', 'routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/complete', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'marks' => [
[
'step_id' => 42,
],
],
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_runs_complete",
"arguments": {
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"marks": [
{
"step_id": 42
}
]
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}POSTAccept a routine run
Manager approval of a run in review (routines.control at the location). Chained routines start.
| Token scope | write |
|---|---|
| Permission |
routines.control |
| MCP tool | routines_runs_accept |
Path parameters
uuid
uuid
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.control, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/accept' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/accept', {
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/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/accept",
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', 'routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/accept', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_runs_accept",
"arguments": {
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}POSTReturn a routine run for rework
Returns a completed run to in_progress with a note; the performer is notified.
| Token scope | write |
|---|---|
| Permission |
routines.control |
| MCP tool | routines_runs_return |
Path parameters
uuid
uuid
Required Request body
note
string
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.control, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/return' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/return', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"note": "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/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/return",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"note":"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', 'routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/return', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'note' => '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": "routines_runs_return",
"arguments": {
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}POSTSkip a routine run
Cancels a run with a reason (location closed, sanitary day). Skipped runs are not counted in analytics.
| Token scope | write |
|---|---|
| Permission |
routines.control |
| MCP tool | routines_runs_skip |
Path parameters
uuid
uuid
Required Request body
reason
string
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.control, is read-only
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/skip' \
-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/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/skip', {
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/routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/skip",
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', 'routines/runs/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/skip', [
'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": "routines_runs_skip",
"arguments": {
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}
}
}{
"data": {
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"category": "opening",
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"trigger_type": "schedule",
"label": "Morning",
"opens_at": "2026-11-03T08:00:00+05:00",
"due_at": "2026-11-03T09:00:00+05:00",
"status": "in_progress",
"late": false,
"progress": {
"done": 5,
"total": 8,
"issues": 1
},
"started_at": "2026-11-03T08:04:00+05:00",
"started_by": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"completed_at": null,
"completed_by": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"skip_reason": null,
"parent_run_uuid": null,
"updated_at": "2026-11-03T08:31:00+05:00",
"url": "https://app.jecheck.io/ui/routines?run=9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"description": "Everything that must be done before the first guest arrives.",
"instruction": {
"uuid": "0c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"title": "Opening standard"
},
"options": {
"sequential": false,
"allow_na": true,
"approval": "none",
"photo_from_camera": true
},
"sections": [
{
"section_id": 31,
"title": "Restrooms",
"item": 0,
"item_name": "Men",
"can_fill": true,
"steps": [
{
"id": 210,
"title": "Liquid soap",
"hint": "Refill if less than a third is left",
"type": "choice",
"settings": {
"options": [
{
"key": "ok",
"label": "Enough",
"issue": false
},
{
"key": "none",
"label": "Out of soap",
"issue": true
}
]
},
"photo": "on_issue",
"required": true,
"critical": false,
"conditional": false,
"instruction": null,
"mark": {
"status": "issue",
"value": "none",
"display": "Out of soap",
"flag": null,
"reason": null,
"comment": "Ordered from the warehouse",
"photos": [
{
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
],
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"spawned_run_uuid": null
}
}
]
}
],
"missing_required": [
"Restrooms (Women): Liquid soap"
],
"children": [
{
"uuid": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"title": "Restock consumables",
"status": "pending",
"trigger_type": "on_issue"
}
],
"can": {
"fill": true,
"complete": true,
"accept": false,
"return": false,
"skip": true
}
}
}POSTUpload a routine photo or signature
Uploads an image (jpg, png, webp, heic, up to 15 MB) and returns its path to pass in photos of a step mark (or as value of a signature step).
| Token scope | write |
|---|---|
| Permission |
routines.fill |
| MCP tool | routines_media_upload |
| Content type | multipart/form-data application/json |
Request body
file
file
Required name and content_base64).
kind
string
photo. photosignatureResponses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.fill, is read-only
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/routines/media' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-F 'kind=photo' \
-F '[email protected]'const form = new FormData();
form.append('kind', "photo");
form.append('file', fileInput.files[0]);
const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/media', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.JECHECK_TOKEN}` },
body: form,
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/routines/media",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
data={"kind":"photo"},
files=[("file", open("photo.jpg", "rb"))],
)
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', 'routines/media', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'multipart' => [
['name' => 'kind', 'contents' => 'photo'],
['name' => 'file', 'contents' => fopen('photo.jpg', 'r')],
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_media_upload",
"arguments": {
"kind": "photo"
}
}
}{
"data": {
"path": "routines/761/photos/2026/11/7f1e.jpg",
"url": "https://storage.example.com/routines/761/photos/2026/11/7f1e.jpg?X-Amz-Signature=…"
}
}GETList routine step issues
Step marks with problems ("cannot do", out-of-range values, wrong code) at visible locations, newest first, with the follow-up task if one was created.
| Token scope | read |
|---|---|
| Permission |
routines.view |
| MCP tool | routines_issues · read-only |
Query parameters
page
integer
1. Minimum 1.
per_page
integer
25. Value 1–100.
location_ids
array<integer>
date
date
date_from
date
date_to
date
routine_id
integer
critical
boolean
Responses
| Status | Description |
|---|---|
| 200 | OK — a page of results with meta |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks routines.view
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/issues?date=2026-11-01' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/issues?date=2026-11-01', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data, meta } = await res.json();import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/routines/issues",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"date":"2026-11-01"},
)
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', 'routines/issues', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'date' => '2026-11-01',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_issues",
"arguments": {
"date": "2026-11-01"
}
}
}{
"data": [
{
"run_uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"routine": {
"id": 12,
"title": "Opening the shift"
},
"location": {
"id": 4609,
"name": "Abay Ave"
},
"day": "2026-11-03",
"step": {
"id": 210,
"title": "Liquid soap",
"critical": false
},
"item": 0,
"value": "none",
"reason": null,
"comment": "Ordered from the warehouse",
"user": {
"id": 311,
"uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
"name": "Aigerim Sadykova"
},
"marked_at": "2026-11-03T08:12:00+05:00",
"task_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}GETGet routine analytics
Discipline metrics for runs whose due time has passed (skipped runs excluded): totals vs previous period, trend, by location (with the average audit score), by routine, by employee, problem steps, start hours, recent failures and insights. Locations follow analytics.view.
| Token scope | read |
|---|---|
| Permission |
analytics.view |
| MCP tool | routines_analytics · read-only |
Query parameters
period
string
30d. 7d30d90d12mdate_from
date
date_to).
date_to
date
location_ids
array<integer>
routine_id
integer
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks analytics.view
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/routines/analytics?period=30d' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/routines/analytics?period=30d', {
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/routines/analytics",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"period":"30d"},
)
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', 'routines/analytics', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'period' => '30d',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "routines_analytics",
"arguments": {
"period": "30d"
}
}
}{
"data": {
"period": {
"from": "2026-10-05",
"to": "2026-11-03"
},
"totals": {
"due": 420,
"on_time": 371,
"late": 18,
"failed": 31,
"missed": 12,
"review": 4,
"issues": 57,
"rate": 88,
"done_rate": 93,
"steps_rate": 95
},
"previous": {
"due": 398,
"on_time": 330,
"late": 25,
"failed": 43,
"missed": 20,
"review": 2,
"issues": 61,
"rate": 83,
"done_rate": 89,
"steps_rate": 92
},
"trend": [
{
"bucket": "2026-11-03",
"due": 14,
"on_time": 12,
"late": 1,
"failed": 1,
"missed": 0,
"review": 0,
"issues": 2,
"rate": 86,
"done_rate": 93,
"steps_rate": 96
}
],
"by_location": [
{
"id": 4609,
"name": "Abay Ave",
"due": 140,
"on_time": 130,
"late": 4,
"failed": 6,
"missed": 2,
"review": 0,
"issues": 12,
"rate": 93,
"done_rate": 96,
"steps_rate": 97,
"audit": {
"avg": 64,
"count": 5
}
}
],
"by_routine": [
{
"id": 12,
"name": "Opening the shift",
"due": 60,
"on_time": 55,
"late": 3,
"failed": 2,
"missed": 1,
"review": 0,
"issues": 5,
"rate": 92,
"done_rate": 97,
"steps_rate": 98
}
],
"by_employee": [
{
"id": 311,
"name": "Aigerim Sadykova",
"runs": 40,
"on_time": 37,
"rate": 93,
"marks": 380,
"issues": 6,
"photos": 44
}
],
"problem_steps": [
{
"step_id": 210,
"step": "Liquid soap",
"routine": "Opening the shift",
"critical": false,
"count": 9,
"points": 3,
"tasks": 4,
"reason": "Out of consumables"
}
],
"hours": [
{
"hour": 8,
"due": 60,
"rate": 92
}
],
"failed": [
{
"uuid": "9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d",
"date": "2026-11-02",
"time": "23:00",
"routine": "Closing",
"location": "Abay Ave",
"status": "missed",
"progress": 0
}
],
"insights": [
{
"type": "gap",
"location": "Abay Ave",
"rate": 93,
"audit": 64
}
]
}
}