Nordlet

← Документация / Руководства

Соглашения об API

Структура запросов, права доступа, списочные запросы, денежные суммы, ошибки, идемпотентность, ограничения частоты запросов.

Структура запросов

Каждая операция представляет собой POST-запрос в стиле RPC:

POST /v1/{module}/{resource}/{action}

Модули: reference, partners, catalog, sales, purchases, ledger, bank, declarations, files, webhooks, audit. Действия являются глаголами: create, get, update, delete, list, а также специализированными действиями домена вроде issue, register, match, apply-advance, generate.

Аутентификация и права доступа

Authorization: Bearer <api key>. Ключи содержат права доступа (scopes), которые проверяются для каждого действия:

  • {module}:read - операции чтения и получения списков
  • {module}:write - операции изменения данных
  • {module}:* или * - подстановочные знаки (wildcards)

Отсутствие прав приводит к ошибке 403 forbidden. Отсутствие ключа или его недействительность - к ошибке 401 unauthorized.

Списочные запросы

Все запросы list подчиняются единому контракту:

{
  "page": 1,
  "pageSize": 50,
  "sort": [{ "field": "createdAt", "dir": "desc" }],
  "filter": [{ "field": "paymentStatus", "op": "eq", "value": "unpaid" }]
}

Операторы фильтрации: eq (равно), ne (не равно), contains (регистронезависимый поиск текста, только для текстовых полей), gte (больше или равно), lte (меньше или равно), in (любое значение из списка до 500 элементов). Поля, доступные для сортировки и фильтрации, задаются с помощью белых списков для каждого эндпоинта (см. спецификацию OpenAPI); запросы с неизвестными полями возвращают ошибку 400. Добавьте параметр totals: ["field", …] в запрос для суммирования числовых полей по всем подходящим строкам; если записи содержат валюту, поле totalsByCurrency вернет те же суммы с разбивкой по кодам валют. Ответы: { rows, total, page, pageSize, totals?, totalsByCurrency? }.

Деньги и даты

Денежные суммы передаются в виде строк с фиксированной точкой, содержащих до 4 знаков после запятой ("121.0000"), и никогда не передаются в виде чисел с плавающей точкой. НДС рассчитывается по каждой строке с применением округления к ближайшему большему (half-up). Даты имеют формат YYYY-MM-DD; временные метки (timestamps) передаются в формате ISO 8601 UTC.

Ошибки

Везде используется единый формат ответа с ошибкой:

{
  "error": {
    "code": "validation",
    "message": "Request validation failed",
    "requestId": "k1x…",
    "key": "request_invalid",
    "fieldErrors": { "lines.0.unitPriceExclVat": ["Invalid value"] }
  }
}

Коды: validation (400/422, а также 413, если загруженный файл или тело запроса превышают допустимый размер), unauthorized (401), payment_required (402, пробный период или предоплаченный лимит исчерпаны; в сообщении указано, как продолжить работу), forbidden (403), not_found (404), conflict (409), idempotency_key_reuse (422), idempotency_in_progress (409), rate_limited (429), internal (5xx). Если сообщение входит в число стандартных, поле key указывает его ключ, чтобы клиент мог использовать собственный перевод, а поле params содержит подставляемые значения (например, { "period": "2026-09" } для заблокированного отчетного периода). При обращении в поддержку всегда указывайте requestId.

Идемпотентность

Добавьте заголовок Idempotency-Key: <any string ≤255 chars> к любому запросу, изменяющему данные:

  • Повторный запрос с тем же ключом и полезной нагрузкой возвращает сохраненный ответ байт-в-байт с заголовком x-idempotent-replay: true.
  • Тот же ключ с другой полезной нагрузкой возвращает ошибку 422 idempotency_key_reuse.
  • Одновременный дублирующийся запрос, пока первый находится в обработке, возвращает ошибку 409 idempotency_in_progress.
  • Ключи привязываются к API-ключу или пользователю, который их отправляет, в контексте компании, для которой выполняется вызов, поэтому две разные интеграции одной компании могут использовать один и тот же ключ. Вызовы, сделанные до создания компании, а также вызовы account/*, сохраняют ключи в разрезе пользователей, благодаря чему повторный вызов account/companies/create создает ровно одну компанию.
  • Сохраненный ответ возвращается только после того, как вызывающая сторона повторно пройдет все проверки прав (права доступа, роль, компания и статус выставления счетов); в противном случае запрос получит актуальную ошибку.
  • Ключи истекают через 24 часа. Ответы с ошибками (4xx) также воспроизводятся при повторных запросах; ответы 5xx не сохраняются, поэтому повторные запросы выполняются заново.

Ограничения частоты запросов (Rate limits)

300 запросов в минуту на один API-ключ (на предоставленные учетные данные; для неаутентифицированных запросов лимиты применяются по IP-адресу). При получении ошибки 429 учитывайте заголовок retry-after. Заголовки x-ratelimit-* информируют об оставшемся лимите.

Вебхуки

События записываются в транзакционную таблицу исходящих сообщений (outbox) в рамках той же транзакции базы данных, что и само изменение: для отмененной транзакции событие никогда не отправляется, а ни одно подтвержденное событие не теряется. Доставка:

  • POST-запрос на ваш URL-адрес с JSON-представлением события
  • x-nordlet-timestamp: <unix seconds> - время подписания сообщения
  • x-nordlet-signature-v2: t=<timestamp>,v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">, создается с использованием вашего секретного ключа подписки
  • x-nordlet-signature: sha256=<hex HMAC-SHA256 of the raw body> - сохраняется в неизменном виде для совместимости с существующими получателями
  • x-nordlet-delivery содержит идентификатор доставки, который остается неизменным при любых повторных попытках отправки одного и того же события
  • Ответ, отличный от диапазона 2xx, или перенаправление (redirect) приводят к повторной попытке через 30 секунд, 2 минуты, 10 минут, 30 минут, 1 час, 3 часа, 6 часов и 12 часов (всего в течение примерно 24 часов), после чего доставка помечается как неудавшаяся
  • Каждая доставка отправляется только одним сервером одновременно, поэтому запуск нескольких экземпляров API не приведет к дублированию отправки

Перед доверием к полезной нагрузке проверьте заголовок x-nordlet-signature-v2 с помощью сравнения за постоянное время (constant-time comparison) и отклоните доставку, если ее временная метка отличается от системных часов более чем на 5 минут. Временная метка входит в состав подписанного текста, поэтому перехваченную доставку нельзя воспроизвести позже со свежей временной меткой. Сохраняйте идентификаторы обработанных доставок из заголовка x-nordlet-delivery, чтобы игнорировать повторы в пределах этого окна. Неудавшуюся доставку можно отправить повторно через webhooks/deliveries/redeliver.

Аудит

Любое изменение данных фиксируется в системе (actor, action, entity, diff) и доступно для выборки через audit/list.

Бухгалтерские гарантии

  • Проводки в журнале всегда сбалансированы. Это контролируется отложенным триггером базы данных в момент фиксации транзакции (commit), а не только кодом приложения.
  • Нумерация документов идет без пропусков в разрезе серий и лет; выделение номеров безопасно при высокой параллельности.
  • Заблокированные отчетные периоды отклоняют любые проводки с датой внутри них (409).
  • Документы с мягким удалением (soft-deleted) сохраняют свою историю в аудите; проведение или регистрация документов исключает возможность их удаления.