For AI agents: markdown of this page — /docs-content-en/workday/records.md documentation index — /llms.txt
Workday history
GET /v1/workday/records
Returns an employee's workday history: start and end times, seconds worked, break durations, and an offset calculated for each record from the current IANA time zone (TIME_ZONE) in the employee profile. Records are selected by the required employee identifier and a period; without a period the window covers the last 7 days.
This endpoint requires both timeman and one of user_brief, user_basic, or user. An empty page is returned without an additional profile lookup. If the current IANA profile zone cannot be retrieved or its historical rules cannot yield an offset for a non-empty page, the endpoint returns 502 BITRIX_UNAVAILABLE with no partial data. The API cannot detect a zone reassignment after record creation, so that case does not produce a 502.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ParameteruserId (query) |
Typenumber | Requiredyes | DescriptionEmployee identifier, an integer greater than zero. Your own identifier — GET /v1/users/me, the employee list — GET /v1/users |
Parameterfrom (query) |
Typestring | Requiredno | DescriptionStart of the period, ISO-8601 with an explicit offset or Z — for example 2026-06-01T00:00:00Z. Filters by the day start time. A date without a time returns 400. Encode the plus sign in the offset as %2B; otherwise the value arrives with a space instead of the sign and is not recognized |
Parameterto (query) |
Typestring | Requiredno | DescriptionEnd of the period in the same format. A from later than to returns 400 |
Parameterlimit (query) |
Typenumber | Requiredno | DescriptionRecords per page, from 1 to 50. Defaults to 50 |
Parameteroffset (query) |
Typenumber | Requiredno | DescriptionOffset from the start of the selection, a non-negative integer. Defaults to 0. Cannot be combined with page |
Parameterpage (query) |
Typenumber | Requiredno | DescriptionPage number starting from one, equivalent to offset with the value (page − 1) × limit. Cannot be combined with offset |
Parameterorder (query) |
Typestring | Requiredno | DescriptionOrder by the day start time: desc — newest first, asc — oldest first. Defaults to desc |
Both ends of the period are optional: if neither from nor to is passed, the selection is limited to the last 7 days from the moment of the request. A query parameter outside this list returns 400 INVALID_PARAMS with the name of the passed key and the list of accepted ones.
Examples
curl — personal key
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.com/v1/workday/records?userId=1&from=2020-01-01T00:00:00Z&limit=3&order=asc"
curl — another employee's records
curl -H "X-Api-Key: YOUR_API_KEY" \
"https://vibecode.bitrix24.com/v1/workday/records?userId=503&from=2020-01-01T00:00:00Z&limit=3&order=asc"
curl — OAuth application
curl -H "X-Api-Key: YOUR_APP_KEY" \
-H "Authorization: Bearer USER_SESSION_TOKEN" \
"https://vibecode.bitrix24.com/v1/workday/records?userId=1&from=2020-01-01T00:00:00Z&limit=3&order=asc"
JavaScript — personal key
const params = new URLSearchParams({
userId: '1',
from: '2020-01-01T00:00:00Z',
limit: '3',
order: 'asc',
})
const res = await fetch(`https://vibecode.bitrix24.com/v1/workday/records?${params}`, {
headers: { 'X-Api-Key': 'YOUR_API_KEY' },
})
const { data, meta } = await res.json()
for (const record of data) {
console.log(record.startTime, '— seconds worked:', record.duration)
}
console.log('More records available:', meta.hasMore)
JavaScript — OAuth application
const params = new URLSearchParams({
userId: '1',
from: '2020-01-01T00:00:00Z',
limit: '3',
order: 'asc',
})
const res = await fetch(`https://vibecode.bitrix24.com/v1/workday/records?${params}`, {
headers: {
'X-Api-Key': 'YOUR_APP_KEY',
'Authorization': 'Bearer USER_SESSION_TOKEN',
},
})
const { data, meta } = await res.json()
Response fields
| Field | Type | Description |
|---|---|---|
Fieldsuccess |
Typeboolean | Descriptiontrue on a successful response |
Fielddata |
Typearray | DescriptionArray of workday records. An empty array when no record falls within the period |
Fielddata[].id |
Typenumber | DescriptionWorkday record identifier |
Fielddata[].userId |
Typenumber | DescriptionIdentifier of the employee the record belongs to |
Fielddata[].startTime |
Typestring | DescriptionDay start time, ISO-8601. The string keeps the account offset and is not rewritten to the employee's time zone |
Fielddata[].endTime |
Typestring | null | DescriptionDay end time in the same format. On a day that is still open the field is absent or arrives as null |
Fielddata[].duration |
Typenumber | null | DescriptionTime worked during the day, in seconds. On a day that is still open the field is absent or arrives as null |
Fielddata[].breakLength |
Typenumber | null | DescriptionTotal break duration for the day, in seconds. On a day that is still open the field is absent or arrives as null |
Fielddata[].state.status |
Typestring | DescriptionRecord state in lowercase. On a finished day it is closed |
Fielddata[].state.recommendedCloseTime |
Typestring | null | DescriptionRecommended day close time. For the records checked, this value is null |
Fielddata[].isApproved |
Typeboolean | null | DescriptionWhether the workday record is approved. On a day that is still open the field is absent or arrives as null |
Fielddata[].tzOffset |
Typenumber | DescriptionRequired offset in seconds east of UTC, calculated for the startTime instant using the historical rules of the current IANA time zone (TIME_ZONE) in the employee profile. It is not proof that this zone was assigned when the record was created |
Fieldmeta.hasMore |
Typeboolean | Descriptiontrue when the page is filled up to limit and more records follow |
Fieldmeta.total |
Typenumber | DescriptionNumber of records in the selection — the sum of offset and the page length. Arrives only when the page is shorter than limit |
Fieldmeta.from |
Typestring | DescriptionThe lower bound of the applied period. Present when that bound is set — either by the from passed in or by the substituted last-7-days window |
Fieldmeta.to |
Typestring | DescriptionThe upper bound of the applied period. Absent for a period with no upper bound (only from is passed) |
Response example
The first three records from oldest to newest, ?userId=1&from=2020-01-01T00:00:00Z&limit=3&order=asc:
{
"success": true,
"data": [
{
"id": 1,
"userId": 1,
"startTime": "2020-04-23T13:42:10+00:00",
"endTime": "2020-04-23T17:53:26+00:00",
"duration": 12999,
"breakLength": 2077,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 5,
"userId": 1,
"startTime": "2020-04-24T09:16:12+00:00",
"endTime": "2020-04-24T16:30:13+00:00",
"duration": 26041,
"breakLength": 0,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 7,
"userId": 1,
"startTime": "2020-04-27T09:04:11+00:00",
"endTime": "2020-04-27T18:00:00+00:00",
"duration": 32149,
"breakLength": 0,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
}
],
"meta": { "hasMore": true, "from": "2020-01-01T00:00:00Z" }
}
The last page of the same selection, ?userId=1&from=2020-01-01T00:00:00Z&offset=40&order=asc — fewer records than limit arrived, so total appears in meta:
{
"success": true,
"data": [
{
"id": 163,
"userId": 1,
"startTime": "2024-07-24T17:34:51+00:00",
"endTime": "2024-07-25T02:35:00+00:00",
"duration": 32409,
"breakLength": 0,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 165,
"userId": 1,
"startTime": "2024-08-26T12:34:47+00:00",
"endTime": "2024-08-26T12:58:15+00:00",
"duration": 1408,
"breakLength": 0,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 167,
"userId": 1,
"startTime": "2024-09-05T12:07:30+00:00",
"endTime": "2025-03-27T14:53:08+00:00",
"duration": 17548827,
"breakLength": 311,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 171,
"userId": 1,
"startTime": "2026-05-05T09:52:51+00:00",
"endTime": "2026-05-05T09:53:37+00:00",
"duration": 20,
"breakLength": 26,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 173,
"userId": 1,
"startTime": "2026-05-05T09:55:38+00:00",
"endTime": "2026-05-05T09:55:39+00:00",
"duration": 1,
"breakLength": 0,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
},
{
"id": 175,
"userId": 1,
"startTime": "2026-05-05T10:07:02+00:00",
"endTime": "2026-05-05T10:07:06+00:00",
"duration": 2,
"breakLength": 2,
"state": { "status": "closed", "recommendedCloseTime": null },
"isApproved": true,
"tzOffset": 7200
}
],
"meta": { "hasMore": false, "total": 46, "from": "2020-01-01T00:00:00Z" }
}
Error response example
400 — a required parameter is missing:
{
"success": false,
"error": {
"code": "MISSING_REQUIRED_PARAMS",
"message": "userId is required. Bitrix24 refuses this method without it; get the id from GET /v1/users/me."
}
}
Errors
| HTTP | Code | Description |
|---|---|---|
| HTTP400 | CodeMISSING_REQUIRED_PARAMS |
DescriptionuserId is missing |
| HTTP400 | CodeINVALID_PARAMS |
DescriptionParameter validation failed: an invalid userId, limit outside 1..50, a date without an explicit offset or one that does not exist in the calendar, from later than to, offset together with page, a parameter passed twice, an offset above 999,999,999, or an unknown query parameter |
| HTTP401 | CodeMISSING_API_KEY |
DescriptionThe X-Api-Key header is missing |
| HTTP401 | CodeINVALID_API_KEY |
DescriptionThe key is not recognized — no such key exists on the platform |
| HTTP401 | CodeKEY_EXPIRED |
DescriptionThe API key has expired |
| HTTP401 | CodeTOKEN_MISSING |
DescriptionThe key has no OAuth tokens configured for the portal |
| HTTP402 | CodeACCOUNT_FROZEN |
DescriptionThe portal balance is frozen |
| HTTP403 | CodeSCOPE_DENIED |
DescriptionThe key does not satisfy the compound requirement: it needs timeman plus at least one of user_brief, user_basic, or user |
| HTTP403 | CodeBITRIX_ACCESS_DENIED |
DescriptionBitrix24 denied access — including when the key is not allowed to read the specified employee's time sheet |
| HTTP409 | CodeTIMEMAN_MODULE_NOT_ENABLED |
DescriptionBitrix24 did not accept the method. Call GET /v1/workday/status: the same refusal means the time tracking module is disabled on the portal; a 200 response means the portal does not return workday history while the current day state stays available |
| HTTP422 | CodeBITRIX_ERROR |
DescriptionOther Bitrix24 refusals — the text is in message |
| HTTP429 | CodeRATE_LIMITED |
DescriptionThe rate limit of this endpoint is exceeded — 30 requests per minute per key. The current limit is returned in the x-ratelimit-limit header. For an application key the count is kept separately for each employee |
| HTTP429 | CodeRATE_LIMITED |
DescriptionThe Bitrix24 portal request limit is exceeded |
| HTTP502 | CodeBITRIX_UNAVAILABLE |
DescriptionThe Bitrix24 portal is unavailable, or the current IANA profile zone or the record offset derived from it cannot be determined reliably; an unknown former zone does not cause this error |
For the full list of common API errors, see Errors.
Known specifics
- The key does not restrict permissions to the caller's identity. A personal key
vibe_api_…accesses the Bitrix24 account through an inbound webhook and reads the time sheet with the rights of the Bitrix24 employee the webhook was issued under — that is, the rights of the key creator, not of whoever uses the key. The owner of such a key can enumerateuserIdand export the clock-in and clock-out times of the whole company. An application keyvibe_app_…works differently — the request goes with the rights of the employee who opened the session. Bitrix24, not the platform, decides who may read another employee's time sheet: the Bitrix24 account administrator or the employee's direct manager. - The response names the period it applied.
meta.fromandmeta.toare present even when no period was passed: otherwise ameta.totalof0cannot be told apart from "this employee has no records at all", though it only means "none in the last 7 days". For a period left open on one side, only the bound that is set comes back. - A parameter must not be passed twice.
?userId=1&userId=2returns400instead of silently picking one of the values. meta.totalarrives only on a short page. On a full page the field is absent from the response entirely, so a handler that reads it unconditionally getsundefined. The value is correct at the time of the response: withorder=desca record added between two requests shifts the output window.- The offset in
startTimeandendTimeis not the employee's time zone. Both strings keep the account offset and are not rewritten.tzOffsetis calculated for the absolutestartTimeinstant using the historical rules of the current IANA time zone (TIME_ZONE) in the employee profile. To get local time in that zone, parse the absolute instant fromstartTimeand applytzOffset. If another IANA zone was assigned after record creation, the API does not know the former assignment: the returned value belongs to the current profile zone and is not proof of the zone assigned at that time. - Durations arrive in seconds.
durationandbreakLengthare integer numbers of seconds. The relatedGET /v1/workday/statusreturns durations as a string of the formHH:MM:SS, so parsing does not carry over from one page to the other. - The selection is limited to the employee and the day start time. The endpoint supports no other filters: you cannot filter by end time, duration or approval status, and sorting is by start time only. Filter the returned records locally to obtain the subset you need.
- The whole history is traversed in pages of 50 records. The endpoint does not return more than 50 records per request; deeper pages are reached with
offsetorpage. The end of the output is signaled bymeta.hasMorewith the valuefalse. A request with an offset beyond the end of the selection returns an empty array, andmeta.totaldoes not arrive in such a response.