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

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
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:

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
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 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 (TIME_ZONE) 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 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 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

Work and daily reports

Work reports

Daily reports