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 identity 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
userId (query) number yes Employee identifier, an integer greater than zero. Your own identifier — GET /v1/users/me, the employee list — GET /v1/users
from (query) string no Start 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
to (query) string no End of the period in the same format. A from later than to returns 400
limit (query) number no Records per page, from 1 to 50. Defaults to 50
offset (query) number no Offset from the start of the selection, a non-negative integer. Defaults to 0. Cannot be combined with page
page (query) number no Page number starting from one, equivalent to offset with the value (page − 1) × limit. Cannot be combined with offset
order (query) string no Order 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

Terminal
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

Terminal
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

Terminal
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

javascript
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

javascript
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
success boolean true on a successful response
data array Array of workday records. An empty array when no record falls within the period
data[].id number Workday record identifier
data[].userId number Identifier of the employee the record belongs to
data[].startTime string Day start time, ISO-8601. The string keeps the account offset and is not rewritten to the employee's time zone
data[].endTime string | null Day end time in the same format. On a day that is still open the field is absent or arrives as null
data[].duration number | null Time worked during the day, in seconds. On a day that is still open the field is absent or arrives as null
data[].breakLength number | null Total break duration for the day, in seconds. On a day that is still open the field is absent or arrives as null
data[].state.status string Record state in lowercase. On a finished day it is closed
data[].state.recommendedCloseTime string | null Recommended day close time. On verified records null
data[].isApproved boolean | null Whether the workday record is approved. On a day that is still open the field is absent or arrives as null
data[].tzOffset number Required seconds east of UTC calculated for the startTime instant using the historical rules of the current IANA TIME_ZONE identity in the employee profile. It is not proof that this zone was assigned when the record was created
meta.hasMore boolean true when the page is filled up to limit and more records follow
meta.total number Number of records in the selection — the sum of offset and the page length. Arrives only when the page is shorter than limit
meta.from string The 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
meta.to string The upper bound of the applied period. Absent for a period left open at the top (only from passed)

Response example

The first three records from oldest to newest, ?userId=1&from=2020-01-01T00:00:00Z&limit=3&order=asc:

JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "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
400 MISSING_REQUIRED_PARAMS userId is missing
400 INVALID_PARAMS Parameter 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
401 MISSING_API_KEY The X-Api-Key header is missing
401 INVALID_API_KEY Invalid API key
401 KEY_EXPIRED The API key has expired
401 TOKEN_MISSING The key has no OAuth tokens configured for the portal
402 ACCOUNT_FROZEN The portal balance is frozen
403 SCOPE_DENIED The key does not satisfy the compound requirement: it needs timeman plus at least one of user_brief, user_basic, or user
403 BITRIX_ACCESS_DENIED Bitrix24 denied access — including when the key is not allowed to read the specified employee's time sheet
409 TIMEMAN_MODULE_NOT_ENABLED Bitrix24 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
422 BITRIX_ERROR Other Bitrix24 refusals — the text is in message
429 RATE_LIMITED The rate limit of this endpoint is exceeded — 30 requests per minute per key. For an application key the count is kept separately for each employee
429 RATE_LIMITED The Bitrix24 portal request limit is exceeded
502 BITRIX_UNAVAILABLE The 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

The full list of common API errors — 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 enumerate userId and export the clock-in and clock-out times of the whole company. An application key vibe_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.from and meta.to are present even when no period was passed: otherwise a meta.total of 0 cannot 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=2 returns 400 instead of silently picking one of the values.
  • meta.total arrives only on a short page. On a full page the field is absent from the response entirely, so a handler that reads it unconditionally gets undefined. The value is correct at the time of the response: with order=desc a record added between two requests shifts the output window.
  • The offset in startTime and endTime is not the employee's time zone. Both strings keep the account offset and are not rewritten. tzOffset is calculated for the absolute startTime instant using the historical rules of the current IANA TIME_ZONE identity in the employee profile. To get local time in that zone, parse the absolute instant from startTime and apply tzOffset. 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. duration and breakLength are integer numbers of seconds. The related GET /v1/workday/status returns durations as a string of the form HH: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 has no other axes: you cannot narrow the selection by end time, duration or the approval flag, and sorting is by start time only. The needed slice is assembled from the returned records on your side.
  • 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 offset or page. The end of the output is signaled by meta.hasMore with the value false. A request with an offset beyond the end of the selection returns an empty array, and meta.total does not arrive in such a response.

See also