jeCheckDevelopers
Get an API key

GETGet dispatcher settings

GET /dispatcher/settings

Whether the dispatcher is on, which duties it performs and its limits. Duties: task_flow — asks about stuck/overdue tasks; audit_triage — makes sure every audit finding has an owner who accepted it; fix_verification — asks reviewers to accept or return fixes, closes tasks when the item passes in a new audit (close_on_pass); haccp_urgent — critical HACCP deviations; plan_discipline — overdue planned audits; morning_digest — manager digest; intake — requests written to the bot.

Token scoperead
Permission dispatcher.view or dispatcher.manage
MCP tooldispatcher_settings_get · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.view / dispatcher.manage
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/dispatcher/settings' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/settings', {
  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/dispatcher/settings",
    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', 'dispatcher/settings', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_settings_get",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "enabled": true,
    "duties": {
      "task_flow": true,
      "audit_triage": true,
      "fix_verification": true,
      "haccp_urgent": true,
      "plan_discipline": true,
      "morning_digest": true,
      "intake": true
    },
    "stale_days": 3,
    "escalate_after": 2,
    "accept_hours": 2,
    "due_shift_days": 7,
    "due_shift_max": 2,
    "close_on_pass": true,
    "haccp_minutes": 30,
    "digest_hour": 8,
    "work_from": 9,
    "work_to": 20
  }
}

PATCHUpdate dispatcher settings

PATCH /dispatcher/settings

Partial update: pass only what changes. Numbers are clamped to their allowed ranges. Turning the dispatcher on requires a plan with AI features.

Token scopewrite
Permission dispatcher.manage
MCP tooldispatcher_settings_update

Request body

enabled boolean
Turn the dispatcher on or off.
duties object
Duties to turn on or off.
task_flow boolean
audit_triage boolean
fix_verification boolean
haccp_urgent boolean
plan_discipline boolean
morning_digest boolean
intake boolean
close_on_pass boolean
Close a task when its item passes in a new audit at the same location.
stale_days integer
Value 1–14.
escalate_after integer
Value 1–5.
accept_hours integer
Value 1–24.
due_shift_days integer
Value 0–30.
due_shift_max integer
Value 0–5.
haccp_minutes integer
Value 5–240.
digest_hour integer
Value 5–12.
work_from integer
Value 0–23.
work_to integer
Value 1–24.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.manage, is read-only
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/dispatcher/settings' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "enabled": true,
    "duties": {
        "task_flow": true,
        "audit_triage": true,
        "fix_verification": true
    },
    "close_on_pass": true,
    "stale_days": 42
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/settings', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "enabled": true,
      "duties": {
          "task_flow": true,
          "audit_triage": true,
          "fix_verification": true
      },
      "close_on_pass": true,
      "stale_days": 42
  }),
});
const { data } = await res.json();
import os, requests

res = requests.patch(
    "https://app.jecheck.io/api/ext/v1/dispatcher/settings",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"enabled":True,"duties":{"task_flow":True,"audit_triage":True,"fix_verification":True},"close_on_pass":True,"stale_days":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('PATCH', 'dispatcher/settings', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'enabled' => true,
        'duties' => [
            'task_flow' => true,
            'audit_triage' => true,
            'fix_verification' => true,
        ],
        'close_on_pass' => true,
        'stale_days' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_settings_update",
        "arguments": {
            "enabled": true,
            "duties": {
                "task_flow": true,
                "audit_triage": true,
                "fix_verification": true
            },
            "close_on_pass": true,
            "stale_days": 42
        }
    }
}
Response 200
{
  "data": {
    "enabled": true,
    "duties": {
      "task_flow": true,
      "audit_triage": true,
      "fix_verification": true,
      "haccp_urgent": true,
      "plan_discipline": true,
      "morning_digest": true,
      "intake": true
    },
    "stale_days": 3,
    "escalate_after": 2,
    "accept_hours": 2,
    "due_shift_days": 7,
    "due_shift_max": 2,
    "close_on_pass": true,
    "haccp_minutes": 30,
    "digest_hour": 8,
    "work_from": 9,
    "work_to": 20
  }
}

GETGet dispatcher metrics

GET /dispatcher/metrics

Last 7 days and the 7 days before: reaction_minutes — median time from a question about audit findings to the answer; accepted_share — % of findings accepted within accept_hours; on_time_share — % of tasks closed on time; without_manager_share — % of task questions resolved without a manager; answered_share — % of questions answered; messages — messages sent. stale_now — open tasks without movement for over 7 days; coverage — employees with Telegram connected.

Token scoperead
Permission dispatcher.view or dispatcher.manage
MCP tooldispatcher_metrics · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.view / dispatcher.manage
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/dispatcher/metrics' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/metrics', {
  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/dispatcher/metrics",
    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', 'dispatcher/metrics', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_metrics",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "current": {
      "reaction_minutes": 38,
      "accepted_share": 81,
      "on_time_share": 74,
      "without_manager_share": 88,
      "answered_share": 92,
      "messages": 146,
      "triage": 21,
      "closed_tasks": 57
    },
    "previous": {
      "reaction_minutes": 95,
      "accepted_share": 62,
      "on_time_share": 61,
      "without_manager_share": 80,
      "answered_share": 85,
      "messages": 120,
      "triage": 18,
      "closed_tasks": 49
    },
    "stale_now": 6,
    "coverage": {
      "linked": 34,
      "total": 41
    }
  }
}

GETList dispatcher conversations

GET /dispatcher/threads

Conversations of the dispatcher about one item each (a task, an audit, a HACCP deviation, a planned audit, a request), newest first. reason (why the assignee is blocked or says the task is not theirs) is returned only with the dispatcher.messages permission.

Token scoperead
Permission dispatcher.view or dispatcher.manage
MCP tooldispatcher_threads_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.
duty string
Only this duty.
task_flowaudit_triagefix_verificationhaccp_urgentplan_disciplinemorning_digestintake
state string
Only this state.
openwaitingescalateddoneclosed
subject_type string
Only conversations about this kind of item.
taskaudithaccpplanintake
subject_id integer
Only conversations about this item (with subject_type).
user_id integer
Only conversations with this employee.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.view / dispatcher.manage
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/dispatcher/threads?duty=task_flow' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/threads?duty=task_flow', {
  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/dispatcher/threads",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"duty":"task_flow"},
)
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', 'dispatcher/threads', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'duty' => 'task_flow',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_threads_list",
        "arguments": {
            "duty": "task_flow"
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 512,
      "duty": "task_flow",
      "subject_type": "task",
      "subject_id": 88231,
      "user": {
        "id": 42,
        "name": "Aliya Sarsenova"
      },
      "role": "executor",
      "state": "waiting",
      "awaiting": null,
      "attempts": 1,
      "escalation_level": 0,
      "next_at": "2026-11-08T09:00:00+05:00",
      "answered_at": "2026-11-04T11:20:00+05:00",
      "created_at": "2026-11-04T11:02:00+05:00",
      "updated_at": "2026-11-04T11:20:00+05:00",
      "reason": "Waiting for the supplier to deliver a new seal"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "has_more": false
  }
}

GETRetrieve a conversation

GET /dispatcher/threads/{id}

A conversation with its messages. Message texts are returned only with the dispatcher.messages permission (each view is recorded in the access log); otherwise messages is null.

Token scoperead
Permission dispatcher.view or dispatcher.manage
MCP tooldispatcher_threads_get · read-only

Path parameters

id integer Required
Conversation id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.view / dispatcher.manage
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/dispatcher/threads/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/threads/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/dispatcher/threads/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', 'dispatcher/threads/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_threads_get",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "id": 512,
    "duty": "task_flow",
    "subject_type": "task",
    "subject_id": 88231,
    "user": {
      "id": 42,
      "name": "Aliya Sarsenova"
    },
    "role": "executor",
    "state": "waiting",
    "awaiting": null,
    "attempts": 1,
    "escalation_level": 0,
    "next_at": "2026-11-08T09:00:00+05:00",
    "answered_at": "2026-11-04T11:20:00+05:00",
    "created_at": "2026-11-04T11:02:00+05:00",
    "updated_at": "2026-11-04T11:20:00+05:00",
    "reason": "Waiting for the supplier to deliver a new seal",
    "messages": [
      {
        "id": 9001,
        "direction": "out",
        "channel": "telegram",
        "status": "sent",
        "text": "The task is 2 days overdue.",
        "buttons": [
          [
            {
              "text": "Done"
            },
            {
              "text": "Need more time"
            }
          ]
        ],
        "sent_at": "2026-11-04T11:02:00+05:00"
      },
      {
        "id": 9002,
        "direction": "in",
        "channel": "telegram",
        "status": "received",
        "text": null,
        "buttons": null,
        "button": "time",
        "sent_at": "2026-11-04T11:19:00+05:00"
      }
    ]
  }
}

GETList employees and their Telegram and Slack connection

GET /dispatcher/people

Active employees of the company: whether Telegram is connected (linked), stopped the bot (blocked), whether a phone is set in the profile (needed to connect by sharing the phone number in the bot), and whether the employee was found in the connected Slack workspace (slack) — then the dispatcher writes to them in Slack.

Token scoperead
Permission dispatcher.manage
MCP tooldispatcher_people · 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.
linked boolean
Only connected (true) or not connected (false) employees.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.manage
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/dispatcher/people?linked=true' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/people?linked=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/dispatcher/people",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"linked":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', 'dispatcher/people', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'linked' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_people",
        "arguments": {
            "linked": true
        }
    }
}
Response 200
{
  "data": [
    {
      "user": {
        "id": 42,
        "name": "Aliya Sarsenova"
      },
      "linked": true,
      "blocked": false,
      "username": "aliya_s",
      "has_phone": true,
      "slack": false
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "has_more": false
  }
}

GETGet the Slack connection

GET /dispatcher/slack

Whether the company connected its Slack workspace to the dispatcher. Employees are matched by email; matched employees get dispatcher messages in Slack instead of Telegram. Connecting needs the Slack sign-in in a browser — open install_url while signed in to jeCheck.

Token scoperead
Permission dispatcher.view
MCP tooldispatcher_slack_get · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.view
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/dispatcher/slack' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/slack', {
  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/dispatcher/slack",
    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', 'dispatcher/slack', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_slack_get",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "available": true,
    "connected": true,
    "team": "Harbor & Oak",
    "matched": 18,
    "synced_at": "2026-11-04T12:10:00+05:00",
    "install_url": "https://app.jecheck.io/ui/dispatcher/slack/install"
  }
}

POSTMatch Slack members to employees again

POST /dispatcher/slack/sync

Re-reads the Slack workspace members and matches them to employees by email (after new hires or email changes).

Token scopewrite
Permission dispatcher.manage
MCP tooldispatcher_slack_sync

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.manage, is read-only
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/dispatcher/slack/sync' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/slack/sync', {
  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/dispatcher/slack/sync",
    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', 'dispatcher/slack/sync', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_slack_sync",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "matched": 18,
    "members": 24
  }
}

DELDisconnect Slack

DELETE /dispatcher/slack

Removes the jeCheck app from the Slack workspace; the dispatcher writes to employees in Telegram or via push notifications again.

Token scopewrite
Permission dispatcher.manage
MCP tooldispatcher_slack_disconnect · destructive

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.manage, is read-only
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X DELETE 'https://app.jecheck.io/api/ext/v1/dispatcher/slack' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/slack', {
  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/dispatcher/slack",
    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', 'dispatcher/slack', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_slack_disconnect",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "disconnected": true
  }
}

POSTCreate Telegram connection links

POST /dispatcher/invites

One-time links (valid 7 days) that connect each employee to the dispatcher bot. Send them by your own channel, or connect employees by a QR code with the bot link: an employee shares the phone number and the bot finds them by the profile phone.

Token scopewrite
Permission dispatcher.manage
MCP tooldispatcher_invites_create

Request body

user_ids array<integer> Required
Employee ids. Maximum items 500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks dispatcher.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/dispatcher/invites' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_ids": [
        12,
        14
    ]
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/dispatcher/invites', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "user_ids": [
          12,
          14
      ]
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/dispatcher/invites",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"user_ids":[12,14]},
)
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', 'dispatcher/invites', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'user_ids' => [
            12,
            14,
        ],
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_invites_create",
        "arguments": {
            "user_ids": [
                12,
                14
            ]
        }
    }
}
Response 200
{
  "data": {
    "links": [
      {
        "user_id": 42,
        "url": "https://t.me/jecheck_bot?start=Qm3x8YtK2pZr7Lw0Vn5aBcDe"
      }
    ]
  }
}

GETList my open dispatcher questions

GET /me/dispatcher/questions

Questions the dispatcher is waiting for the acting user to answer (the same as in Telegram and its mini app): stuck or overdue tasks, audit findings to accept, fixes to confirm, HACCP alerts, overdue planned audits, due date approvals. text is the question in HTML (Telegram subset); buttons are the possible answers — pass action and arg of a button to the answer operation. input tells what else is accepted: text (free answer), date, reason, who (free text) or photo.

Token scoperead
Permission Any employee; results follow the token's visibility
MCP tooldispatcher_me_questions · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Plan without API access
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/me/dispatcher/questions' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/me/dispatcher/questions', {
  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/me/dispatcher/questions",
    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', 'me/dispatcher/questions', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_me_questions",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "data": [
      {
        "id": 512,
        "duty": "task_flow",
        "role": "executor",
        "open": true,
        "state": "open",
        "awaiting": "answer",
        "input": "text",
        "photo": true,
        "text": "The task is overdue by 3 days.\n<b>Replace the shower seal</b> · Downtown club · due 01.11\n\nWhat is its status?",
        "buttons": [
          [
            {
              "text": "✅ Done",
              "action": "done",
              "arg": null
            },
            {
              "text": "🕒 Need time",
              "action": "time",
              "arg": null
            }
          ],
          [
            {
              "text": "⛔ Blocked",
              "action": "blocked",
              "arg": null
            },
            {
              "text": "↩️ Not mine",
              "action": "not_mine",
              "arg": null
            }
          ]
        ],
        "asked_at": "2026-11-04T11:02:00+05:00",
        "task": {
          "id": 88231,
          "key": "6f1c2a9e-3b7d-4c11-9a52-0d8e7f6b1a23",
          "title": "Replace the shower seal",
          "status": "in_progress",
          "location": "Downtown club",
          "due": "01.11.2026",
          "overdue": true,
          "needs_photo": true
        }
      }
    ]
  }
}

GETGet my dispatcher question

GET /me/dispatcher/questions/{id}

One question with task details: description, photos (links valid for 2 hours), last comments, the audit it came from.

Token scoperead
Permission Any employee; results follow the token's visibility
MCP tooldispatcher_me_questions_get · read-only

Path parameters

id integer Required
Question (conversation) id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Plan without API access
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/me/dispatcher/questions/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/me/dispatcher/questions/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/me/dispatcher/questions/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', 'me/dispatcher/questions/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_me_questions_get",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "id": 512,
    "duty": "task_flow",
    "role": "executor",
    "open": true,
    "state": "open",
    "awaiting": "answer",
    "input": "text",
    "photo": true,
    "text": "The task is overdue by 3 days.\n<b>Replace the shower seal</b> · Downtown club · due 01.11\n\nWhat is its status?",
    "buttons": [
      [
        {
          "text": "✅ Done",
          "action": "done",
          "arg": null
        },
        {
          "text": "🕒 Need time",
          "action": "time",
          "arg": null
        }
      ],
      [
        {
          "text": "⛔ Blocked",
          "action": "blocked",
          "arg": null
        },
        {
          "text": "↩️ Not mine",
          "action": "not_mine",
          "arg": null
        }
      ]
    ],
    "asked_at": "2026-11-04T11:02:00+05:00",
    "task": {
      "id": 88231,
      "key": "6f1c2a9e-3b7d-4c11-9a52-0d8e7f6b1a23",
      "title": "Replace the shower seal",
      "status": "in_progress",
      "location": "Downtown club",
      "due": "01.11.2026",
      "overdue": true,
      "needs_photo": true
    }
  }
}

POSTAnswer my dispatcher question

POST /me/dispatcher/questions/{id}/answer

Answers as the acting user, exactly like a button, a short message or a photo in Telegram: the dispatcher applies it in jeCheck (completes the task, moves the due date within the company norm or asks the manager, records the reason, attaches the after photo). Pass one of: action (+ arg) from the question buttons, text, or photo. Returns the dispatcher replies and the question state after the answer (open: false — nothing more is needed).

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP tooldispatcher_me_questions_answer
Content typemultipart/form-data application/json

Path parameters

id integer Required
Question (conversation) id.

Query parameters

photo file
Photo (after photo for a task, photo for a request).

Request body

action string
Button action, e.g. done, time, date, blocked, not_mine, accept, return, approve, deny. Maximum length 32.
arg string
Button argument, e.g. a date 2026-11-11 for date. Maximum length 40.
text string
Free-text answer (a reason, a new date in words, a comment). Maximum length 2000.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Read-only token
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/me/dispatcher/questions/42/answer' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "action": "string",
    "arg": "string",
    "text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/me/dispatcher/questions/42/answer', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "action": "string",
      "arg": "string",
      "text": "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/me/dispatcher/questions/42/answer",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"action":"string","arg":"string","text":"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', 'me/dispatcher/questions/42/answer', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'action' => 'string',
        'arg' => 'string',
        'text' => '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": "dispatcher_me_questions_answer",
        "arguments": {
            "id": 42,
            "action": "string",
            "arg": "string",
            "text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "replies": [
      {
        "html": "Moved the due date to 11.11. I will write if the task is not closed by then.",
        "buttons": []
      }
    ],
    "question": {
      "id": 512,
      "duty": "task_flow",
      "role": "executor",
      "open": false,
      "state": "waiting",
      "awaiting": null,
      "input": null,
      "photo": true,
      "text": "The task is overdue by 3 days.\n<b>Replace the shower seal</b> · Downtown club · due 01.11\n\nWhat is its status?",
      "buttons": [],
      "asked_at": "2026-11-04T11:02:00+05:00",
      "task": {
        "id": 88231,
        "key": "6f1c2a9e-3b7d-4c11-9a52-0d8e7f6b1a23",
        "title": "Replace the shower seal",
        "status": "in_progress",
        "location": "Downtown club",
        "due": "01.11.2026",
        "overdue": true,
        "needs_photo": true
      }
    }
  }
}

POSTSend a request to the dispatcher

POST /me/dispatcher/requests

What broke or what is needed, in free words (as a message to the bot). The dispatcher works out the location and the department and asks to confirm: the reply contains the question with the confirmation buttons — answer it with the answer operation. Needs the dispatcher and its «Requests» duty to be on.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP tooldispatcher_me_requests_create
Content typemultipart/form-data application/json

Query parameters

photo file
Photo of the problem.

Request body

text string Required
What happened or what is needed. Length 3–2000.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Read-only 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/me/dispatcher/requests' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/me/dispatcher/requests', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "text": "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/me/dispatcher/requests",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"text":"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', 'me/dispatcher/requests', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'text' => '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": "dispatcher_me_requests_create",
        "arguments": {
            "text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "replies": [
      {
        "html": "Request: <b>Shower tap is leaking</b> · Downtown club → Maintenance. Create?",
        "buttons": [
          [
            {
              "text": "Create",
              "action": "create",
              "arg": null
            },
            {
              "text": "Cancel",
              "action": "cancel",
              "arg": null
            }
          ]
        ]
      }
    ],
    "question": {
      "id": 640,
      "duty": "intake",
      "role": "executor",
      "open": true,
      "state": "open",
      "awaiting": "answer",
      "input": "text",
      "photo": true,
      "text": "The task is overdue by 3 days.\n<b>Replace the shower seal</b> · Downtown club · due 01.11\n\nWhat is its status?",
      "buttons": [
        [
          {
            "text": "✅ Done",
            "action": "done",
            "arg": null
          },
          {
            "text": "🕒 Need time",
            "action": "time",
            "arg": null
          }
        ],
        [
          {
            "text": "⛔ Blocked",
            "action": "blocked",
            "arg": null
          },
          {
            "text": "↩️ Not mine",
            "action": "not_mine",
            "arg": null
          }
        ]
      ],
      "asked_at": "2026-11-04T11:02:00+05:00",
      "task": null
    }
  }
}

GETGet my Telegram connection

GET /me/telegram

Whether the acting user receives dispatcher messages in Telegram.

Token scoperead
Permission Any employee; results follow the token's visibility
MCP tooldispatcher_me_telegram · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Plan without API access
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/me/telegram' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/me/telegram', {
  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/me/telegram",
    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', 'me/telegram', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "dispatcher_me_telegram",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "available": true,
    "linked": true,
    "username": "aliya_s"
  }
}