jeCheckDevelopers
Get an API key

GETList routines

GET /routines

Routine templates of the company (the library), in display order, with today's progress across all locations.

Token scoperead
Permission routines.view
MCP toolroutines_list · read-only

Query parameters

page integer
Page number, starting from 1. Default 1. Minimum 1.
per_page integer
Items per page (max 100). Default 25. Value 1–100.
active boolean
Only enabled (true) or disabled (false) routines.
category string
openingclosingcleaningshiftsafetymaintenanceserviceother
trigger_type string
scheduleafteron_issuemanual
location_id integer
Only routines that run at this location.
search string
Search in title. Maximum length 200.
updated_since date-time
Only routines changed after this moment.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.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/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
        }
    }
}
Response 200
{
  "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

GET /routines/{id}

Full routine: trigger, conditions, control options, sections and steps with settings and display conditions.

Token scoperead
Permission routines.view
MCP toolroutines_get · read-only

Path parameters

id integer Required
Routine id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.view
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 '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
        }
    }
}
Response 200
{
  "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

POST /routines

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 scopewrite
Permission routines.manage
MCP toolroutines_create

Request body

title string Required
Routine title. Length 1–255.
description string | null
Maximum length 5000.
category string
openingclosingcleaningshiftsafetymaintenanceserviceother
color string
Hex color, e.g. #f59e0b.
active boolean
Enable right away (default false: created disabled, no runs).
location_ids array<integer> | null
Locations; null — all locations of the company.
position_ids array<integer>
Job positions that perform the routine; empty — any staff of the location.
trigger object
When it runs. {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
Control: {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
Knowledge-base article id.
sections array<object>
Sections with steps. Omitted — one placeholder step is created. Maximum items 50.
id integer
Existing section id; omit for a new section.
title string | null
Maximum length 255.
position_ids array<integer>
Positions that perform this section.
repeat_for array<string>
Repeat the steps for each object (e.g. room names). Maximum items 200.
steps array<object> Required
id integer
Existing step id — keep it to preserve marks history; omit for a new step.
key string
Client key of a new step, used by show_if.step_ref. Maximum length 40.
title string Required
Maximum length 500.
hint string | null
Maximum length 5000.
type string
Step type, default check.
checknumberchoicetextphotosignaturescantimerinfo
settings object
Type settings: number — unit, min, max, warn_min, warn_max; choice — options [{key, label, issue}]; photo — min_photos; scan — code; timer — minutes; text — placeholder.
photo string
noneoptionalrequiredon_issue
required boolean
critical boolean
show_if object | null
Show only if a step above in the same section matches: {step_ref (id or key), op: done|issue|equals|lt|gt, value}.
instruction_id integer | null

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
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/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"
        }
    }
}
Response 201
{
  "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

PATCH /routines/{id}

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 scopewrite
Permission routines.manage
MCP toolroutines_update

Path parameters

id integer Required
Routine id.

Request body

title string
Routine title. Length 1–255.
description string | null
Maximum length 5000.
category string
openingclosingcleaningshiftsafetymaintenanceserviceother
color string
Hex color, e.g. #f59e0b.
active boolean
Enable / disable the routine.
location_ids array<integer> | null
Locations; null — all locations of the company.
position_ids array<integer>
Job positions that perform the routine; empty — any staff of the location.
trigger object
When it runs. {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
Control: {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
Knowledge-base article id.
sections array<object>
Full structure: replaces sections and steps (steps with marks that are dropped go to the archive). Maximum items 50.
id integer
Existing section id; omit for a new section.
title string | null
Maximum length 255.
position_ids array<integer>
Positions that perform this section.
repeat_for array<string>
Repeat the steps for each object (e.g. room names). Maximum items 200.
steps array<object> Required
id integer
Existing step id — keep it to preserve marks history; omit for a new step.
key string
Client key of a new step, used by show_if.step_ref. Maximum length 40.
title string Required
Maximum length 500.
hint string | null
Maximum length 5000.
type string
Step type, default check.
checknumberchoicetextphotosignaturescantimerinfo
settings object
Type settings: number — unit, min, max, warn_min, warn_max; choice — options [{key, label, issue}]; photo — min_photos; scan — code; timer — minutes; text — placeholder.
photo string
noneoptionalrequiredon_issue
required boolean
critical boolean
show_if object | null
Show only if a step above in the same section matches: {step_ref (id or key), op: done|issue|equals|lt|gt, value}.
instruction_id integer | null

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage, 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 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"
        }
    }
}
Response 200
{
  "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

POST /routines/{id}/duplicate

Creates a disabled copy with the same structure and settings.

Token scopewrite
Permission routines.manage
MCP toolroutines_duplicate

Path parameters

id integer Required
Routine id.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage, 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/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
        }
    }
}
Response 201
{
  "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

DELETE /routines/{id}

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 scopewrite
Permission routines.manage
MCP toolroutines_delete · destructive

Path parameters

id integer Required
Routine id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
404not_found — The record does not exist or is not visible to the token
409conflict — Other routines are chained to this one
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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
        }
    }
}
Response 200
{
  "data": {
    "deleted": true
  }
}

GETList routine templates

GET /routines/templates

Ready-made routine packs by business type (restaurant, shop, hotel, …) that can be installed with routines.templates.install.

Token scoperead
Permission routines.manage
MCP toolroutines_templates_list · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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": {}
    }
}
Response 200
{
  "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

POST /routines/templates/install

Installs a whole pack, or one routine of it (routine_key) together with its chain parent. Routines are created disabled.

Token scopewrite
Permission routines.manage
MCP toolroutines_templates_install

Request body

pack string Required
Pack key from routines.templates.list. Maximum length 60.
routine_key string
Install only this routine of the pack. Maximum length 60.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage, is read-only
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/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"
        }
    }
}
Response 201
{
  "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

POST /routines/from-checklist/{checklist_id}

Converts checklist questions into routine steps without scores. The checklist is not changed; the routine is created disabled.

Token scopewrite
Permission routines.manage
MCP toolroutines_from_checklist

Path parameters

checklist_id integer Required
Checklist id.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.manage, 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/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
        }
    }
}
Response 201
{
  "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

GET /routines/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 scoperead
Permission routines.view
MCP toolroutines_runs_list · read-only

Query parameters

page integer
Page number, starting from 1. Default 1. Minimum 1.
per_page integer
Items per page (max 100). Default 25. Value 1–100.
location_ids array<integer>
Only these locations.
date date
Only this local day.
date_from date
Local day from (inclusive).
date_to date
Local day to (inclusive).
routine_id integer
Only runs of this routine.
status array<string>
Filter by status: pending, in_progress, review, done, overdue, incomplete, missed, skipped.
user_id integer
Only runs started, completed or marked by this user.
trigger_type string
scheduleafteron_issuemanual
updated_since date-time
Only runs changed after this moment — use for incremental sync.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.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/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"
        }
    }
}
Response 200
{
  "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

GET /routines/runs/{uuid}

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 scoperead
Permission routines.view or routines.fill
MCP toolroutines_runs_get · read-only

Path parameters

uuid uuid Required
Run UUID.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.view / routines.fill
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 '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"
        }
    }
}
Response 200
{
  "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

GET /routines/board

All runs of a day by location with totals (what the "Daily schedule" screen shows), plus routines the token may start manually.

Token scoperead
Permission routines.view
MCP toolroutines_board · read-only

Query parameters

location_ids array<integer>
Only these locations (default — all visible).
date date
Local day, default — today of the company.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.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/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"
        }
    }
}
Response 200
{
  "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

GET /routines/my

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 scoperead
Permission routines.fill
MCP toolroutines_my · read-only

Query parameters

location_id integer
Only this location.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.fill
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/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
        }
    }
}
Response 200
{
  "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

POST /routines/{id}/start

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 scopewrite
Permission routines.fill or routines.control
MCP toolroutines_runs_start

Path parameters

id integer Required
Routine id.

Request body

location_id integer Required
Location id.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.fill / routines.control, 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/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
        }
    }
}
Response 201
{
  "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

POST /routines/runs/{uuid}/marks

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 scopewrite
Permission routines.fill or routines.control
MCP toolroutines_runs_mark

Path parameters

uuid uuid Required
Run UUID.

Request body

marks array<object> Required
Items 1–300.
step_id integer Required
item integer
Minimum 0.
status string
doneissuena
reason string
Maximum length 255.
comment string
Maximum length 2000.
photos array<string>
Maximum items 10.
marked_at date-time
client_uuid uuid

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.fill / routines.control, 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/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"
                }
            ]
        }
    }
}
Response 200
{
  "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

POST /routines/runs/{uuid}/complete

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 scopewrite
Permission routines.fill or routines.control
MCP toolroutines_runs_complete

Path parameters

uuid uuid Required
Run UUID.

Request body

marks array<object>
Last marks, same format as routines.runs.mark. Maximum items 300.
step_id integer Required

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.fill / routines.control, is read-only
404not_found — The record does not exist or is not visible to the token
422validation_failed — Required steps are not marked
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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
                }
            ]
        }
    }
}
Response 200
{
  "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

POST /routines/runs/{uuid}/accept

Manager approval of a run in review (routines.control at the location). Chained routines start.

Token scopewrite
Permission routines.control
MCP toolroutines_runs_accept

Path parameters

uuid uuid Required
Run UUID.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.control, 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/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"
        }
    }
}
Response 200
{
  "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

POST /routines/runs/{uuid}/return

Returns a completed run to in_progress with a note; the performer is notified.

Token scopewrite
Permission routines.control
MCP toolroutines_runs_return

Path parameters

uuid uuid Required
Run UUID.

Request body

note string Required
Length 1–500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.control, 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/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."
        }
    }
}
Response 200
{
  "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

POST /routines/runs/{uuid}/skip

Cancels a run with a reason (location closed, sanitary day). Skipped runs are not counted in analytics.

Token scopewrite
Permission routines.control
MCP toolroutines_runs_skip

Path parameters

uuid uuid Required
Run UUID.

Request body

reason string Required
Length 1–255.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.control, 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/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."
        }
    }
}
Response 200
{
  "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

POST /routines/media

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 scopewrite
Permission routines.fill
MCP toolroutines_media_upload
Content typemultipart/form-data application/json

Request body

file file Required
Image file (multipart; in MCP — object with name and content_base64).
kind string
Default photo.
photosignature

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.fill, is read-only
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/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"
        }
    }
}
Response 201
{
  "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

GET /routines/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 scoperead
Permission routines.view
MCP toolroutines_issues · read-only

Query parameters

page integer
Page number, starting from 1. Default 1. Minimum 1.
per_page integer
Items per page (max 100). Default 25. Value 1–100.
location_ids array<integer>
Only these locations.
date date
Only this local day.
date_from date
Local day from (inclusive).
date_to date
Local day to (inclusive).
routine_id integer
Only runs of this routine.
critical boolean
Only critical steps.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks routines.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/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"
        }
    }
}
Response 200
{
  "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

GET /routines/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 scoperead
Permission analytics.view
MCP toolroutines_analytics · read-only

Query parameters

period string
Default 30d.
7d30d90d12m
date_from date
Custom period start (with date_to).
date_to date
Custom period end.
location_ids array<integer>
Only these locations.
routine_id integer
Only this routine.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks analytics.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/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"
        }
    }
}
Response 200
{
  "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
      }
    ]
  }
}