> This page is for С камеры в облако.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://next.developer.frame.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://next.developer.frame.io/_mcp/server.

# Практическое руководство: обработка ошибок

## Введение





В этом руководстве рассматривается обработка ошибок при взаимодействии с API-интерфейсом C2C. Корректная обработка ошибок HTTP — важнейшая часть надежной интеграции с любой сторонней службой.





## Типы ошибок





Ошибки в вашей интеграции могут появляться из различных источников, которые можно разделить на четыре основные группы:




* **Ошибки ввода-вывода:** возникают в результате аппаратных операций на вашем устройстве, например при сбоях операций чтения/записи.
* **Ошибки приложения:** возникают из-за проблем в коде вашего приложения.
* **Сетевые ошибки:** возникают в сетевом стеке и передаются вашей сетевой библиотекой.
* **Ошибки API-интерфейса:** генерируются серверными службами Frame.io.




Для каждой категории ошибок нужен свой подход к обработке. Это руководство посвящено в основном ошибкам API-интерфейса, но мы рассмотрим и общие стратегии для других категорий.





## Как API-интерфейс возвращает ошибки





API-интерфейс Frame.io передает ошибки с помощью двух основных механизмов:




* **Коды состояния:** коды ошибок HTTP, которые указывают на характер проблемы.
* **Сообщения об ошибках:** содержимое нагрузки, которое предоставляет дополнительные сведения об ошибке, особенно когда несколько разных ошибок имеют одинаковый код состояния.




### Коды состояния ошибок

Коды состояния HTTP — это стандартизированные числовые ответы, которые сообщают результат HTTP-запроса. Подробнее см. [документацию по кодам состояния HTTP от Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) или [HTTP Cats](https://http.cat/) с очень наглядными иллюстрациями. Каждая конечная точка API-интерфейса Frame.io указывает ожидаемый код состояния успеха — обычно `200 (OK)`, `201 (Created)` или `204 (No Content)`. Вы можете убедиться, что запрос успешно выполнен, либо проверив эти конкретные коды, либо убедившись, что код находится в диапазоне 200–299. Коды состояния выше 399 указывают на ошибки. Большинство ошибок API-интерфейса возвращают коды 4XX (400-499), указывая на проблемы на стороне клиента. Ошибки за пределами этого диапазона обычно возникают в сетевой инфраструктуре между вашим устройством и нашей службой, за исключением ошибки `500 (Internal Server Error)`, которая указывает на неожиданную проблему на нашем сервере. Кроме того, ответ `404 (Not Found)` может быть передан промежуточными сервисами, а не нашим сервером, несмотря на то что это код 4XX.

Если вы получили неожиданный код состояния, сообщите об этом нашей команде.





### Схемы данных об ошибках





Frame.io возвращает сведения об ошибках в двух форматах: *простом* и *подробном*. Ваша логика обработки ошибок должна учитывать оба формата.





### Простая схема информации об ошибках

Вот пример запроса с неправильным `client_secret`, что привело к ошибке:

```shell
curl -X POST https://api.frame.io/v2/auth/device/code \
    --include \
    --header 'x-client-version: 2.0.0' \
    --form 'client_id=Some-Client-ID' \
    --form 'client_secret=bad_secret' \
    --form 'scope=asset_create offline'
```





Ответ:





```text
HTTP/2 400
...

{"error":"invalid_client"}
```





Простая схема содержит только одно поле для идентификации ошибки.





### Подробная схема информации об ошибках





Для сравнения, вот запрос без надлежащей авторизации:





```shell
curl -X POST https://api.frame.io/v2/devices/heartbeat \
    --header 'Authorization: Bearer bad-token' \
    --header 'x-client-version: 2.0.0' \
    | python -m json.tool
```





Ответ:





```json
{
    "code": 409,
    "errors": [
        {
            "code": 409,
            "detail": "The channel you're uploading from is currently paused.",
            "status": 409,
            "title": "Channel Paused"
        }
    ],
    "message": "Channel Paused"
}
```

Подробные ошибки содержат поле `message`, которое описывает тип ошибки.

### Определение типа ошибки





При обработке ошибок Frame.io сначала проверьте наличие структурированных данных об ошибке, а затем используйте код состояния HTTP, если такие данные отсутствуют.





Пример базовой реализации обработки ошибок:





**`Python`**

```python title="Python"
# Dict of known error codes: native errors.
ERROR_STATUS_MAP = {
    429: SlowDownError,
    ...
}

# Dict of known error messages: native errros.
ERROR_MESSAGE_MAP = {
   "Channel Paused": ChannelPausedError,
   "invalid_client": InvalidClientError,
   "slow_down": SlowDownError,
   ...
}

def _c2c_extract_error_message(response):
    """
    Gets the error message from an error payload. Returns `None` 
    if an error payload is not found.
    """

    # Try to decode the payload, if it is not JSON return `None`
    try:
        payload = response.json() 
    except JSONDecodeError:
       return None

    # Try the simple error schema first.
    message = payload.get("error", default=None)
    if message is not None:
        return message

    # Now try the detailed schema. Return None if we do not find one.
    return payload.get("message", default=None)

def _c2c_error_type_from_response(response):
    """
    Converts a bad HTTP response into an error.
    """
    error_message = _c2c_extract_error_message(response)

    # try to do a lookup of the error type by message.
    error_type = ERROR_MESSAGE_MAP.get(error_message, default=None) 
    if error_type is not None:
        return error_type()

    # If not, try to do a lookup by error code.
    error_type = ERROR_STATUS_MAP.get(response.status_code, default=None)
    if error_type is not None:
        return error_type()

    # Otherwise we are going to return an `UnknownAPIError` to signal that we
    # encoutnered an error from Frame.io's backend servers, but do not know the
    # message and/or status code.
    return UnknownAPIError(message=error_message)

def raise_on_frameio_error(response, expected_status):
    """
    Raises a native error from an HTTP response if the response indicates an error
    occured. Expected status should be the status we expect to get (200, 201, 204, 
    etc).
    """

    # If the status code is less than `400`, then it is not an error status code.
    if response.status < 400:

        # Check that the status code is the one we expected, otherwise raise an
        # error.
        if response.status != expected_status:
            raise UnexpectedStatusError(
                expected=expected_status, received=response.status
            )

        return None

    # Otherwise convert and raise a native error.
    raise _c2c_error_type_from_response(response)
```





Справочные таблицы ошибок, упомянутые в этом примере, предоставлены в конце данного руководства.





### Ошибки AWS

При добавлении фрагментов файлов вы работаете напрямую с AWS S3, который имеет собственный формат ошибок. Подробную информацию см. в [документации по распространенным ошибкам AWS](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList). Как правило, при несущественных ошибках AWS следует сделать еще как минимум одну попытку.

Ошибки AWS возвращаются в формате XML:





```xml
<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>NoSuchKey</Code>
  <Message>The resource you requested does not exist</Message>
  <Resource>/mybucket/myfoto.jpg</Resource> 
  <RequestId>4442587FB7D0A2F9</RequestId>
</Error>
```

Элемент `Code` идентифицирует тип ошибки.

## Повторные попытки при ошибках





### Когда повторять запросы

В таблицах ошибок в этом руководстве указано, при каких ошибках API-интерфейса нужно сделать еще одну попытку. Что касается ошибок, вызванных не API-интерфейсом, а операциями ввода-вывода, сетевыми библиотеками или AWS, рекомендуем повторять те, которые могут возникнуть из-за временных условий. Перегрузка сети, временные отключения сервера или потеря пакетов обычно требуют повторных попыток. Большинство сетевых библиотек вызывают ошибку `TimeoutError`, когда выполнение запроса занимает слишком много времени. Это основной случай, когда нужно повторить попытку.

### Если есть сомнения, попробуйте еще раз

В вычислительных средах могут возникать непредсказуемые проблемы. Даже если ошибка кажется фатальной, часто стоит сделать одну повторную попытку. Временные состояния системы, аппаратные сбои (например [переворот битов космическими лучами](https://www.youtube.com/watch?v=AaZ_RSt0KP8)) или иногда состояния памяти могут вызывать ошибки, которые кажутся фатальными, но разрешаются при второй попытке. Однако для некоторых ошибок выполнять повторные попытки не следует. Например, ответ `409: CHANNEL PAUSED` при создании ресурса указывает, что устройство приостановлено и не должно выполнять добавление. Это состояние установлено преднамеренно и вряд ли изменится при повторной попытке.

### Экспоненциальная задержка

Frame.io реализует ограничение скорости, при превышении ограничений возникает либо ошибка `429: Slow Down`, либо состояние `400` со следующими структурированными данными:

```text
HTTP/2 400

{"error":"slow_down"}
```





Получив такие ответы, включите экспоненциальную задержку для повторных попыток. Рекомендуемая формула для расчета задержки (в секундах):





**`Python`**

```python title="Python"
delay = min(2 ** attempt / 2, 32.0)
```

Где `attempt` начинается с 0. В результате задержка постепенно увеличивается: 0,5 с, 1 с, 2 с, 4 с, 8 с, 16 с, 32 с, и для всех последующих попыток составляет 32 секунды.
<Info title="Произвольное изменение времени задержки">
  Мы рекомендуем добавить к времени задержки случайный компонент, чтобы предотвратить синхронизацию повторных запросов от нескольких устройства после одного и того же состояния ошибки. Это помогает снизить риск [стадного эффекта](https://medium.com/@venkteshsubramaniam/the-thundering-herd-distributed-systems-rate-limiting-9128d20e1f00), когда многие устройства одновременно повторяют запросы после сбоя. Лучший вариант — добавить случайное смещение между 0 и половиной рассчитанной задержки: `math.rand(0, delay // 2)`.
</Info>


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





### Обнаружение состояния отключения





Сетевые ошибки могут указывать на то, что Frame.io недоступен по следующим причинам:




* Локальная сеть не работает
* Службы Frame.io испытывают проблемы
* Промежуточный сетевой компонент не работает




Важно различать эти ситуации. Если ошибка указывает на проблему с подключением, запустите задачу мониторинга, которая проверит восстановление службы и проинформирует пользователя об отключении.





### Ожидание подключения и авторизации





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

Блокируйте все вызовы API-интерфейса (кроме `https://api.frame.io/health`), когда обнаруживаете состояние отключения. При возникновении проблем с подключением запустите фоновую задачу, которая опрашивает конечную точку состояния и блокирует дальнейшие вызовы API-интерфейса до восстановления подключения.

Аналогично, если истек срок действия токена, блокируйте вызовы, зависящие от авторизации, до выдачи нового токена. Если обновить токен не удается, предупредите пользователя, что необходима повторная аутентификация.





При опросе состояния подключения применяйте тот же подход с экспоненциальной задержкой, описанный ранее.





### Таймауты запросов





Настройте соответствующие значения таймаута для разных типов запросов:




* **По умолчанию**: 15 секунд для базовых запросов
* **Обновление авторизации**: 2 минуты с учетом потенциальной обработки на сервере
* **Добавление фрагмента файла**: 5 минут для работы с медленными сетями при передаче больших данных




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





Реализация в псевдокоде, показывающая обработку ошибок с экспоненциальной задержкой:





**`Python`**

```python title="Python"
# List of errors we know are fatal and should not be retried.
FATAL_ERRORS = (
    ChannelPausedError,
    DevicesDisabledError,
    ...
)

# List of errors we know should be retried more than once.
RETRY_ERRORS = (
    TimeoutError,
    NotFoundError,
    SlowDownError,
    UnknownAPIError,
    ...
)

# List of errors that could be the result of Frame.io being unreachable.
DISCONNECTED_ERRORS = (
    TimeoutError,
    HttpClientError,
    ...
)

def retry_with_backoff(next_handler):
    """
    Middleware for retrying errors with exponential backoff.
    """

    def retry_handler(call, retry_count):
        """
        Handler for retrying c2c API calls with exponential backoff.
        """

        error = None

        # We will retry the call 8 times here, totalling 63.5 seconds +- ~32 seconds.
        for attempt in range(start=1, stop=retry_count + 1):

            # If we are attempting to reach an endpoint that requires authorization
            # we should wait unil we have valid authorization before attempting
            # a call. We need to do this each time in case our access_token
            # expires between attempts.
            C2C.wait_for_authorized(call)

            # Likewise, we should wait until we are connected to Frame.io to attempt
            # a call if we are not calling `https://api.frame.io/health`
            C2C.wait_for_connected(call)

            try:
                # Return the result on a success.
                return next_handler(call)
            except FATAL_ERRORS as error:
                # If we hit an error we know is fatal, raise the error without
                # retrying it.
                raise error

            except RETRY_ERRORS as error:
                # If we hit an error we know we should retry many times, continue,
                # but notify our client if we think we may have been disconnected.
                if type(error) in DISCONNECTED_ERRORS:
                    C2C.notify_disconnected()

            except BaseException as error:
                # Otherwise, do not retry the call more than once.
                if attempt > 1:
                    raise error

            # The delay for the next attempt should be no more than 32 seconds.
            # This algorithm will go: 0.5s, 1s, 2s, 4s, 8s, 16s, 32s, 32s, ...
            delay = min(2 ** attempt / 2, 32.0)

            # Add some randomness (jitter) to the delay (up to half the value of
            # the delay in either direction).
            delay += math.random(-delay, delay) / 2

            # Wait between retries
            sleep(delay)

        # If we have exhausted all retries,
        raise error

    return retry_handler
```





## Таблицы ошибок





В следующих таблицах приведены категории ошибок API-интерфейса Frame.io и рекомендации по их обработке. Вот что означает каждый столбец:

`Сообщение`: идентификатор сообщения структурированных данных об ошибке `Код HTTP`: код состояния HTTP `Тип ошибки`: логическая категория ошибки (подробно описана в разделе [Описания](#descriptions)) `Схема`: формат структурированных данных об ошибке ([простой ](#simple-error-schema) или [подробный](#detailed-error-schema)) `Повтор`: рекомендация по повторным попыткам (`да` — несколько попыток, `одна` — одна попытка, `нет` — критическая ошибка) Звездочки (*) означают, что в разделе [Описания](#descriptions) приведены особые указания.

### Сообщения об ошибках Frame.io




| Сообщение | Тип ошибки | Код HTTP | Схема | Повтор |
| ----------------------------- | ---------------------------------------------------------- | ------------------ | ----------------- | ------- |
| &quot;access_denied&quot; | [AccessDenied](#accessdenied) | 401 | [простой](#simple-error-schema) | одна |
| &quot;authorization_pending&quot; | [AuthorizationPending](#authorizationpending) | 400 | [простой](#simple-error-schema) | да |
| &quot;Channel Paused&quot; | [ChannelPaused](#channelpaused) | 409 | [простой](#simple-error-schema) | нет |
| &quot;expired_token&quot; | [ExpiredToken](#expiredtoken) | 400 | [простой](#simple-error-schema) | нет |
| &quot;Invalid Argument&quot; | [InvalidArgument](#invalidargument) | 422 | [подробный](#detailed-error-schema) | нет |
| &quot;invalid_client&quot; | [InvalidClient](#invalidclient) | 400 | [простой](#simple-error-schema) | нет |
| &quot;Invalid client version&quot; | [InvalidClientVersion](#invalidclientversion) | 400 | [простой](#simple-error-schema) | нет |
| &quot;invalid_grant&quot; | [InvalidGrant](#invalidgrant) | 400 | [простой](#simple-error-schema) | нет |
| &quot;invalid_request&quot; | [InvalidRequest](#invalidrequest) | 400 | [простой](#simple-error-schema) | одна |
| &quot;Not Authorized&quot; | [UnauthorizedClient](#unauthorizedclient) | 401 | [подробный](#detailed-error-schema) | нет |
| &quot;slow_down&quot; | [SlowDown](#slowdown) | 400 | [простой](#simple-error-schema) | да |
| &quot;unauthorized_client&quot; | [UnauthorizedClient](#unauthorizedclient) | 401 | [простой](#simple-error-schema) | да* |




### Коды состояния Frame.io




| Код HTTP | Тип ошибки | Повтор |
| --------------- | ------------------------------------------------------ | ------- |
| 400 | [InvalidRequest](#invalidrequest) | одна |
| 401 | [UnauthorizedClient](#unauthorizedclient) | нет |
| 422 | [InvalidContentType](#invalidcontenttype) | нет |
| 429 | [SlowDown](#slowdown) | да |
| 500 | [InternalServerError](#internalservererror) | да |




### Ошибки AWS

Подробное описание [см. в документации AWS](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList).
| Ошибка | Повтор |
| ------------------------- | ------- |
| InternalError | да |
| OperationAborted | да |
| RequestTimeout | да |
| ServiceUnavailable | да |
| SlowDown | да |
| [Все остальные ошибки] | одна |



<Info title="Разбор похожих ошибок AWS">
  Как `SlowDown`, так и `ServiceUnavailable`, возвращенные AWS, указывают на проблемы с частотой запросов и могут обрабатываться аналогично ошибке `SlowDown`, возвращенной Frame.io, с использованием экспоненциальной задержки. Аналогичным образом ошибка `InternalError`, возвращенная AWS, по смыслу соответствует ошибке `InternalServerError` в нашем API-интерфейсе.
</Info>


### Описания





#### AccessDenied





Возвращается, когда пользователь отклоняет авторизацию во время сопряжения устройства.





#### AuthorizationPending

Указывает, что пользователь еще не ввел код сопряжения устройства. Продолжайте опрос после периода `interval`, указанного в ответе с кодом устройства.

#### ChannelPaused





Канал устройства был приостановлен при создании ресурса. *Не пытайтесь добавить этот ресурс снова.*





#### ExpiredToken





Срок действия кода сопряжения устройства истек. Создайте новый код и перезапустите процесс сопряжения.





#### InternalServerError

Указывает на неожиданную проблему сервера. Повторите попытку один раз. Сообщайте нашей команде об ошибках `500`, чтобы мы провели расследование. Обратите внимание: что некоторые известные проблемы возвращают ошибку `500`, хотя должны возвращать `InvalidRequest`:
* Попытка добавления в несуществующий канал устройства
* Запрос недопустимого количества пользовательских фрагментов




#### InvalidArgument





Параметр полезной нагрузки содержал недопустимое значение. Убедитесь, что значения параметров соответствуют ожиданиям API-интерфейса.





#### InvalidContentType

Заголовок `Content-Type` запроса не поддерживается. API-интерфейс обычно принимает:
* `form/multipart` (только конечные точки авторизации)
* `application/x-www-form-urlencoded` (все конечные точки)
* `application/json` (конечные точки без авторизации)




#### InvalidClient

Предоставленные учетные данные (`client_id`, `client_secret` и т. д.) не распознаны. Проверьте учетные данные интеграции.

### InvalidClientVersion

Заголовок `x-client-version` был продублирован или содержит недопустимую [семантическую версию](https://semver.org/).

#### InvalidGrant





Тип разрешения авторизации недействителен. Правильные значения приведены в руководствах по авторизации.





#### InvalidRequest





Параметры запроса или формат полезной нагрузки неверны. Проверьте имена полей и форматы значений.





Если ошибка возвращена во время обновления токена, ваш срок действия токена обновления истек, и нужно перезапустить процесс авторизации.





#### SlowDown





Вы превысили ограничение частоты запросов. Используйте экспоненциальную задержку для последующих запросов. Обратите внимание, что эту ошибку может вызвать выполнение нескольких запросов кода устройства через одно и то же подключение TCP — создавайте новые подключения для каждого запроса сопряжения.





#### UnauthorizedClient

Обычно указывает на истекший или отсутствующий `access_token`. Если возвращена эта ошибка, обновите токен перед повторной попыткой.

Если ошибка возникает при обновлении токена, необходимо перезапустить процесс авторизации и предложить пользователю повторно подключиться.





Эта ошибка также может возникнуть при доступе к ресурсам вне области авторизации вашего устройства или когда проект отключил устройства C2C. Убедитесь, что вы запросили соответствующие области во время авторизации.





*Если эта ошибка возникает при обновлении токена, весь процесс авторизации должен быть перезапущен с участием пользователя.*





## Дальнейшие шаги

Мы рекомендуем обращаться к нашей команде с любыми вопросами и переходить к [руководству по расширенному добавлению](./how-to-advanced-uploads). Мы с удовольствием поможем вам в продолжении изучения процессов интеграций. Если вы еще не сделали этого, просмотрите руководство [Реализация C2C: настройка](./implementing-c2c-setting-up) перед продолжением. Вам понадобится `access_token`, полученный [в процессе аутентификации и авторизации](./implementing-c2c-authentication-and-authorization). Это руководство основано на [руководстве по базовому добавлению](./how-to-basic-upload) и [руководстве по расширенному добавлению](./how-to-advanced-uploads).