jeCheckDevelopers
Get an API key
Developers/API reference/Schedule & attendance

Schedule & attendance

Staff schedule: shifts, publishing, open shifts, clock-in/out with breaks, who is on shift now, attendance records, timesheet, absences and shift swaps, availability, KPI and payroll. Location permissions follow schedule.*; pay rates and payroll require pay.* and are omitted otherwise.

Operations
GET/schedule/shiftsList shifts GET/schedule/boardGet the schedule board of a location POST/schedule/shiftsCreate a shift PATCH/schedule/shifts/{id}Update a shift DELETE/schedule/shifts/{id}Delete a shift POST/schedule/publishPublish the schedule POST/schedule/copyCopy shifts to another period POST/schedule/autofillAuto-fill the schedule POST/schedule/clearClear shifts of a period GET/schedule/templatesList shift templates GET/schedule/my/shiftsList my shifts GET/schedule/my/monthGet my hours for a month GET/schedule/open-shiftsList open shifts I can take POST/schedule/open-shifts/{uuid}/claimClaim an open shift GET/schedule/clockGet my clock state POST/schedule/clock/inClock in POST/schedule/clock/outClock out POST/schedule/clock/break-startStart a break POST/schedule/clock/break-endEnd a break POST/schedule/mediaUpload a clock photo or request file GET/schedule/attendanceGet who is on shift GET/schedule/attendance/recordsList attendance records POST/schedule/attendance/recordsAdd an attendance record PATCH/schedule/attendance/records/{id}Correct an attendance record DELETE/schedule/attendance/records/{id}Delete an attendance record POST/schedule/attendance/records/approveApprove attendance records GET/schedule/timesheetGet the timesheet GET/schedule/payrollGet the payroll POST/schedule/payroll/approveApprove the payroll POST/schedule/payroll/reopenReopen the payroll POST/schedule/payroll/paidMark the payroll as paid POST/schedule/payroll/adjustmentsAdd a payroll adjustment DELETE/schedule/payroll/adjustments/{id}Delete a payroll adjustment GET/schedule/kpiGet the KPI scorecard POST/schedule/kpi/valuesSet a KPI value GET/schedule/requestsList absence and swap requests GET/schedule/my/requestsList my requests POST/schedule/absencesCreate an absence POST/schedule/absences/{uuid}/cancelCancel an absence POST/schedule/absences/{uuid}/decideApprove or reject an absence request POST/schedule/swapsRequest a shift swap POST/schedule/swaps/{uuid}/answerAnswer a swap request POST/schedule/swaps/{uuid}/cancelCancel a swap request POST/schedule/swaps/{uuid}/decideApprove or reject a swap request GET/schedule/availabilityGet availability PUT/schedule/availabilityReplace availability

GETList shifts

GET /schedule/shifts

Shifts at locations where the token has schedule.view, by date. Drafts and unpublished changes are included (status: draft, published, changed; removed — deleted but not yet published). Default period — the current week, max 62 days.

Token scoperead
Permission schedule.view
MCP toolschedule_shifts_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.
date_from date
From (inclusive).
date_to date
To (inclusive).
location_ids array<integer>
Only these locations.
user_ids array<integer>
Only these employees.
status string
draftpublishedchanged
open boolean
true — only open (unassigned) shifts.
include_removed boolean
Include removed shifts awaiting publication.
updated_since date-time
Only shifts 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 schedule.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/schedule/shifts?date_from=2026-11-01' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/shifts?date_from=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/schedule/shifts",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"date_from":"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', 'schedule/shifts', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'date_from' => '2026-11-01',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_shifts_list",
        "arguments": {
            "date_from": "2026-11-01"
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 70211,
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "user": {
        "id": 311,
        "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
        "name": "Aigerim Sadykova"
      },
      "position_id": 12,
      "template_id": 3,
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "starts_at": "2026-11-04T09:00:00+05:00",
      "ends_at": "2026-11-04T18:00:00+05:00",
      "overnight": false,
      "break_minutes": 60,
      "minutes": 480,
      "status": "published",
      "published": true,
      "removed": false,
      "open": false,
      "note": null,
      "color": null,
      "updated_at": "2026-11-01T12:00:00+05:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "has_more": false
  }
}

GETGet the schedule board of a location

GET /schedule/board

What the schedule grid shows for one location: employees with hour totals, shifts with warnings and attendance, absences, availability, demand coverage and totals. Shift cost, employee rates and the budget are included only with pay.rates.view at the location.

Token scoperead
Permission schedule.view
MCP toolschedule_board · read-only

Query parameters

location_id integer Required
Location id.
date_from date
date_to date

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.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/schedule/board?location_id=12' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/board?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/schedule/board",
    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', 'schedule/board', [
    '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": "schedule_board",
        "arguments": {
            "location_id": 12
        }
    }
}
Response 200
{
  "data": {
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "date_from": "2026-11-02",
    "date_to": "2026-11-08",
    "days": [
      {
        "date": "2026-11-02",
        "weekday": 1,
        "holiday": null
      }
    ],
    "employees": [
      {
        "id": 311,
        "name": "Aigerim Sadykova",
        "position_id": 12,
        "position": "Barista",
        "pay_type": "hourly",
        "rate": 1500,
        "totals": {
          "minutes": 2400,
          "shifts": 5,
          "cost": 60000
        }
      }
    ],
    "shifts": [
      {
        "id": 70211,
        "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "user": {
          "id": 311,
          "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "name": "Aigerim Sadykova"
        },
        "position_id": 12,
        "template_id": 3,
        "date": "2026-11-04",
        "start": "09:00",
        "end": "18:00",
        "starts_at": "2026-11-04T09:00:00+05:00",
        "ends_at": "2026-11-04T18:00:00+05:00",
        "overnight": false,
        "break_minutes": 60,
        "minutes": 480,
        "status": "published",
        "published": true,
        "removed": false,
        "open": false,
        "note": null,
        "color": null,
        "updated_at": "2026-11-01T12:00:00+05:00",
        "warnings": [],
        "cost": 12000,
        "attendance": null
      }
    ],
    "absences": [
      {
        "id": 912,
        "user_id": 311,
        "type": "vacation",
        "from": "2026-12-01",
        "to": "2026-12-14",
        "status": "approved"
      }
    ],
    "availability": [
      {
        "user_id": 311,
        "date": "2026-11-06",
        "kind": "unavailable",
        "start": null,
        "end": null,
        "note": "University"
      }
    ],
    "coverage": {
      "2026-11-02": [
        {
          "position_id": 12,
          "start": "08:00",
          "end": "12:00",
          "need": 2,
          "have": 1
        }
      ]
    },
    "totals": {
      "minutes": 9600,
      "cost": 240000,
      "budget": 300000
    },
    "unpublished": 3,
    "warnings_count": 1,
    "can_pay": true,
    "can_edit": true,
    "can_publish": true,
    "currency": "KZT"
  }
}

POSTCreate a shift

POST /schedule/shifts

Creates a draft shift (employees see it after schedule.publish). Rejected in a closed month.

Token scopewrite
Permission schedule.edit
MCP toolschedule_shifts_create

Request body

location_id integer Required
Location id (schedule.edit there).
user_id integer | null
Employee; null — open shift anyone eligible can claim.
position_id integer | null
Job position; default — the employee's position.
template_id integer | null
Shift template: fills start, end, break and position.
date date Required
Shift date (local).
start string
Start time HH:MM (local). Required without a template.
end string
End time HH:MM; earlier than start — ends next day.
break_minutes integer
Value 0–600.
note string | null
Maximum length 500.
color string | null
Maximum length 20.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.edit, 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/schedule/shifts' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "location_id": 12,
    "user_id": 42,
    "position_id": 42,
    "template_id": 42,
    "date": "2026-11-01"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/shifts', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "location_id": 12,
      "user_id": 42,
      "position_id": 42,
      "template_id": 42,
      "date": "2026-11-01"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/shifts",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"user_id":42,"position_id":42,"template_id":42,"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('POST', 'schedule/shifts', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'user_id' => 42,
        'position_id' => 42,
        'template_id' => 42,
        'date' => '2026-11-01',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_shifts_create",
        "arguments": {
            "location_id": 12,
            "user_id": 42,
            "position_id": 42,
            "template_id": 42,
            "date": "2026-11-01"
        }
    }
}
Response 201
{
  "data": {
    "id": 70211,
    "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "position_id": 12,
    "template_id": 3,
    "date": "2026-11-04",
    "start": "09:00",
    "end": "18:00",
    "starts_at": "2026-11-04T09:00:00+05:00",
    "ends_at": "2026-11-04T18:00:00+05:00",
    "overnight": false,
    "break_minutes": 60,
    "minutes": 480,
    "status": "published",
    "published": true,
    "removed": false,
    "open": false,
    "note": null,
    "color": null,
    "updated_at": "2026-11-01T12:00:00+05:00"
  }
}

PATCHUpdate a shift

PATCH /schedule/shifts/{id}

Partial update. A published shift becomes changed until published again.

Token scopewrite
Permission schedule.edit
MCP toolschedule_shifts_update

Path parameters

id integer Required
Shift id.

Request body

location_id integer
Location id (schedule.edit there).
user_id integer | null
Employee; null — open shift anyone eligible can claim.
position_id integer | null
Job position; default — the employee's position.
template_id integer | null
Shift template: fills start, end, break and position.
date date
Shift date (local).
start string
Start time HH:MM (local). Required without a template.
end string
End time HH:MM; earlier than start — ends next day.
break_minutes integer
Value 0–600.
note string | null
Maximum length 500.
color string | null
Maximum length 20.

Responses

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

res = requests.patch(
    "https://app.jecheck.io/api/ext/v1/schedule/shifts/42",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"user_id":42,"position_id":42,"template_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('PATCH', 'schedule/shifts/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'user_id' => 42,
        'position_id' => 42,
        'template_id' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_shifts_update",
        "arguments": {
            "id": 42,
            "location_id": 12,
            "user_id": 42,
            "position_id": 42,
            "template_id": 42
        }
    }
}
Response 200
{
  "data": {
    "id": 70211,
    "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "position_id": 12,
    "template_id": 3,
    "date": "2026-11-04",
    "start": "09:00",
    "end": "18:00",
    "starts_at": "2026-11-04T09:00:00+05:00",
    "ends_at": "2026-11-04T18:00:00+05:00",
    "overnight": false,
    "break_minutes": 60,
    "minutes": 480,
    "status": "published",
    "published": true,
    "removed": false,
    "open": false,
    "note": null,
    "color": null,
    "updated_at": "2026-11-01T12:00:00+05:00"
  }
}

DELDelete a shift

DELETE /schedule/shifts/{id}

Deletes a draft shift; a published one is marked removed until the schedule is published again.

Token scopewrite
Permission schedule.edit
MCP toolschedule_shifts_delete · destructive

Path parameters

id integer Required
Shift id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.edit, 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 DELETE 'https://app.jecheck.io/api/ext/v1/schedule/shifts/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/shifts/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/schedule/shifts/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', 'schedule/shifts/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_shifts_delete",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "deleted": true
  }
}

POSTPublish the schedule

POST /schedule/publish

Publishes drafts and changes of a location for a period; affected employees are notified (unless notify=false).

Token scopewrite
Permission schedule.publish
MCP toolschedule_publish

Request body

location_id integer Required
date_from date Required
date_to date Required
notify boolean
Notify employees, default true.

Responses

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

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/publish",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"date_from":"2026-11-01","date_to":"2026-11-30","notify":True},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'schedule/publish', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'date_from' => '2026-11-01',
        'date_to' => '2026-11-30',
        'notify' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_publish",
        "arguments": {
            "location_id": 12,
            "date_from": "2026-11-01",
            "date_to": "2026-11-30",
            "notify": true
        }
    }
}
Response 200
{
  "data": {
    "published": 24,
    "removed": 1,
    "notified": 9
  }
}

POSTCopy shifts to another period

POST /schedule/copy

Copies the shifts of days days starting at date_from to target_date, optionally repeated. Absent employees are skipped. Copies are drafts.

Token scopewrite
Permission schedule.edit
MCP toolschedule_copy

Request body

location_id integer Required
date_from date Required
days integer Required
Value 1–31.
target_date date Required
repeat integer
How many times to repeat, default 1. Value 1–12.
replace boolean
Replace existing shifts in the target period.

Responses

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

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/copy",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"date_from":"2026-11-01","days":42,"target_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('POST', 'schedule/copy', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'date_from' => '2026-11-01',
        'days' => 42,
        'target_date' => '2026-11-01',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_copy",
        "arguments": {
            "location_id": 12,
            "date_from": "2026-11-01",
            "days": 42,
            "target_date": "2026-11-01"
        }
    }
}
Response 200
{
  "data": {
    "created": 35
  }
}

POSTAuto-fill the schedule

POST /schedule/autofill

Assigns employees to shifts by demand, availability, absences and rules; unfilled demand becomes open shifts (unless create_open=false).

Token scopewrite
Permission schedule.edit
MCP toolschedule_autofill

Request body

location_id integer Required
date_from date Required
date_to date Required
create_open boolean

Responses

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

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/autofill",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"date_from":"2026-11-01","date_to":"2026-11-30","create_open":True},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'schedule/autofill', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'date_from' => '2026-11-01',
        'date_to' => '2026-11-30',
        'create_open' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_autofill",
        "arguments": {
            "location_id": 12,
            "date_from": "2026-11-01",
            "date_to": "2026-11-30",
            "create_open": true
        }
    }
}
Response 200
{
  "data": {
    "assigned": 18,
    "open": 2,
    "unfilled": 1
  }
}

POSTClear shifts of a period

POST /schedule/clear

Deletes the shifts of a location for a period (published ones are marked removed until publishing).

Token scopewrite
Permission schedule.edit
MCP toolschedule_clear · destructive

Request body

location_id integer Required
date_from date Required
date_to date Required

Responses

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

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/clear",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"date_from":"2026-11-01","date_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', 'schedule/clear', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'date_from' => '2026-11-01',
        'date_to' => '2026-11-30',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_clear",
        "arguments": {
            "location_id": 12,
            "date_from": "2026-11-01",
            "date_to": "2026-11-30"
        }
    }
}
Response 200
{
  "data": {
    "cleared": 12
  }
}

GETList shift templates

GET /schedule/templates

Company-wide shift templates and templates of the location.

Token scoperead
Permission schedule.view
MCP toolschedule_templates_list · read-only

Query parameters

location_id integer
Include templates of this location.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.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/schedule/templates?location_id=12' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/templates?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/schedule/templates",
    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', 'schedule/templates', [
    '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": "schedule_templates_list",
        "arguments": {
            "location_id": 12
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 3,
      "location_id": null,
      "name": "Morning",
      "code": "M",
      "start": "08:00",
      "end": "16:00",
      "break_minutes": 30,
      "position_id": null,
      "color": "#fab005"
    }
  ]
}

GETList my shifts

GET /schedule/my/shifts

Published shifts of the acting user with attendance and (if enabled by the company) colleagues on the same shift. Default — current week, max 62 days.

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

Query parameters

date_from date
date_to date

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Plan without API access
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/schedule/my/shifts?date_from=2026-11-01' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/my/shifts?date_from=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/schedule/my/shifts",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"date_from":"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', 'schedule/my/shifts', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'date_from' => '2026-11-01',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_my_shifts",
        "arguments": {
            "date_from": "2026-11-01"
        }
    }
}
Response 200
{
  "data": [
    {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "starts_at": "2026-11-04T09:00:00+05:00",
      "ends_at": "2026-11-04T18:00:00+05:00",
      "break_minutes": 60,
      "hours": 8,
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "position_id": 12,
      "position": "Barista",
      "note": null,
      "open": false,
      "pending_change": false,
      "attendance": null,
      "colleagues": [
        {
          "user_id": 312,
          "name": "Arman Bekov",
          "start": "09:00",
          "end": "18:00",
          "position": "Cashier",
          "shift_uuid": "3d4e5f6a-7b8c-4d9e-8f0a-1b2c3d4e5f6a"
        }
      ]
    }
  ]
}

GETGet my hours for a month

GET /schedule/my/month

Timesheet of the acting user for a month: totals and day codes; accrued pay only if the company shows pay to staff.

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

Query parameters

month string
Month YYYY-MM, default — current.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Plan without API access
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/schedule/my/month?month=string' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/my/month?month=string', {
  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/schedule/my/month",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"month":"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('GET', 'schedule/my/month', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'month' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_my_month",
        "arguments": {
            "month": "string"
        }
    }
}
Response 200
{
  "data": {
    "month": "2026-11",
    "totals": {
      "planned_hours": 168,
      "hours": 52.5,
      "night_hours": 0,
      "holiday_hours": 0,
      "overtime_hours": 0,
      "days": 7,
      "late_count": 1,
      "late_minutes": 4,
      "absences": []
    },
    "days": [
      {
        "date": "2026-11-03",
        "code": "Я",
        "planned": 480,
        "hours": 7.5,
        "late": 4
      }
    ],
    "pay": null
  }
}

GETList open shifts I can take

GET /schedule/open-shifts

Published unassigned future shifts at locations where the acting user may work, matching the position and without overlaps.

Token scoperead
Permission Any employee; results follow the token's visibility
MCP toolschedule_open_shifts_list · 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/schedule/open-shifts' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/open-shifts', {
  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/schedule/open-shifts",
    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', 'schedule/open-shifts', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_open_shifts_list",
        "arguments": {}
    }
}
Response 200
{
  "data": [
    {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "starts_at": "2026-11-04T09:00:00+05:00",
      "ends_at": "2026-11-04T18:00:00+05:00",
      "break_minutes": 60,
      "hours": 8,
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "position_id": 12,
      "position": "Barista",
      "note": null,
      "open": false,
      "requested": false,
      "taken": false
    }
  ]
}

POSTClaim an open shift

POST /schedule/open-shifts/{uuid}/claim

Takes an open shift: assigned right away or sent to the manager, depending on company rules.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_open_shifts_claim

Path parameters

uuid uuid Required
Shift UUID.

Request body

note string
Maximum length 500.

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/schedule/open-shifts/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/claim' \
  -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/schedule/open-shifts/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/claim', {
  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/schedule/open-shifts/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/claim",
    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', 'schedule/open-shifts/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/claim', [
    '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": "schedule_open_shifts_claim",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "status": "approved"
  }
}

GETGet my clock state

GET /schedule/clock

Whether the acting user is on shift / on a break, the open attendance, current and next shift, clock-in locations with geofence and the company clock rules.

Token scoperead
Permission Any employee; results follow the token's visibility
MCP toolschedule_clock_state · 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/schedule/clock' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/clock', {
  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/schedule/clock",
    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', 'schedule/clock', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_clock_state",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "on_shift": true,
    "on_break": false,
    "attendance": {
      "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
      "date": "2026-11-03",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "clock_in_at": "2026-11-03T09:04:00+05:00",
      "clock_out_at": null,
      "breaks": [
        {
          "start": "13:00",
          "end": "13:30"
        }
      ],
      "status": "open",
      "late_minutes": 4,
      "early_minutes": 0,
      "worked_minutes": 0,
      "approved_minutes": null,
      "flags": [
        "late"
      ]
    },
    "current_shift": {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "starts_at": "2026-11-04T09:00:00+05:00",
      "ends_at": "2026-11-04T18:00:00+05:00",
      "break_minutes": 60,
      "hours": 8,
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "position_id": 12,
      "position": "Barista",
      "note": null,
      "open": false
    },
    "next_shift": null,
    "sites": [
      {
        "id": 4609,
        "name": "Abay Ave",
        "latitude": 43.2389,
        "longitude": 76.8897,
        "radius": 150
      }
    ],
    "rules": {
      "require_geo": true,
      "require_photo": false,
      "block_out_of_zone": false,
      "allow_without_shift": true,
      "early_clock_in_minutes": 30,
      "allow_web_clock": false
    },
    "server_time": "2026-11-03T09:05:00+05:00"
  }
}

POSTClock in

POST /schedule/clock/in

Starts attendance for the acting user (same rules as the mobile app: geofence, photo, early clock-in limit, matching shift). Repeating the same uuid returns the existing record. Errors carry a reason code: already_in, no_shift, too_early, geo_required, out_of_zone, photo_required, qr_invalid, department_required, bad_time.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_clock_in

Request body

uuid uuid
Client UUID of the clock-in, for safe retries.
attendance_uuid uuid
Attendance to close (clock-out); default — the open one.
location_id integer
Location; default — from the shift or geolocation.
shift_uuid uuid
Shift being worked.
qr string
Scanned QR content (link /clock/{token} or the token). Maximum length 500.
lat number
lng number
photo string
Photo path from schedule.media.upload.
recorded_at date-time
Device time for offline sync.
note string
Maximum length 500.

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/schedule/clock/in' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "location_id": 12,
    "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/clock/in', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "location_id": 12,
      "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/clock/in",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","attendance_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","location_id":12,"shift_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"},
)
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', 'schedule/clock/in', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'attendance_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'location_id' => 12,
        'shift_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_clock_in",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "location_id": 12,
            "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "attendance": {
      "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
      "date": "2026-11-03",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "clock_in_at": "2026-11-03T09:04:00+05:00",
      "clock_out_at": null,
      "breaks": [
        {
          "start": "13:00",
          "end": "13:30"
        }
      ],
      "status": "open",
      "late_minutes": 4,
      "early_minutes": 0,
      "worked_minutes": 0,
      "approved_minutes": null,
      "flags": [
        "late"
      ]
    },
    "clock": {
      "on_shift": true,
      "on_break": false,
      "attendance": {
        "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
        "date": "2026-11-03",
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "clock_in_at": "2026-11-03T09:04:00+05:00",
        "clock_out_at": null,
        "breaks": [
          {
            "start": "13:00",
            "end": "13:30"
          }
        ],
        "status": "open",
        "late_minutes": 4,
        "early_minutes": 0,
        "worked_minutes": 0,
        "approved_minutes": null,
        "flags": [
          "late"
        ]
      },
      "current_shift": {
        "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
        "date": "2026-11-04",
        "start": "09:00",
        "end": "18:00",
        "starts_at": "2026-11-04T09:00:00+05:00",
        "ends_at": "2026-11-04T18:00:00+05:00",
        "break_minutes": 60,
        "hours": 8,
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "position_id": 12,
        "position": "Barista",
        "note": null,
        "open": false
      },
      "next_shift": null,
      "sites": [
        {
          "id": 4609,
          "name": "Abay Ave",
          "latitude": 43.2389,
          "longitude": 76.8897,
          "radius": 150
        }
      ],
      "rules": {
        "require_geo": true,
        "require_photo": false,
        "block_out_of_zone": false,
        "allow_without_shift": true,
        "early_clock_in_minutes": 30,
        "allow_web_clock": false
      },
      "server_time": "2026-11-03T09:05:00+05:00"
    }
  }
}

POSTClock out

POST /schedule/clock/out

Closes the open attendance of the acting user and computes worked minutes. Errors: not_in, break_open, bad_time.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_clock_out

Request body

uuid uuid
Client UUID of the clock-in, for safe retries.
attendance_uuid uuid
Attendance to close (clock-out); default — the open one.
location_id integer
Location; default — from the shift or geolocation.
shift_uuid uuid
Shift being worked.
qr string
Scanned QR content (link /clock/{token} or the token). Maximum length 500.
lat number
lng number
photo string
Photo path from schedule.media.upload.
recorded_at date-time
Device time for offline sync.
note string
Maximum length 500.

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/schedule/clock/out' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "location_id": 12,
    "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/clock/out', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "location_id": 12,
      "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/clock/out",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","attendance_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","location_id":12,"shift_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"},
)
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', 'schedule/clock/out', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'attendance_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'location_id' => 12,
        'shift_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_clock_out",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "location_id": 12,
            "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "attendance": {
      "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
      "date": "2026-11-03",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "clock_in_at": "2026-11-03T09:04:00+05:00",
      "clock_out_at": null,
      "breaks": [
        {
          "start": "13:00",
          "end": "13:30"
        }
      ],
      "status": "open",
      "late_minutes": 4,
      "early_minutes": 0,
      "worked_minutes": 0,
      "approved_minutes": null,
      "flags": [
        "late"
      ]
    },
    "clock": {
      "on_shift": true,
      "on_break": false,
      "attendance": {
        "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
        "date": "2026-11-03",
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "clock_in_at": "2026-11-03T09:04:00+05:00",
        "clock_out_at": null,
        "breaks": [
          {
            "start": "13:00",
            "end": "13:30"
          }
        ],
        "status": "open",
        "late_minutes": 4,
        "early_minutes": 0,
        "worked_minutes": 0,
        "approved_minutes": null,
        "flags": [
          "late"
        ]
      },
      "current_shift": {
        "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
        "date": "2026-11-04",
        "start": "09:00",
        "end": "18:00",
        "starts_at": "2026-11-04T09:00:00+05:00",
        "ends_at": "2026-11-04T18:00:00+05:00",
        "break_minutes": 60,
        "hours": 8,
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "position_id": 12,
        "position": "Barista",
        "note": null,
        "open": false
      },
      "next_shift": null,
      "sites": [
        {
          "id": 4609,
          "name": "Abay Ave",
          "latitude": 43.2389,
          "longitude": 76.8897,
          "radius": 150
        }
      ],
      "rules": {
        "require_geo": true,
        "require_photo": false,
        "block_out_of_zone": false,
        "allow_without_shift": true,
        "early_clock_in_minutes": 30,
        "allow_web_clock": false
      },
      "server_time": "2026-11-03T09:05:00+05:00"
    }
  }
}

POSTStart a break

POST /schedule/clock/break-start

Starts a break in the open attendance. Errors: not_in, break_open.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_clock_break_start

Request body

uuid uuid
Client UUID of the clock-in, for safe retries.
attendance_uuid uuid
Attendance to close (clock-out); default — the open one.
location_id integer
Location; default — from the shift or geolocation.
shift_uuid uuid
Shift being worked.
qr string
Scanned QR content (link /clock/{token} or the token). Maximum length 500.
lat number
lng number
photo string
Photo path from schedule.media.upload.
recorded_at date-time
Device time for offline sync.
note string
Maximum length 500.

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/schedule/clock/break-start' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "location_id": 12,
    "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/clock/break-start', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "location_id": 12,
      "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/clock/break-start",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","attendance_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","location_id":12,"shift_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"},
)
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', 'schedule/clock/break-start', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'attendance_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'location_id' => 12,
        'shift_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_clock_break_start",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "location_id": 12,
            "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "attendance": {
      "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
      "date": "2026-11-03",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "clock_in_at": "2026-11-03T09:04:00+05:00",
      "clock_out_at": null,
      "breaks": [
        {
          "start": "13:00",
          "end": "13:30"
        }
      ],
      "status": "open",
      "late_minutes": 4,
      "early_minutes": 0,
      "worked_minutes": 0,
      "approved_minutes": null,
      "flags": [
        "late"
      ]
    },
    "clock": {
      "on_shift": true,
      "on_break": false,
      "attendance": {
        "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
        "date": "2026-11-03",
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "clock_in_at": "2026-11-03T09:04:00+05:00",
        "clock_out_at": null,
        "breaks": [
          {
            "start": "13:00",
            "end": "13:30"
          }
        ],
        "status": "open",
        "late_minutes": 4,
        "early_minutes": 0,
        "worked_minutes": 0,
        "approved_minutes": null,
        "flags": [
          "late"
        ]
      },
      "current_shift": {
        "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
        "date": "2026-11-04",
        "start": "09:00",
        "end": "18:00",
        "starts_at": "2026-11-04T09:00:00+05:00",
        "ends_at": "2026-11-04T18:00:00+05:00",
        "break_minutes": 60,
        "hours": 8,
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "position_id": 12,
        "position": "Barista",
        "note": null,
        "open": false
      },
      "next_shift": null,
      "sites": [
        {
          "id": 4609,
          "name": "Abay Ave",
          "latitude": 43.2389,
          "longitude": 76.8897,
          "radius": 150
        }
      ],
      "rules": {
        "require_geo": true,
        "require_photo": false,
        "block_out_of_zone": false,
        "allow_without_shift": true,
        "early_clock_in_minutes": 30,
        "allow_web_clock": false
      },
      "server_time": "2026-11-03T09:05:00+05:00"
    }
  }
}

POSTEnd a break

POST /schedule/clock/break-end

Ends the current break. Errors: not_in, no_break.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_clock_break_end

Request body

uuid uuid
Client UUID of the clock-in, for safe retries.
attendance_uuid uuid
Attendance to close (clock-out); default — the open one.
location_id integer
Location; default — from the shift or geolocation.
shift_uuid uuid
Shift being worked.
qr string
Scanned QR content (link /clock/{token} or the token). Maximum length 500.
lat number
lng number
photo string
Photo path from schedule.media.upload.
recorded_at date-time
Device time for offline sync.
note string
Maximum length 500.

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/schedule/clock/break-end' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "location_id": 12,
    "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/clock/break-end', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "location_id": 12,
      "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/clock/break-end",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","attendance_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","location_id":12,"shift_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"},
)
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', 'schedule/clock/break-end', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'attendance_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'location_id' => 12,
        'shift_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_clock_break_end",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "attendance_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "location_id": 12,
            "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "attendance": {
      "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
      "date": "2026-11-03",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "clock_in_at": "2026-11-03T09:04:00+05:00",
      "clock_out_at": null,
      "breaks": [
        {
          "start": "13:00",
          "end": "13:30"
        }
      ],
      "status": "open",
      "late_minutes": 4,
      "early_minutes": 0,
      "worked_minutes": 0,
      "approved_minutes": null,
      "flags": [
        "late"
      ]
    },
    "clock": {
      "on_shift": true,
      "on_break": false,
      "attendance": {
        "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
        "date": "2026-11-03",
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "clock_in_at": "2026-11-03T09:04:00+05:00",
        "clock_out_at": null,
        "breaks": [
          {
            "start": "13:00",
            "end": "13:30"
          }
        ],
        "status": "open",
        "late_minutes": 4,
        "early_minutes": 0,
        "worked_minutes": 0,
        "approved_minutes": null,
        "flags": [
          "late"
        ]
      },
      "current_shift": {
        "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
        "date": "2026-11-04",
        "start": "09:00",
        "end": "18:00",
        "starts_at": "2026-11-04T09:00:00+05:00",
        "ends_at": "2026-11-04T18:00:00+05:00",
        "break_minutes": 60,
        "hours": 8,
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "position_id": 12,
        "position": "Barista",
        "note": null,
        "open": false
      },
      "next_shift": null,
      "sites": [
        {
          "id": 4609,
          "name": "Abay Ave",
          "latitude": 43.2389,
          "longitude": 76.8897,
          "radius": 150
        }
      ],
      "rules": {
        "require_geo": true,
        "require_photo": false,
        "block_out_of_zone": false,
        "allow_without_shift": true,
        "early_clock_in_minutes": 30,
        "allow_web_clock": false
      },
      "server_time": "2026-11-03T09:05:00+05:00"
    }
  }
}

POSTUpload a clock photo or request file

POST /schedule/media

kind=clock — selfie for clock-in/out (image up to 8 MB); kind=absence — document for an absence request (image or PDF up to 10 MB). Returns the path to pass as photo / file.

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

Request body

file file Required
File (multipart; in MCP — object with name and content_base64).
kind string
Default clock.
clockabsence

Responses

StatusDescription
201Created
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/schedule/media' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -F 'kind=clock' \
  -F '[email protected]'
const form = new FormData();
form.append('kind', "clock");
form.append('file', fileInput.files[0]);

const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/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/schedule/media",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    data={"kind":"clock"},
    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', 'schedule/media', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'multipart' => [
        ['name' => 'kind', 'contents' => 'clock'],
        ['name' => 'file', 'contents' => fopen('photo.jpg', 'r')],
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_media_upload",
        "arguments": {
            "kind": "clock"
        }
    }
}
Response 201
{
  "data": {
    "path": "staff/761/clock/2026/11/0f1e.jpg"
  }
}

GETGet who is on shift

GET /schedule/attendance

Attendance board by location: every published shift of the day with status (upcoming, late, working, break, done, absent, missed) and clock-ins without a shift. Phones only with users.contacts. Default — today at each location.

Token scoperead
Permission schedule.attendance
MCP toolschedule_attendance · read-only

Query parameters

location_ids array<integer>
Only these locations (default — all with schedule.attendance).
date date
Day, default — today.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.attendance
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/schedule/attendance?date=2026-11-01' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/attendance?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/schedule/attendance",
    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', 'schedule/attendance', [
    '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": "schedule_attendance",
        "arguments": {
            "date": "2026-11-01"
        }
    }
}
Response 200
{
  "data": [
    {
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "date": "2026-11-03",
      "now": "09:15",
      "counts": {
        "working": 3,
        "late": 1,
        "upcoming": 2,
        "total": 6
      },
      "people": [
        {
          "user": {
            "id": 311,
            "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
            "name": "Aigerim Sadykova"
          },
          "position": "Barista",
          "phone": null,
          "status": "working",
          "shift": {
            "id": 70211,
            "start": "09:00",
            "end": "18:00"
          },
          "attendance": {
            "id": 55120,
            "in": "09:04",
            "out": null,
            "late_minutes": 4,
            "worked_minutes": 11,
            "method": "app",
            "flags": [
              "late"
            ]
          },
          "late_now_minutes": null,
          "absence": null
        }
      ]
    }
  ]
}

GETList attendance records

GET /schedule/attendance/records

Clock-in/out journal at locations with schedule.attendance, newest first (max 1000 per period). flag: late, early, out_of_zone, manual, offline, auto_closed, unapproved. Default period — last 7 days.

Token scoperead
Permission schedule.attendance
MCP toolschedule_attendance_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.
date_from date
date_to date
location_ids array<integer>
user_id integer
flag string
Maximum length 30.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.attendance
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/schedule/attendance/records?date_from=2026-11-01' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/attendance/records?date_from=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/schedule/attendance/records",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"date_from":"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', 'schedule/attendance/records', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'date_from' => '2026-11-01',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_attendance_list",
        "arguments": {
            "date_from": "2026-11-01"
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 55120,
      "uuid": "5f6a7b8c-9d0e-4f1a-8b2c-3d4e5f6a7b8c",
      "user": {
        "id": 311,
        "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
        "name": "Aigerim Sadykova"
      },
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "date": "2026-11-03",
      "clock_in": "2026-11-03 09:04",
      "clock_out": "2026-11-03 18:02",
      "shift": {
        "id": 70211,
        "start": "09:00",
        "end": "18:00",
        "minutes": 480
      },
      "break_minutes": 30,
      "worked_minutes": 508,
      "approved_minutes": null,
      "approved": false,
      "status": "closed",
      "in_method": "app",
      "out_method": "app",
      "in_distance": 12,
      "out_distance": 20,
      "in_photo": null,
      "out_photo": null,
      "late_minutes": 4,
      "early_minutes": 0,
      "flags": [
        "late"
      ],
      "note": null,
      "edits": 0
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "has_more": false
  }
}

POSTAdd an attendance record

POST /schedule/attendance/records

Manual attendance for an employee (forgot to clock in) with a reason; written to the edit history. Times are local wall time YYYY-MM-DD HH:MM.

Token scopewrite
Permission schedule.attendance
MCP toolschedule_attendance_create

Request body

user_id integer Required
location_id integer Required
shift_id integer
clock_in string Required
YYYY-MM-DD HH:MM, local.
clock_out string | null
YYYY-MM-DD HH:MM, local.
break_minutes integer
Minimum 0.
approved_minutes integer | null
Minimum 0.
note string
Maximum length 500.
reason string Required
Maximum length 500.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.attendance, 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/schedule/attendance/records' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 42,
    "location_id": 12,
    "shift_id": 42,
    "clock_in": "string",
    "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/attendance/records', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "user_id": 42,
      "location_id": 12,
      "shift_id": 42,
      "clock_in": "string",
      "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/schedule/attendance/records",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"user_id":42,"location_id":12,"shift_id":42,"clock_in":"string","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', 'schedule/attendance/records', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'user_id' => 42,
        'location_id' => 12,
        'shift_id' => 42,
        'clock_in' => 'string',
        '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": "schedule_attendance_create",
        "arguments": {
            "user_id": 42,
            "location_id": 12,
            "shift_id": 42,
            "clock_in": "string",
            "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 201
{
  "data": {
    "id": 55121
  }
}

PATCHCorrect an attendance record

PATCH /schedule/attendance/records/{id}

Corrects times of an attendance record with a reason (kept in history). Rejected in a closed month.

Token scopewrite
Permission schedule.attendance
MCP toolschedule_attendance_update

Path parameters

id integer Required
Attendance id.

Request body

clock_in string
YYYY-MM-DD HH:MM, local.
clock_out string | null
break_minutes integer
Minimum 0.
approved_minutes integer | null
Minimum 0.
note string
Maximum length 500.
reason string Required
Maximum length 500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.attendance, 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/schedule/attendance/records/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "clock_in": "string",
    "clock_out": "string",
    "break_minutes": 42,
    "approved_minutes": 42,
    "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/attendance/records/42', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "clock_in": "string",
      "clock_out": "string",
      "break_minutes": 42,
      "approved_minutes": 42,
      "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.patch(
    "https://app.jecheck.io/api/ext/v1/schedule/attendance/records/42",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"clock_in":"string","clock_out":"string","break_minutes":42,"approved_minutes":42,"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('PATCH', 'schedule/attendance/records/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'clock_in' => 'string',
        'clock_out' => 'string',
        'break_minutes' => 42,
        'approved_minutes' => 42,
        '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": "schedule_attendance_update",
        "arguments": {
            "id": 42,
            "clock_in": "string",
            "clock_out": "string",
            "break_minutes": 42,
            "approved_minutes": 42,
            "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "id": 55120,
    "updated": true
  }
}

DELDelete an attendance record

DELETE /schedule/attendance/records/{id}
Token scopewrite
Permission schedule.attendance
MCP toolschedule_attendance_delete · destructive

Path parameters

id integer Required
Attendance id.

Query parameters

reason string Required
Why the record is deleted. Maximum length 500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.attendance, 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 DELETE 'https://app.jecheck.io/api/ext/v1/schedule/attendance/records/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/attendance/records/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/schedule/attendance/records/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', 'schedule/attendance/records/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_attendance_delete",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "deleted": true
  }
}

POSTApprove attendance records

POST /schedule/attendance/records/approve

Approves worked minutes of closed records (they go to the timesheet as approved). minutes overrides the value when approving one record.

Token scopewrite
Permission schedule.attendance
MCP toolschedule_attendance_approve

Request body

ids array<integer> Required
Items 1–500.
minutes integer
Minimum 0.

Responses

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

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/attendance/records/approve",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"ids":[12,14],"minutes":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', 'schedule/attendance/records/approve', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'ids' => [
            12,
            14,
        ],
        'minutes' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_attendance_approve",
        "arguments": {
            "ids": [
                12,
                14
            ],
            "minutes": 42
        }
    }
}
Response 200
{
  "data": {
    "approved": 12
  }
}

GETGet the timesheet

GET /schedule/timesheet

Planned and payable hours by employee and day (minutes), night/holiday/overtime, lateness, absences and day codes. A month, or date_from/date_to up to 62 days. closed — the month is locked.

Token scoperead
Permission schedule.timesheet
MCP toolschedule_timesheet · read-only

Query parameters

month string
Month YYYY-MM, default — current.
date_from date
date_to date
location_ids array<integer>

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.timesheet
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/schedule/timesheet?month=string' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/timesheet?month=string', {
  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/schedule/timesheet",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"month":"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('GET', 'schedule/timesheet', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'month' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_timesheet",
        "arguments": {
            "month": "string"
        }
    }
}
Response 200
{
  "data": {
    "date_from": "2026-11-01",
    "date_to": "2026-11-30",
    "closed": false,
    "holidays": [],
    "rows": [
      {
        "user": {
          "id": 311,
          "name": "Aigerim Sadykova",
          "position": "Barista",
          "personnel_no": "A-17",
          "location_ids": [
            4609
          ]
        },
        "totals": {
          "planned": 10080,
          "worked": 3200,
          "payable": 3150,
          "night": 0,
          "holiday": 0,
          "overtime": 0,
          "late_count": 1,
          "late": 4,
          "days": 7,
          "shifts": 7,
          "no_show": 0,
          "absences": [],
          "paid_absence_days": 0
        },
        "days": [
          {
            "date": "2026-11-03",
            "code": "Я",
            "planned": 480,
            "payable": 450,
            "night": 0,
            "holiday": 0,
            "overtime": 0,
            "late": 4,
            "absence": null,
            "flags": [
              "late"
            ],
            "open": false
          }
        ]
      }
    ]
  }
}

GETGet the payroll

GET /schedule/payroll

Payroll for a month by employee: hours, amounts (base, night, holiday, overtime, KPI, adjustments, deductions, payable) and status (draft, approved, paid). Sensitive: requires pay.payroll.view (locations of the employees); each call is written to the access log. rate / pay_type are omitted for employees of locations without pay.rates.view.

Token scoperead
Permission pay.payroll.view
Plan featurepayroll
MCP toolschedule_payroll · read-only

Query parameters

month string
Month YYYY-MM, default — current.
location_ids array<integer>

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks pay.payroll.view, or the plan has no payroll
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/schedule/payroll?month=string' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/payroll?month=string', {
  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/schedule/payroll",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"month":"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('GET', 'schedule/payroll', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'month' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_payroll",
        "arguments": {
            "month": "string"
        }
    }
}
Response 200
{
  "data": {
    "month": "2026-10",
    "currency": "KZT",
    "status": "approved",
    "approved_at": "2026-11-02T10:00:00+05:00",
    "paid_at": null,
    "lines": [
      {
        "user": {
          "id": 311,
          "name": "Aigerim Sadykova",
          "position": "Barista",
          "personnel_no": "A-17",
          "location_ids": [
            4609
          ]
        },
        "pay_type": "hourly",
        "rate": 1500,
        "hours": {
          "planned": 168,
          "payable": 170.5,
          "night": 0,
          "holiday": 8,
          "overtime": 2.5
        },
        "days": 21,
        "shifts": 21,
        "late_count": 2,
        "no_show": 0,
        "paid_absence_days": 0,
        "absences": [],
        "kpi_score": 96.5,
        "amounts": {
          "base": 255750,
          "night": 0,
          "holiday": 12000,
          "overtime": 5625,
          "absence": 0,
          "kpi": 20000,
          "bonus": 0,
          "compensation": 0,
          "accrued": 293375,
          "penalty": 0,
          "deduction": 0,
          "advance": 50000,
          "percent": [
            {
              "name": "IIT",
              "percent": 10,
              "amount": 29337.5
            }
          ],
          "deducted": 79337.5,
          "payable": 214037.5
        },
        "adjustments": [
          {
            "id": 81,
            "kind": "advance",
            "amount": 50000,
            "comment": "Mid-month advance"
          }
        ],
        "norm_hours": 168
      }
    ],
    "totals": {
      "base": 255750,
      "night": 0,
      "holiday": 12000,
      "overtime": 5625,
      "absence": 0,
      "kpi": 20000,
      "bonus": 0,
      "compensation": 0,
      "accrued": 293375,
      "penalty": 0,
      "deduction": 0,
      "advance": 50000,
      "deducted": 79337.5,
      "payable": 214037.5,
      "hours": 170.5,
      "people": 1
    }
  }
}

POSTApprove the payroll

POST /schedule/payroll/approve

Freezes the payroll of a month and closes the month for changes of shifts, attendance and absences. Requires pay.payroll.approve at all locations.

Token scopewrite
Permission pay.payroll.approve
Plan featurepayroll
MCP toolschedule_payroll_approve

Request body

month string Required
YYYY-MM.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks pay.payroll.approve, is read-only, or the plan has no payroll
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/schedule/payroll/approve' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "month": "string"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/payroll/approve', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "month": "string"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/payroll/approve",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"month":"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', 'schedule/payroll/approve', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'month' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_payroll_approve",
        "arguments": {
            "month": "string"
        }
    }
}
Response 200
{
  "data": {
    "month": "2026-10",
    "status": "approved"
  }
}

POSTReopen the payroll

POST /schedule/payroll/reopen

Reopens an approved month for corrections.

Token scopewrite
Permission pay.payroll.approve
Plan featurepayroll
MCP toolschedule_payroll_reopen

Request body

month string Required
YYYY-MM.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks pay.payroll.approve, is read-only, or the plan has no payroll
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/schedule/payroll/reopen' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "month": "string"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/payroll/reopen', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "month": "string"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/payroll/reopen",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"month":"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', 'schedule/payroll/reopen', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'month' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_payroll_reopen",
        "arguments": {
            "month": "string"
        }
    }
}
Response 200
{
  "data": {
    "month": "2026-10",
    "status": "draft"
  }
}

POSTMark the payroll as paid

POST /schedule/payroll/paid
Token scopewrite
Permission pay.payroll.approve
Plan featurepayroll
MCP toolschedule_payroll_paid

Request body

month string Required
YYYY-MM.
paid boolean
false — undo, default true.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks pay.payroll.approve, is read-only, or the plan has no payroll
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/schedule/payroll/paid' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "month": "string",
    "paid": true
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/payroll/paid', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "month": "string",
      "paid": true
  }),
});
const { data } = await res.json();
import os, requests

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

$res = $client->request('POST', 'schedule/payroll/paid', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'month' => 'string',
        'paid' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_payroll_paid",
        "arguments": {
            "month": "string",
            "paid": true
        }
    }
}
Response 200
{
  "data": {
    "month": "2026-10",
    "paid": true
  }
}

POSTAdd a payroll adjustment

POST /schedule/payroll/adjustments

Bonus, compensation, penalty, deduction or advance for an employee in an open month.

Token scopewrite
Permission pay.payroll.approve
Plan featurepayroll
MCP toolschedule_payroll_adjustments_create

Request body

user_id integer Required
month string Required
YYYY-MM.
kind string Required
bonuscompensationpenaltydeductionadvance
amount number Required
Minimum 0.01.
comment string
Maximum length 500.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks pay.payroll.approve, is read-only, or the plan has no payroll
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/schedule/payroll/adjustments' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 42,
    "month": "string",
    "kind": "bonus",
    "amount": 4.5
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/payroll/adjustments', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "user_id": 42,
      "month": "string",
      "kind": "bonus",
      "amount": 4.5
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/payroll/adjustments",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"user_id":42,"month":"string","kind":"bonus","amount":4.5},
)
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', 'schedule/payroll/adjustments', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'user_id' => 42,
        'month' => 'string',
        'kind' => 'bonus',
        'amount' => 4.5,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_payroll_adjustments_create",
        "arguments": {
            "user_id": 42,
            "month": "string",
            "kind": "bonus",
            "amount": 4.5
        }
    }
}
Response 201
{
  "data": {
    "id": 81,
    "user_id": 311,
    "month": "2026-11",
    "kind": "advance",
    "amount": 50000,
    "comment": "Mid-month advance"
  }
}

DELDelete a payroll adjustment

DELETE /schedule/payroll/adjustments/{id}
Token scopewrite
Permission pay.payroll.approve
Plan featurepayroll
MCP toolschedule_payroll_adjustments_delete · destructive

Path parameters

id integer Required
Adjustment id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks pay.payroll.approve, is read-only, or the plan has no payroll
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 DELETE 'https://app.jecheck.io/api/ext/v1/schedule/payroll/adjustments/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/payroll/adjustments/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/schedule/payroll/adjustments/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', 'schedule/payroll/adjustments/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_payroll_adjustments_delete",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "deleted": true
  }
}

GETGet the KPI scorecard

GET /schedule/kpi

KPI metrics and each employee's values, achievement and total score for a month. The KPI bonus base is included only with pay.rates.view.

Token scoperead
Permission schedule.kpi
Plan featurepayroll
MCP toolschedule_kpi · read-only

Query parameters

month string
Month YYYY-MM, default — current.
location_ids array<integer>

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.kpi, or the plan has no payroll
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/schedule/kpi?month=string' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/kpi?month=string', {
  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/schedule/kpi",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"month":"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('GET', 'schedule/kpi', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'month' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_kpi",
        "arguments": {
            "month": "string"
        }
    }
}
Response 200
{
  "data": {
    "month": "2026-11",
    "closed": false,
    "threshold": 70,
    "cap": 120,
    "metrics": [
      {
        "id": 4,
        "name": "Punctuality",
        "description": null,
        "unit": "%",
        "source": "punctuality",
        "direction": "up",
        "target": 95,
        "weight": 30,
        "position_ids": [],
        "active": true
      }
    ],
    "people": [
      {
        "user": {
          "id": 311,
          "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "name": "Aigerim Sadykova"
        },
        "position": "Barista",
        "kpi_bonus": 20000,
        "kpi_bonus_type": "fixed",
        "score": 96.5,
        "metrics": [
          {
            "metric_id": 4,
            "value": 98,
            "auto": 98,
            "manual": false,
            "comment": null,
            "achievement": 103.2
          }
        ]
      }
    ]
  }
}

POSTSet a KPI value

POST /schedule/kpi/values

Sets (or overrides an automatic) value of a metric for an employee and month. value: null clears the manual value.

Token scopewrite
Permission schedule.kpi
Plan featurepayroll
MCP toolschedule_kpi_values_set

Request body

metric_id integer Required
user_id integer Required
month string Required
YYYY-MM.
value number | null
comment string
Maximum length 500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.kpi, is read-only, or the plan has no payroll
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/schedule/kpi/values' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "metric_id": 42,
    "user_id": 42,
    "month": "string",
    "value": 4.5
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/kpi/values', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "metric_id": 42,
      "user_id": 42,
      "month": "string",
      "value": 4.5
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/kpi/values",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"metric_id":42,"user_id":42,"month":"string","value":4.5},
)
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', 'schedule/kpi/values', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'metric_id' => 42,
        'user_id' => 42,
        'month' => 'string',
        'value' => 4.5,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_kpi_values_set",
        "arguments": {
            "metric_id": 42,
            "user_id": 42,
            "month": "string",
            "value": 4.5
        }
    }
}
Response 200
{
  "data": {
    "saved": true
  }
}

GETList absence and swap requests

GET /schedule/requests

Requests of employees of locations with schedule.requests: pending (default) or history (decided in the last 90 days).

Token scoperead
Permission schedule.requests
MCP toolschedule_requests_list · read-only

Query parameters

status string
Default pending.
pendinghistory

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.requests
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/schedule/requests?status=pending' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/requests?status=pending', {
  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/schedule/requests",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"status":"pending"},
)
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', 'schedule/requests', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'status' => 'pending',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_requests_list",
        "arguments": {
            "status": "pending"
        }
    }
}
Response 200
{
  "data": {
    "absences": [
      {
        "id": 912,
        "uuid": "8c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
        "user": {
          "id": 311,
          "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "name": "Aigerim Sadykova"
        },
        "type": "vacation",
        "date_from": "2026-12-01",
        "date_to": "2026-12-14",
        "days": 14,
        "status": "pending",
        "paid": true,
        "comment": "Family trip",
        "file": null,
        "decided_by": null,
        "decided_at": null,
        "decision_note": null,
        "created_at": "2026-11-03T10:00:00+05:00"
      }
    ],
    "swaps": [
      {
        "id": 340,
        "uuid": "9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a",
        "type": "give",
        "status": "pending_peer",
        "note": "Doctor appointment",
        "from_user": {
          "id": 311,
          "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "name": "Aigerim Sadykova"
        },
        "to_user": null,
        "shift": {
          "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
          "date": "2026-11-04",
          "start": "09:00",
          "end": "18:00",
          "location": {
            "id": 4609,
            "name": "Abay Ave"
          }
        },
        "to_shift": null,
        "decision_note": null,
        "created_at": "2026-11-03T10:00:00+05:00"
      }
    ]
  }
}

GETList my requests

GET /schedule/my/requests

Absences of the acting user (last 60 days and future) and swap requests — own and incoming (can_answer).

Token scoperead
Permission Any employee; results follow the token's visibility
MCP toolschedule_my_requests · 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/schedule/my/requests' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/my/requests', {
  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/schedule/my/requests",
    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', 'schedule/my/requests', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_my_requests",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "absences": [
      {
        "id": 912,
        "uuid": "8c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
        "user": {
          "id": 311,
          "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "name": "Aigerim Sadykova"
        },
        "type": "vacation",
        "date_from": "2026-12-01",
        "date_to": "2026-12-14",
        "days": 14,
        "status": "pending",
        "paid": true,
        "comment": "Family trip",
        "file": null,
        "decided_by": null,
        "decided_at": null,
        "decision_note": null,
        "created_at": "2026-11-03T10:00:00+05:00"
      }
    ],
    "swaps": [
      {
        "id": 340,
        "uuid": "9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a",
        "type": "give",
        "status": "pending_peer",
        "note": "Doctor appointment",
        "from_user": {
          "id": 311,
          "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
          "name": "Aigerim Sadykova"
        },
        "to_user": null,
        "shift": {
          "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
          "date": "2026-11-04",
          "start": "09:00",
          "end": "18:00",
          "location": {
            "id": 4609,
            "name": "Abay Ave"
          }
        },
        "to_shift": null,
        "decision_note": null,
        "created_at": "2026-11-03T10:00:00+05:00",
        "mine": true,
        "can_answer": false
      }
    ]
  }
}

POSTCreate an absence

POST /schedule/absences

Without user_id (or with the acting user) — a request of the acting user, goes to the manager. For another employee — recorded by the manager and approved right away (schedule.requests at the employee's location; release_shifts turns their shifts into open ones).

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_absences_create

Request body

user_id integer
Employee, default — the acting user.
type string Required
vacationsickday_offunpaidbusiness_tripstudymaternitytruancyother
date_from date Required
date_to date
Default — same day.
comment string
Maximum length 1000.
paid boolean
Manager only: override whether the absence is paid.
file string
Document path from schedule.media.upload (kind=absence).
release_shifts boolean

Responses

StatusDescription
201Created
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/schedule/absences' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 42,
    "type": "vacation",
    "date_from": "2026-11-01",
    "date_to": "2026-11-30"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/absences', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "user_id": 42,
      "type": "vacation",
      "date_from": "2026-11-01",
      "date_to": "2026-11-30"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/absences",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"user_id":42,"type":"vacation","date_from":"2026-11-01","date_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', 'schedule/absences', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'user_id' => 42,
        'type' => 'vacation',
        'date_from' => '2026-11-01',
        'date_to' => '2026-11-30',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_absences_create",
        "arguments": {
            "user_id": 42,
            "type": "vacation",
            "date_from": "2026-11-01",
            "date_to": "2026-11-30"
        }
    }
}
Response 201
{
  "data": {
    "id": 912,
    "uuid": "8c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "type": "vacation",
    "date_from": "2026-12-01",
    "date_to": "2026-12-14",
    "days": 14,
    "status": "pending",
    "paid": true,
    "comment": "Family trip",
    "file": null,
    "decided_by": null,
    "decided_at": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

POSTCancel an absence

POST /schedule/absences/{uuid}/cancel

The employee cancels their pending request; a manager with schedule.requests can cancel any absence of their employees.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_absences_cancel

Path parameters

uuid uuid Required
Absence UUID.

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/schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel', {
  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/schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel",
    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', 'schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_absences_cancel",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "id": 912,
    "uuid": "8c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "type": "vacation",
    "date_from": "2026-12-01",
    "date_to": "2026-12-14",
    "days": 14,
    "status": "pending",
    "paid": true,
    "comment": "Family trip",
    "file": null,
    "decided_by": null,
    "decided_at": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

POSTApprove or reject an absence request

POST /schedule/absences/{uuid}/decide

Manager decision; the employee is notified. release_shifts makes their shifts in these days open.

Token scopewrite
Permission schedule.requests
MCP toolschedule_absences_decide

Path parameters

uuid uuid Required
Absence UUID.

Request body

approve boolean Required
note string
Maximum length 500.
release_shifts boolean
paid boolean

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.requests, 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/schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "approve": true,
    "note": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
    "release_shifts": true,
    "paid": true
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "approve": true,
      "note": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
      "release_shifts": true,
      "paid": true
  }),
});
const { data } = await res.json();
import os, requests

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

$res = $client->request('POST', 'schedule/absences/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'approve' => true,
        'note' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
        'release_shifts' => true,
        'paid' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_absences_decide",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "approve": true,
            "note": "Seal on fridge #2 is torn, temperature rises to +9 °C.",
            "release_shifts": true,
            "paid": true
        }
    }
}
Response 200
{
  "data": {
    "id": 912,
    "uuid": "8c9d0e1f-2a3b-4c4d-9e5f-6a7b8c9d0e1f",
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "type": "vacation",
    "date_from": "2026-12-01",
    "date_to": "2026-12-14",
    "days": 14,
    "status": "pending",
    "paid": true,
    "comment": "Family trip",
    "file": null,
    "decided_by": null,
    "decided_at": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

POSTRequest a shift swap

POST /schedule/swaps

give — give away own shift (to to_user_id or anyone eligible); swap — exchange with a colleague's shift to_shift_uuid. The colleague answers, then the manager decides.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_swaps_create

Request body

type string Required
giveswap
shift_uuid uuid Required
Own published shift.
to_shift_uuid uuid
Colleague's shift (for swap).
to_user_id integer
Colleague (for give).
note string
Maximum length 500.

Responses

StatusDescription
201Created
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/schedule/swaps' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "give",
    "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "to_shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    "to_user_id": 42
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/swaps', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "type": "give",
      "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "to_shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "to_user_id": 42
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/swaps",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"type":"give","shift_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","to_shift_uuid":"3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b","to_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('POST', 'schedule/swaps', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'type' => 'give',
        'shift_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'to_shift_uuid' => '3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b',
        'to_user_id' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_swaps_create",
        "arguments": {
            "type": "give",
            "shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "to_shift_uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "to_user_id": 42
        }
    }
}
Response 201
{
  "data": {
    "id": 340,
    "uuid": "9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a",
    "type": "give",
    "status": "pending_peer",
    "note": "Doctor appointment",
    "from_user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "to_user": null,
    "shift": {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      }
    },
    "to_shift": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

POSTAnswer a swap request

POST /schedule/swaps/{uuid}/answer

The colleague accepts or declines a swap / give-away addressed to them.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_swaps_answer

Path parameters

uuid uuid Required
Swap request UUID.

Request body

accept boolean Required

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/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/answer' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "accept": true
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/answer', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "accept": true
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/answer",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"accept":True},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/answer', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'accept' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_swaps_answer",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "accept": true
        }
    }
}
Response 200
{
  "data": {
    "id": 340,
    "uuid": "9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a",
    "type": "give",
    "status": "pending_peer",
    "note": "Doctor appointment",
    "from_user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "to_user": null,
    "shift": {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      }
    },
    "to_shift": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

POSTCancel a swap request

POST /schedule/swaps/{uuid}/cancel
Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_swaps_cancel

Path parameters

uuid uuid Required
Swap request UUID.

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/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel', {
  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/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel",
    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', 'schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/cancel', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_swaps_cancel",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "id": 340,
    "uuid": "9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a",
    "type": "give",
    "status": "pending_peer",
    "note": "Doctor appointment",
    "from_user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "to_user": null,
    "shift": {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      }
    },
    "to_shift": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

POSTApprove or reject a swap request

POST /schedule/swaps/{uuid}/decide

Manager decision (schedule.requests at the shift location). Approval reassigns the shifts and notifies both employees.

Token scopewrite
Permission schedule.requests
MCP toolschedule_swaps_decide

Path parameters

uuid uuid Required
Swap request UUID.

Request body

approve boolean Required
note string
Maximum length 500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks schedule.requests, 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/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "approve": true,
    "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "approve": true,
      "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/schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"approve":True,"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', 'schedule/swaps/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/decide', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'approve' => true,
        '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": "schedule_swaps_decide",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "approve": true,
            "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "id": 340,
    "uuid": "9d0e1f2a-3b4c-4d5e-8f6a-7b8c9d0e1f2a",
    "type": "give",
    "status": "pending_peer",
    "note": "Doctor appointment",
    "from_user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "to_user": null,
    "shift": {
      "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
      "date": "2026-11-04",
      "start": "09:00",
      "end": "18:00",
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      }
    },
    "to_shift": null,
    "decision_note": null,
    "created_at": "2026-11-03T10:00:00+05:00"
  }
}

GETGet availability

GET /schedule/availability

Weekly / dated availability preferences of the acting user, or of an employee the token manages (schedule.edit).

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

Query parameters

user_id integer
Employee, default — the acting user.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Plan without API access
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/schedule/availability?user_id=42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/availability?user_id=42', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/schedule/availability",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"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('GET', 'schedule/availability', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'user_id' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_availability_get",
        "arguments": {
            "user_id": 42
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 15,
      "weekday": 6,
      "date": null,
      "start_time": null,
      "end_time": null,
      "kind": "unavailable",
      "note": "University"
    }
  ]
}

PUTReplace availability

PUT /schedule/availability

Replaces all availability items of the acting user (or a managed employee). Each item: weekday (1–7) or date, optional start_time/end_time HH:MM, kind unavailable | preferred, note.

Token scopewrite
Permission Any employee; results follow the token's visibility
MCP toolschedule_availability_set

Request body

user_id integer
Employee, default — the acting user.
items array<object> Required
Maximum items 100.
weekday integer
Value 1–7.
date date
start_time string
end_time string
kind string
unavailablepreferred
note string
Maximum length 255.

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 PUT 'https://app.jecheck.io/api/ext/v1/schedule/availability' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 42,
    "items": [
        {
            "weekday": 42,
            "date": "2026-11-01",
            "start_time": "string"
        }
    ]
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/schedule/availability', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "user_id": 42,
      "items": [
          {
              "weekday": 42,
              "date": "2026-11-01",
              "start_time": "string"
          }
      ]
  }),
});
const { data } = await res.json();
import os, requests

res = requests.put(
    "https://app.jecheck.io/api/ext/v1/schedule/availability",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"user_id":42,"items":[{"weekday":42,"date":"2026-11-01","start_time":"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', 'schedule/availability', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'user_id' => 42,
        'items' => [
            [
                'weekday' => 42,
                'date' => '2026-11-01',
                'start_time' => 'string',
            ],
        ],
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "schedule_availability_set",
        "arguments": {
            "user_id": 42,
            "items": [
                {
                    "weekday": 42,
                    "date": "2026-11-01",
                    "start_time": "string"
                }
            ]
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 15,
      "weekday": 6,
      "date": null,
      "start_time": null,
      "end_time": null,
      "kind": "unavailable",
      "note": "University"
    }
  ]
}