
## История рабочего дня

`GET /v1/workday/records`

Возвращает историю рабочих дней сотрудника: время начала и завершения, отработанные секунды и длительность перерывов. Записи отбираются по обязательному идентификатору сотрудника и периоду, а без периода возвращается окно за последние 7 дней.

## Параметры

| Параметр | Тип | Обязательный | Описание |
|----------|-----|--------------|---------|
| `userId` (query) | number | да | Идентификатор сотрудника, целое больше нуля. Свой идентификатор — [`GET /v1/users/me`](/docs/entities/users/me), список сотрудников — [`GET /v1/users`](/docs/entities/users/list) |
| `from` (query) | string | нет | Начало периода, ISO-8601 с явным смещением или `Z` — например `2026-06-01T00:00:00Z`. Отбирает по времени начала дня. Дата без времени возвращает `400`. Знак плюса в смещении кодируется как `%2B`, иначе значение приходит с пробелом вместо знака и не распознаётся |
| `to` (query) | string | нет | Конец периода в том же формате. Значение `from` позже `to` возвращает `400` |
| `limit` (query) | number | нет | Записей на страницу, от 1 до 50. По умолчанию `50` |
| `offset` (query) | number | нет | Смещение от начала выборки, целое от нуля. По умолчанию `0`. Не сочетается с `page` |
| `page` (query) | number | нет | Номер страницы от единицы, равнозначен `offset` со значением `(page − 1) × limit`. Не сочетается с `offset` |
| `order` (query) | string | нет | Порядок по времени начала дня: `desc` — от новых к старым, `asc` — от старых к новым. По умолчанию `desc` |

Оба конца периода необязательны: если ни `from`, ни `to` не переданы, выборка ограничивается последними 7 днями от момента запроса. Параметр запроса вне этого списка возвращает `400 INVALID_PARAMS` с именем переданного ключа и перечнем принимаемых.

## Примеры

### curl — личный ключ

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.tech/v1/workday/records?userId=1&from=2020-01-01T00:00:00Z&limit=3&order=asc"
```

### curl — записи другого сотрудника

```bash
curl -H "X-Api-Key: YOUR_API_KEY" \
  "https://vibecode.bitrix24.tech/v1/workday/records?userId=503&from=2020-01-01T00:00:00Z&limit=3&order=asc"
```

### curl — OAuth-приложение

```bash
curl -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  "https://vibecode.bitrix24.tech/v1/workday/records?userId=1&from=2020-01-01T00:00:00Z&limit=3&order=asc"
```

### JavaScript — личный ключ

```javascript
const params = new URLSearchParams({
  userId: '1',
  from: '2020-01-01T00:00:00Z',
  limit: '3',
  order: 'asc',
})
const res = await fetch(`https://vibecode.bitrix24.tech/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, '— отработано секунд:', record.duration)
}
console.log('Есть ещё записи:', meta.hasMore)
```

### JavaScript — OAuth-приложение

```javascript
const params = new URLSearchParams({
  userId: '1',
  from: '2020-01-01T00:00:00Z',
  limit: '3',
  order: 'asc',
})
const res = await fetch(`https://vibecode.bitrix24.tech/v1/workday/records?${params}`, {
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
  },
})
const { data, meta } = await res.json()
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | `true` при успешном ответе |
| `data` | array | Массив записей рабочего дня. Пустой массив, когда под период не попала ни одна запись |
| `data[].id` | number | Идентификатор записи рабочего дня |
| `data[].userId` | number | Идентификатор сотрудника, которому принадлежит запись |
| `data[].startTime` | string | Время начала дня, ISO-8601 со смещением часового пояса сотрудника |
| `data[].endTime` | string | Время завершения дня в том же формате |
| `data[].duration` | number | Отработано за день, в секундах |
| `data[].breakLength` | number | Суммарная длительность перерывов за день, в секундах |
| `data[].state.status` | string | Состояние записи в нижнем регистре. На проверенных записях приходит `closed` |
| `data[].state.recommendedCloseTime` | string \| null | Рекомендованное время закрытия дня. На проверенных записях `null` |
| `data[].isApproved` | boolean | Признак подтверждённой записи рабочего дня |
| `meta.hasMore` | boolean | `true`, когда страница заполнена до `limit` и за ней есть продолжение |
| `meta.total` | number | Количество записей в выборке — сумма `offset` и длины страницы. Приходит только когда страница короче `limit` |
| `meta.from` | string | Нижняя граница применённого периода. Приходит, когда она задана — переданным `from` либо подставленным окном за последние 7 дней |
| `meta.to` | string | Верхняя граница применённого периода. У периода, открытого сверху (передан только `from`), поля нет |

## Пример ответа

Первые три записи от старых к новым, `?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+03:00",
      "endTime": "2020-04-23T17:53:26+03:00",
      "duration": 12999,
      "breakLength": 2077,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 5,
      "userId": 1,
      "startTime": "2020-04-24T09:16:12+03:00",
      "endTime": "2020-04-24T16:30:13+03:00",
      "duration": 26041,
      "breakLength": 0,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 7,
      "userId": 1,
      "startTime": "2020-04-27T09:04:11+03:00",
      "endTime": "2020-04-27T18:00:00+03:00",
      "duration": 32149,
      "breakLength": 0,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    }
  ],
  "meta": { "hasMore": true, "from": "2020-01-01T00:00:00Z" }
}
```

Последняя страница той же выборки, `?userId=1&from=2020-01-01T00:00:00Z&offset=40&order=asc` — записей пришло меньше `limit`, поэтому в `meta` появляется `total`:

```json
{
  "success": true,
  "data": [
    {
      "id": 163,
      "userId": 1,
      "startTime": "2024-07-24T17:34:51+03:00",
      "endTime": "2024-07-25T02:35:00+03:00",
      "duration": 32409,
      "breakLength": 0,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 165,
      "userId": 1,
      "startTime": "2024-08-26T12:34:47+03:00",
      "endTime": "2024-08-26T12:58:15+03:00",
      "duration": 1408,
      "breakLength": 0,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 167,
      "userId": 1,
      "startTime": "2024-09-05T12:07:30+03:00",
      "endTime": "2025-03-27T14:53:08+03:00",
      "duration": 17548827,
      "breakLength": 311,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 171,
      "userId": 1,
      "startTime": "2026-05-05T09:52:51+03:00",
      "endTime": "2026-05-05T09:53:37+03:00",
      "duration": 20,
      "breakLength": 26,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 173,
      "userId": 1,
      "startTime": "2026-05-05T09:55:38+03:00",
      "endTime": "2026-05-05T09:55:39+03:00",
      "duration": 1,
      "breakLength": 0,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    },
    {
      "id": 175,
      "userId": 1,
      "startTime": "2026-05-05T10:07:02+03:00",
      "endTime": "2026-05-05T10:07:06+03:00",
      "duration": 2,
      "breakLength": 2,
      "state": { "status": "closed", "recommendedCloseTime": null },
      "isApproved": true
    }
  ],
  "meta": { "hasMore": false, "total": 46, "from": "2020-01-01T00:00:00Z" }
}
```

## Пример ответа при ошибке

400 — не передан обязательный параметр:

```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."
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 400 | `MISSING_REQUIRED_PARAMS` | Не передан `userId` |
| 400 | `INVALID_PARAMS` | Нарушена валидация параметров: некорректный `userId`, `limit` вне диапазона от 1 до 50, дата без явного смещения или несуществующая в календаре, `from` позже `to`, `offset` вместе с `page`, повторённый дважды параметр, смещение больше 999 999 999, неизвестный параметр запроса |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `INVALID_API_KEY` | Неверный API-ключ |
| 401 | `KEY_EXPIRED` | Срок действия API-ключа истёк |
| 401 | `TOKEN_MISSING` | Ключу не настроены OAuth-токены для портала |
| 402 | `ACCOUNT_FROZEN` | Баланс портала заморожен |
| 403 | `SCOPE_DENIED` | У ключа нет скоупа `timeman` |
| 403 | `BITRIX_ACCESS_DENIED` | Битрикс24 отказал в доступе — в том числе когда у ключа нет права читать табель указанного сотрудника |
| 409 | `TIMEMAN_MODULE_NOT_ENABLED` | Битрикс24 не принял метод. Вызовите [`GET /v1/workday/status`](/docs/workday/status): тот же отказ означает, что на портале выключен модуль учёта рабочего времени, ответ `200` — что портал не отдаёт историю рабочего дня, а текущее состояние дня остаётся доступным |
| 422 | `BITRIX_ERROR` | Прочие отказы Битрикс24 — текст в `message` |
| 429 | `RATE_LIMITED` | Превышен лимит темпа этого эндпоинта — 30 запросов в минуту на ключ. Для ключа приложения счёт ведётся отдельно по каждому сотруднику |
| 429 | `RATE_LIMITED` | Превышен лимит запросов портала Битрикс24 |
| 502 | `BITRIX_UNAVAILABLE` | Портал Битрикс24 недоступен |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

- **Ключ не сужает права до личности вызывающего.** Личный ключ `vibe_api_…` обращается к порталу через входящий вебхук и читает табель правами того сотрудника Битрикс24, под которым этот вебхук выпущен, — то есть правами создателя ключа, а не того, кто ключ применяет. Владелец такого ключа перебором `userId` выгружает приходы и уходы всей компании. Ключ приложения `vibe_app_…` работает иначе — запрос идёт правами сотрудника, открывшего сессию. Кто вправе читать чужой табель, решает Битрикс24, а не платформа: администратор портала или прямой руководитель сотрудника.
- **Ответ называет применённый период.** `meta.from` и `meta.to` приходят и тогда, когда период не передавали: иначе `meta.total` со значением `0` невозможно отличить от «у сотрудника нет записей вообще», хотя это означает лишь «нет записей за последние 7 дней». У периода, открытого с одной стороны, приходит только заданная граница.
- **Параметр нельзя передавать дважды.** `?userId=1&userId=2` возвращает `400`, а не выбирает одно из значений молча.
- **`meta.total` приходит только на короткой странице.** На полной странице поля в ответе нет вовсе, поэтому обработчик, читающий его безусловно, получит `undefined`. Значение верно на момент ответа: при `order=desc` запись, добавленная между двумя запросами, сдвигает окно выдачи.
- **Длительности приходят в секундах.** `duration` и `breakLength` — целые числа секунд. У соседнего [`GET /v1/workday/status`](/docs/workday/status) длительности отдаются строкой вида `HH:MM:SS`, поэтому разбор с одной страницы на другую не переносится.
- **Отбор ограничен сотрудником и временем начала дня.** Других осей у эндпоинта нет: сузить выборку по времени завершения, длительности или признаку подтверждения нельзя, сортировка тоже идёт только по времени начала. Нужный срез собирается из полученных записей на своей стороне.
- **Вся история обходится страницами по 50 записей.** Больше 50 записей за запрос эндпоинт не отдаёт, глубина берётся через `offset` или `page`. Признак конца выдачи — `meta.hasMore` со значением `false`. Запрос со смещением за концом выборки возвращает пустой массив, и `meta.total` в таком ответе не приходит.

## Смотрите также

- [Текущий статус](/docs/workday/status)
- [Открыть рабочий день](/docs/workday/open)
- [Закрыть рабочий день](/docs/workday/close)
- [Поставить на паузу](/docs/workday/pause)
- [Настройки учёта](/docs/workday/settings)
- [График работы](/docs/workday/schedule)
- [Рабочий день](/docs/workday)
