# Идемпотентность, ошибки и лимиты

Как безопасно повторять изменяющие запросы Invoise, что означают коды ошибок и какие действуют лимиты.

Source: https://docs.invoise.me/ru/integration/idempotency-and-errors/

После таймаута непонятно, прошёл запрос или нет. Повторите ту же операцию с тем же `Idempotency-Key`, чтобы не создать второй инвойс.

## Один ключ на действие

1. До первого запроса создайте уникальный ключ и сохраните рядом с инвойсом в вашей системе.
2. Передайте его в `Idempotency-Key` при создании или изменении бизнес-объекта.
3. После таймаута повторите тот же метод, путь и тело с тем же ключом.
4. Для нового действия создайте новый ключ.

Ключ должен быть непустым и не длиннее 128 байт. Повтор завершённого действия вернёт сохранённый ответ. Другое тело с тем же ключом даст `409 idempotency_conflict`.

Это относится к изменениям инвойсов, магазинов, ключей, вебхуков и другим бизнес-операциям. GET-запросы и запросы входа не используют этот механизм. Требования указаны у каждого метода.

`external_id` связывает инвойс или депозит с записью в вашей системе, но не заменяет ключ идемпотентности.

## Читайте код ошибки

Пример:

```json
{"error":{"code":"idempotency_conflict","message":"idempotency_conflict"}}
```

В программе используйте `error.code`. Сначала проверьте HTTP-статус, потом разбирайте тело ответа.

| Код | Что делать |
| --- | --- |
| `unauthorized` | Проверить ключ, права и ограничение IP. Если сессия истекла — войти заново. |
| `mfa_required` | Подтвердить второй фактор с токеном сессии. |
| `onboarding_required` | Завершить `/onboarding` перед работой с магазинами. |
| `idempotency_key_required` | Передать сохранённый непустой ключ до 128 байт. |
| `idempotency_conflict` | Найти исходный запрос. Не создавать второй инвойс молча. |
| `json_content_type_required` / `invalid_json` | Отправить `Content-Type: application/json` и только описанные поля. |
| `unsupported_network` / `unsupported_asset` | Выбрать поддерживаемую сеть и токен. |
| `network_not_ready` / `asset_not_ready` | Дождаться готовности или выбрать другой доступный маршрут. |
| `amount_below_minimum` / `fee_exceeds_amount` | Проверить сумму, знаки токена и условия магазина. |
| `shop_archived` / `shop_not_found` | Проверить магазин и права доступа. |
| `invalid_expires_in` | Передать `1d`, `7d`, `30d`, `180d` или `365d` и только при создании инвойса. У депозитов нет срока. |
| `invalid_assets` / `issuance_in_group` | Передать разные пары `{chain_id, token}` без `chain_id` и `token`; отменять или отключать группу по её собственному `id`. См. [несколько токенов или сетей](https://docs.invoise.me/ru/payments/deposits-and-invoices/#несколько-токенов-или-сетей). |
| `solana_setup_required` / `tron_setup_required` | Сначала задать [получателя Solana или Tron](https://docs.invoise.me/ru/payments/solana-and-tron/) у магазина. |
| `invoice_amount_limit` / `sandbox_amount_limit` | Уменьшить сумму до [лимита](#лимиты). |
| `active_invoice_limit_reached` / `deposit_address_limit_reached` | Отменить ненужные инвойсы или отключить ненужные депозиты либо попросить Invoise поднять лимит. |
| `api_key_limit_reached` / `too_many_allowed_ips` / `webhook_limit_reached` | Отозвать неиспользуемые ключи, сократить список IP или использовать существующий адрес вебхука. |
| `team_limit_reached` | Удалить участника или отозвать приглашение либо попросить Invoise поднять лимит. |
| `account_disabled` | Сотрудники Invoise заблокировали аккаунт; причина — в `error.details`. См. [заблокированные аккаунты](https://docs.invoise.me/ru/integration/team-and-access/#заблокированные-аккаунты). |
| `rate_limited` | Подождать столько секунд, сколько указано в `Retry-After`, и повторить. |

## Лимиты

Если запрос превышает лимит, Invoise отклоняет его с обычным телом ошибки и кодом из таблицы.

| Лимит | Значение | Ошибка |
| --- | --- | --- |
| Сумма одного инвойса | 1 000 000 токенов | `400 invoice_amount_limit` |
| Сумма одного инвойса или имитированного перевода в Sandbox | 10 000 токенов | `400 sandbox_amount_limit` |
| Активные инвойсы мерчанта | 2 000 | `400 active_invoice_limit_reached` |
| Активные депозитные адреса мерчанта в одном семействе сетей | EVM 10 000, Tron 10 000, Solana 10 | `400 deposit_address_limit_reached` |
| Неотозванные API-ключи магазина | 10 | `400 api_key_limit_reached` |
| Разрешённые IP-адреса или CIDR-диапазоны одного ключа | 10 | `400 too_many_allowed_ips` |
| Адреса вебхуков магазина | 10 | `400 webhook_limit_reached` |
| Участники и ожидающие приглашения мерчанта | 10 | `400 team_limit_reached` |
| Запросы в минуту | 1 200, из них не больше 120 не-GET | `429 rate_limited` |

Лимиты сумм указаны в целых токенах, а не в минимальных единицах. Все поддерживаемые токены — долларовые стейблкоины, поэтому 1 000 000 токенов — это около $1 000 000.

Инвойс активен, пока он не оплачен, не отменён и не истёк. Инвойс с несколькими сетями считается один раз. Депозитный адрес активен, пока его не отключили; каждая сеть депозита — отдельный адрес. Sandbox и боевой режим считаются отдельно для обоих лимитов. Сотрудники Invoise могут изменить для мерчанта лимиты инвойсов, депозитных адресов и команды.

Лимит команды проверяется и при приглашении, и при его принятии. URL адреса вебхука можно изменить, поэтому существующий адрес можно использовать вместо нового.

Запросы считаются по API-ключу, по сессии в интерфейсе или, без авторизации, по IP клиента. Ответ `429` содержит `Retry-After: 60`.

## Повторяйте без дублей

При таймаутах и временных серверных ошибках сохраняйте **тот же ключ** и увеличивайте задержку. При `429` подождите столько секунд, сколько указано в `Retry-After`. Ошибки входных данных и доступа сначала исправьте.

Повторную доставку вебхуков делает Invoise. Надёжно сохраните событие, ответьте 2xx и обработайте его. Дубли отсеивайте по `Invoise-Event-ID`.

Старые маршруты `/projects` — аналоги `/shops` с общей областью идемпотентности. Для новых интеграций используйте `/shops`. В старых кодах ошибок может встречаться `project_*`.
