POS integration (iiko)
Two-way sync of staff clock records and shift schedules with the iiko POS system: connect with an iiko API key, link locations to iiko venues and employees to iiko employees, set sync rules (send clock records as attendance, open personal shifts on the till instantly, receive card swipes at the terminal as clock records, send the published schedule), run a sync for a period and read the sync log.
GETList POS connections
| Token scope | read |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_connections_list · read-only |
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, or the plan has no pos
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/pos/connections' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections', {
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/pos/connections",
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', 'pos/connections', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_connections_list",
"arguments": {}
}
}{
"data": [
{
"id": 3,
"provider": "iiko",
"name": "iiko",
"status": "active",
"last_sync_at": "2026-11-13T10:05:00+05:00"
}
]
}GETGet a POS connection
Connection status, sync rules, iiko reference data (venues, terminal groups, positions, attendance and schedule types), linked locations and counters. The API key itself is never returned.
| Token scope | read |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_connections_get · read-only |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/pos/connections/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/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/pos/connections/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', 'pos/connections/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_connections_get",
"arguments": {
"id": 42
}
}
}{
"data": {
"id": 3,
"uuid": "7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"provider": "iiko",
"name": "iiko",
"status": "active",
"base_url": null,
"default_url": "https://api-ru.iiko.services",
"key_kind": "login",
"key_hint": "••••9f2a",
"settings": {
"attendance_out": "closed",
"attendance_in": true,
"webhook": true,
"priority": "jecheck",
"merge_minutes": 30,
"delete_out": true,
"attendance_type_id": null,
"role_default": null,
"role_map": [],
"schedule_out": false,
"schedule_type_id": null,
"schedule_in": false,
"auto_match": true,
"since": null
},
"catalog": {
"organizations": [
{
"id": "a3f1…",
"name": "Abay Ave"
}
],
"terminal_groups": [
{
"id": "tg-1…",
"org_id": "a3f1…",
"name": "Main hall"
}
],
"roles": [
{
"id": "r-1…",
"name": "Cook",
"code": "CK"
}
],
"attendance_types": [],
"schedule_types": [
{
"id": "s-1…",
"name": "Day",
"code": "D"
}
]
},
"catalog_at": "2026-11-13T10:00:00+05:00",
"last_sync_at": "2026-11-13T10:05:00+05:00",
"last_webhook_at": null,
"last_error": null,
"last_error_at": null,
"webhook_url": "https://app.jecheck.io/hooks/pos/iiko/7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"sites": [
{
"department_id": 4637,
"external_id": "a3f1…",
"external_name": "Abay Ave",
"terminal_group_id": "tg-1…"
}
],
"stats": {
"people": 42,
"matched": 40,
"unmatched": 2,
"pending": 0,
"errors": 0,
"in_day": 18,
"out_day": 21,
"linked": 512
},
"created_at": "2026-11-01T09:00:00+05:00"
}
}POSTConnect iiko
Checks the key, loads iiko reference data, links locations with identical names. Sync starts from today; use since in the settings to change it.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_connections_create |
Request body
provider
string
iiko. iikoname
string
api_login
string
app_id
string
client_secret
string
api_key
string
base_url
string | null
Responses
| Status | Description |
|---|---|
| 201 | Created |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"provider": "iiko",
"name": "Replace the broken fridge seal",
"api_login": "string",
"app_id": "string"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"provider": "iiko",
"name": "Replace the broken fridge seal",
"api_login": "string",
"app_id": "string"
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/pos/connections",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"provider":"iiko","name":"Replace the broken fridge seal","api_login":"string","app_id":"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', 'pos/connections', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'provider' => 'iiko',
'name' => 'Replace the broken fridge seal',
'api_login' => 'string',
'app_id' => 'string',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_connections_create",
"arguments": {
"provider": "iiko",
"name": "Replace the broken fridge seal",
"api_login": "string",
"app_id": "string"
}
}
}{
"data": {
"id": 3,
"uuid": "7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"provider": "iiko",
"name": "iiko",
"status": "active",
"base_url": null,
"default_url": "https://api-ru.iiko.services",
"key_kind": "login",
"key_hint": "••••9f2a",
"settings": {
"attendance_out": "closed",
"attendance_in": true,
"webhook": true,
"priority": "jecheck",
"merge_minutes": 30,
"delete_out": true,
"attendance_type_id": null,
"role_default": null,
"role_map": [],
"schedule_out": false,
"schedule_type_id": null,
"schedule_in": false,
"auto_match": true,
"since": null
},
"catalog": {
"organizations": [
{
"id": "a3f1…",
"name": "Abay Ave"
}
],
"terminal_groups": [
{
"id": "tg-1…",
"org_id": "a3f1…",
"name": "Main hall"
}
],
"roles": [
{
"id": "r-1…",
"name": "Cook",
"code": "CK"
}
],
"attendance_types": [],
"schedule_types": [
{
"id": "s-1…",
"name": "Day",
"code": "D"
}
]
},
"catalog_at": "2026-11-13T10:00:00+05:00",
"last_sync_at": "2026-11-13T10:05:00+05:00",
"last_webhook_at": null,
"last_error": null,
"last_error_at": null,
"webhook_url": "https://app.jecheck.io/hooks/pos/iiko/7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"sites": [
{
"department_id": 4637,
"external_id": "a3f1…",
"external_name": "Abay Ave",
"terminal_group_id": "tg-1…"
}
],
"stats": {
"people": 42,
"matched": 40,
"unmatched": 2,
"pending": 0,
"errors": 0,
"in_day": 18,
"out_day": 21,
"linked": 512
},
"created_at": "2026-11-01T09:00:00+05:00"
}
}PATCHUpdate a POS connection
Rename, pause (status: paused) or resume, or replace the API key (the new key is checked first).
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_connections_update |
Path parameters
id
integer
Required Request body
name
string
status
string
activepausedapi_login
string
app_id
string
client_secret
string
api_key
string
base_url
string | null
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X PATCH 'https://app.jecheck.io/api/ext/v1/pos/connections/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"name": "Replace the broken fridge seal",
"status": "active",
"api_login": "string",
"app_id": "string"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Replace the broken fridge seal",
"status": "active",
"api_login": "string",
"app_id": "string"
}),
});
const { data } = await res.json();import os, requests
res = requests.patch(
"https://app.jecheck.io/api/ext/v1/pos/connections/42",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"name":"Replace the broken fridge seal","status":"active","api_login":"string","app_id":"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', 'pos/connections/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'name' => 'Replace the broken fridge seal',
'status' => 'active',
'api_login' => 'string',
'app_id' => 'string',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_connections_update",
"arguments": {
"id": 42,
"name": "Replace the broken fridge seal",
"status": "active",
"api_login": "string",
"app_id": "string"
}
}
}{
"data": {
"id": 3,
"uuid": "7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"provider": "iiko",
"name": "iiko",
"status": "active",
"base_url": null,
"default_url": "https://api-ru.iiko.services",
"key_kind": "login",
"key_hint": "••••9f2a",
"settings": {
"attendance_out": "closed",
"attendance_in": true,
"webhook": true,
"priority": "jecheck",
"merge_minutes": 30,
"delete_out": true,
"attendance_type_id": null,
"role_default": null,
"role_map": [],
"schedule_out": false,
"schedule_type_id": null,
"schedule_in": false,
"auto_match": true,
"since": null
},
"catalog": {
"organizations": [
{
"id": "a3f1…",
"name": "Abay Ave"
}
],
"terminal_groups": [
{
"id": "tg-1…",
"org_id": "a3f1…",
"name": "Main hall"
}
],
"roles": [
{
"id": "r-1…",
"name": "Cook",
"code": "CK"
}
],
"attendance_types": [],
"schedule_types": [
{
"id": "s-1…",
"name": "Day",
"code": "D"
}
]
},
"catalog_at": "2026-11-13T10:00:00+05:00",
"last_sync_at": "2026-11-13T10:05:00+05:00",
"last_webhook_at": null,
"last_error": null,
"last_error_at": null,
"webhook_url": "https://app.jecheck.io/hooks/pos/iiko/7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"sites": [
{
"department_id": 4637,
"external_id": "a3f1…",
"external_name": "Abay Ave",
"terminal_group_id": "tg-1…"
}
],
"stats": {
"people": 42,
"matched": 40,
"unmatched": 2,
"pending": 0,
"errors": 0,
"in_day": 18,
"out_day": 21,
"linked": 512
},
"created_at": "2026-11-01T09:00:00+05:00"
}
}DELDisconnect iiko
Deletes the connection, its links, rules and log. Clock records and shifts stay in both systems.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_connections_delete · destructive |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X DELETE 'https://app.jecheck.io/api/ext/v1/pos/connections/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/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/pos/connections/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', 'pos/connections/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_connections_delete",
"arguments": {
"id": 42
}
}
}{
"data": {
"deleted": true
}
}POSTTest the connection and refresh reference data
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_connections_test |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections/42/test' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/test', {
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/pos/connections/42/test",
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', 'pos/connections/42/test', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_connections_test",
"arguments": {
"id": 42
}
}
}{
"data": {
"ok": true,
"organizations": 2
}
}PUTLink locations to iiko venues
Pass rows for the locations to change. Empty external_id unlinks a location. terminal_group_id is needed for instant (live) clock-ins; with a single terminal group it is chosen automatically.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_sites_set |
Path parameters
id
integer
Required Request body
sites
array<object>
Required department_id
integer
Required external_id
string | null
terminal_group_id
string | null
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X PUT 'https://app.jecheck.io/api/ext/v1/pos/connections/42/sites' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"sites": [
{
"department_id": 42,
"external_id": "string",
"terminal_group_id": "string"
}
]
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/sites', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"sites": [
{
"department_id": 42,
"external_id": "string",
"terminal_group_id": "string"
}
]
}),
});
const { data } = await res.json();import os, requests
res = requests.put(
"https://app.jecheck.io/api/ext/v1/pos/connections/42/sites",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"sites":[{"department_id":42,"external_id":"string","terminal_group_id":"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('PUT', 'pos/connections/42/sites', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'sites' => [
[
'department_id' => 42,
'external_id' => 'string',
'terminal_group_id' => 'string',
],
],
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_sites_set",
"arguments": {
"id": 42,
"sites": [
{
"department_id": 42,
"external_id": "string",
"terminal_group_id": "string"
}
]
}
}
}{
"data": {
"id": 3,
"uuid": "7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"provider": "iiko",
"name": "iiko",
"status": "active",
"base_url": null,
"default_url": "https://api-ru.iiko.services",
"key_kind": "login",
"key_hint": "••••9f2a",
"settings": {
"attendance_out": "closed",
"attendance_in": true,
"webhook": true,
"priority": "jecheck",
"merge_minutes": 30,
"delete_out": true,
"attendance_type_id": null,
"role_default": null,
"role_map": [],
"schedule_out": false,
"schedule_type_id": null,
"schedule_in": false,
"auto_match": true,
"since": null
},
"catalog": {
"organizations": [
{
"id": "a3f1…",
"name": "Abay Ave"
}
],
"terminal_groups": [
{
"id": "tg-1…",
"org_id": "a3f1…",
"name": "Main hall"
}
],
"roles": [
{
"id": "r-1…",
"name": "Cook",
"code": "CK"
}
],
"attendance_types": [],
"schedule_types": [
{
"id": "s-1…",
"name": "Day",
"code": "D"
}
]
},
"catalog_at": "2026-11-13T10:00:00+05:00",
"last_sync_at": "2026-11-13T10:05:00+05:00",
"last_webhook_at": null,
"last_error": null,
"last_error_at": null,
"webhook_url": "https://app.jecheck.io/hooks/pos/iiko/7d1c2f4e-6a8b-4c3d-9e1f-2a3b4c5d6e7f",
"sites": [
{
"department_id": 4637,
"external_id": "a3f1…",
"external_name": "Abay Ave",
"terminal_group_id": "tg-1…"
}
],
"stats": {
"people": 42,
"matched": 40,
"unmatched": 2,
"pending": 0,
"errors": 0,
"in_day": 18,
"out_day": 21,
"linked": 512
},
"created_at": "2026-11-01T09:00:00+05:00"
}
}PATCHUpdate sync rules
Partial update — pass only the rules to change.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_settings_update |
Path parameters
id
integer
Required Request body
attendance_out
string
closed — a closed attendance after clock-out; live — open/close the personal shift on the till instantly; off.
offclosedliveattendance_in
boolean
webhook
boolean
priority
string
jecheckposmerge_minutes
integer
delete_out
boolean
attendance_type_id
string | null
role_default
string | null
role_map
object
schedule_out
boolean
schedule_type_id
string | null
schedule_in
boolean
auto_match
boolean
since
date | null
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X PATCH 'https://app.jecheck.io/api/ext/v1/pos/connections/42/settings' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"attendance_out": "off",
"attendance_in": true,
"webhook": true,
"priority": "jecheck"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/settings', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"attendance_out": "off",
"attendance_in": true,
"webhook": true,
"priority": "jecheck"
}),
});
const { data } = await res.json();import os, requests
res = requests.patch(
"https://app.jecheck.io/api/ext/v1/pos/connections/42/settings",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"attendance_out":"off","attendance_in":True,"webhook":True,"priority":"jecheck"},
)
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', 'pos/connections/42/settings', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'attendance_out' => 'off',
'attendance_in' => true,
'webhook' => true,
'priority' => 'jecheck',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_settings_update",
"arguments": {
"id": 42,
"attendance_out": "off",
"attendance_in": true,
"webhook": true,
"priority": "jecheck"
}
}
}{
"data": {
"attendance_out": "closed",
"attendance_in": true,
"webhook": true,
"priority": "jecheck",
"merge_minutes": 30,
"delete_out": true,
"attendance_type_id": null,
"role_default": null,
"role_map": [],
"schedule_out": false,
"schedule_type_id": null,
"schedule_in": false,
"auto_match": true,
"since": null
}
}GETList iiko employees and their jeCheck matches
| Token scope | read |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_people_list · read-only |
Path parameters
id
integer
Required Query parameters
page
integer
1. Minimum 1.
per_page
integer
25. Value 1–100.
state
string
matchedunmatchedq
string
Responses
| Status | Description |
|---|---|
| 200 | OK — a page of results with meta |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/pos/connections/42/people?state=matched' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/people?state=matched', {
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/pos/connections/42/people",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"state":"matched"},
)
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', 'pos/connections/42/people', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'state' => 'matched',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_people_list",
"arguments": {
"id": 42,
"state": "matched"
}
}
}{
"data": [
{
"id": 81,
"external_id": "e1…",
"name": "Aliya Sarsenova",
"code": "101",
"card_tail": "4321",
"phone": "+77011112233",
"email": null,
"role": "Cook",
"fired": false,
"matched_by": "phone",
"user": {
"id": 2291,
"uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
"name": "Aliya Sarsenova"
}
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}POSTLoad employees from iiko
Updates the iiko employee list and matches new employees automatically (if auto_match is on).
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_people_refresh |
Path parameters
id
integer
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections/42/people/refresh' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/people/refresh', {
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/pos/connections/42/people/refresh",
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', 'pos/connections/42/people/refresh', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_people_refresh",
"arguments": {
"id": 42
}
}
}{
"data": {
"total": 42,
"matched": 3,
"unmatched": 2
}
}PUTMatch an iiko employee with a jeCheck user
user_id: null removes the match. A jeCheck user can be matched with one iiko employee per connection.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_people_link |
Path parameters
id
integer
Required person_id
integer
Required Request body
user_id
integer | null
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X PUT 'https://app.jecheck.io/api/ext/v1/pos/connections/42/people/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"user_id": 42
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/people/42', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"user_id": 42
}),
});
const { data } = await res.json();import os, requests
res = requests.put(
"https://app.jecheck.io/api/ext/v1/pos/connections/42/people/42",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"user_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('PUT', 'pos/connections/42/people/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'user_id' => 42,
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_people_link",
"arguments": {
"id": 42,
"person_id": 42,
"user_id": 42
}
}
}{
"data": {
"ok": true
}
}POSTCreate jeCheck employees in iiko
For users without an iiko match: the iiko code is the employee number (or JC + user id), the venue is the home location, the position is role_default.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_people_export |
Path parameters
id
integer
Required Request body
user_ids
array<integer>
Required Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections/42/people/export' \
-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/pos/connections/42/people/export', {
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/pos/connections/42/people/export",
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', 'pos/connections/42/people/export', [
'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": "pos_people_export",
"arguments": {
"id": 42,
"user_ids": [
12,
14
]
}
}
}{
"data": {
"created": 2,
"errors": []
}
}POSTSync a period now
attendance — fetch iiko attendance (default: the last 2 days) and send what is queued; resend — queue and send closed clock records of the period; schedule_in — import the iiko schedule as draft shifts. Up to two months; no duplicates are created.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_sync |
Path parameters
id
integer
Required Request body
what
string
Required attendanceresendschedule_infrom
date
to
date
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections/42/sync' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"what": "attendance",
"from": "2026-11-01",
"to": "2026-11-30"
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/sync', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"what": "attendance",
"from": "2026-11-01",
"to": "2026-11-30"
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/pos/connections/42/sync",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"what":"attendance","from":"2026-11-01","to":"2026-11-30"},
)
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', 'pos/connections/42/sync', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'what' => 'attendance',
'from' => '2026-11-01',
'to' => '2026-11-30',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_sync",
"arguments": {
"id": 42,
"what": "attendance",
"from": "2026-11-01",
"to": "2026-11-30"
}
}
}{
"data": {
"created": 3,
"updated": 1,
"linked": 12,
"closed": 0,
"skipped": 0,
"sent": 2
}
}POSTSubscribe to iiko personal-shift events
Sets the jeCheck webhook for every linked venue. If the key already has another webhook address, the venue is reported as busy unless force is true (iiko keeps one address per key — prefer a separate key for jeCheck).
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_webhook_install |
Path parameters
id
integer
Required Request body
force
boolean
false. Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections/42/webhook' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"force": false
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/webhook', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"force": false
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/pos/connections/42/webhook",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"force":False},
)
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', 'pos/connections/42/webhook', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'force' => false,
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_webhook_install",
"arguments": {
"id": 42,
"force": false
}
}
}{
"data": [
{
"org_id": "a3f1…",
"status": "ok"
}
]
}GETSync log
Every record sent to or received from iiko, newest first. status: ok, error, pending (queued or waiting for a retry), skipped.
| Token scope | read |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_events_list · read-only |
Path parameters
id
integer
Required Query parameters
page
integer
1. Minimum 1.
per_page
integer
25. Value 1–100.
status
string
okerrorpendingskippeddirection
string
inoutsysentity
string
attendanceshiftemployeecatalogwebhookconnectionResponses
| Status | Description |
|---|---|
| 200 | OK — a page of results with meta |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl 'https://app.jecheck.io/api/ext/v1/pos/connections/42/events?status=ok' \
-H 'Authorization: Bearer $JECHECK_TOKEN'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/events?status=ok', {
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/pos/connections/42/events",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"status":"ok"},
)
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', 'pos/connections/42/events', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'status' => 'ok',
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_events_list",
"arguments": {
"id": 42,
"status": "ok"
}
}
}{
"data": [
{
"id": 9012,
"direction": "out",
"entity": "attendance",
"action": "sync",
"status": "ok",
"message": "Attendance created",
"local_id": 55123,
"external_id": "b7e2…",
"attempts": 1,
"user": "Aliya Sarsenova",
"department": "Abay Ave",
"next_try_at": null,
"created_at": "2026-11-13T18:02:11+05:00"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}POSTRetry failed sends
Retries one event (event_id) or all failed and pending outgoing events.
| Token scope | write |
|---|---|
| Permission |
pos.manage |
| Plan feature | pos |
| MCP tool | pos_events_retry |
Path parameters
id
integer
Required Request body
event_id
integer
Responses
| Status | Description |
|---|---|
| 200 | OK |
| 401 | unauthorized — Missing, invalid, revoked or expired token
|
| 403 | forbidden · insufficient_scope · plan_required — Token lacks pos.manage, is read-only, or the plan has no pos
|
| 404 | not_found — The record does not exist or is not visible to the token
|
| 422 | validation_failed — Invalid parameters — see error.fields
|
| 429 | rate_limited — Too many requests — retry after Retry-After seconds
|
curl -X POST 'https://app.jecheck.io/api/ext/v1/pos/connections/42/events/retry' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"event_id": 42
}'const res = await fetch('https://app.jecheck.io/api/ext/v1/pos/connections/42/events/retry', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"event_id": 42
}),
});
const { data } = await res.json();import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/pos/connections/42/events/retry",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"event_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', 'pos/connections/42/events/retry', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'event_id' => 42,
],
]);
$data = json_decode($res->getBody(), true)['data'];{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "pos_events_retry",
"arguments": {
"id": 42,
"event_id": 42
}
}
}{
"data": {
"retried": 2
}
}