> This page is for Camera to Cloud.

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

# 오류 처리 방법

## 소개





이 가이드에서는 C2C API와 상호 작용할 때의 오류 처리를 다룹니다. HTTP 오류를 적절하게 처리하는 것은 타사 서비스와 안정적으로 통합하기 위한 필수 구성 요소입니다.





## 오류 유형





통합 시 발생하는 오류는 다양한 소스에서 발생할 수 있으며, 네 가지 주요 그룹으로 분류할 수 있습니다.




* **I/O 오류:** 실패한 읽기/쓰기 작업과 같이 디바이스의 하드웨어 작업에서 발생합니다.
* **애플리케이션 오류:** 애플리케이션 코드 내의 문제로 인해 발생합니다.
* **네트워크 오류:** 네트워킹 스택 내에서 발생하며 네트워킹 라이브러리에 의해 전달됩니다.
* **API 오류:** Frame.io의 백엔드 서비스에 의해 생성됩니다.




각 오류 범주별로 특정 처리 방식을 고려해야 합니다. 이 가이드에서는 주로 API 오류에 초점을 맞추고 있지만, 다른 범주에 대한 일반적인 전략도 함께 다룹니다.





## API 오류 반환 방법





Frame.io의 API는 두 가지 기본 메커니즘을 통해 오류를 전달합니다.




* **상태 코드:** 문제의 특성을 나타내는 HTTP 오류 코드
* **오류 메시지:** 여러 오류 조건이 동일한 상태 코드를 공유하는 경우 등에 추가 오류 세부 정보를 제공하는 페이로드 콘텐츠




### 오류 상태 코드

HTTP 상태 코드는 HTTP 요청의 결과를 전달하는 표준화된 숫자 응답입니다. 자세한 내용은 [Mozilla의 HTTP 상태 코드 문서](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) 또는 [HTTP Cats](https://http.cat/)를 참조하세요. 각 Frame.io API 엔드포인트는 예상되는 성공 상태 코드를 지정합니다. 일반적으로 `200(OK)`, `201(Created)` 또는 `204(No Content)`입니다. 이러한 특정 코드를 확인하거나 코드가 200-299 범위 내에 있는지 확인하여 성공 여부를 확인할 수 있습니다. 399 이상의 상태 코드는 오류를 나타냅니다. 대부분의 API 오류는 4XX 코드(400-499)를 반환하므로 클라이언트 측 문제를 나타냅니다. 이 범위를 벗어나는 오류는 일반적으로 디바이스와 저희 서비스 사이의 네트워크 인프라에서 발생합니다. 단, 저희 서버 내에 예기치 않은 문제가 있음을 나타내는 `500(Internal Server Error)`은 예외입니다. 마찬가지로 `404(Not Found)` 응답은 4XX 코드임에도 불구하고 백엔드가 아닌 중간 서비스에 의해 생성될 수 있습니다.

예기치 않은 상태 코드가 발생하면 저희 팀에 알려주세요.





### 오류 페이로드 스키마





Frame.io는 두 가지 형식으로 오류 세부 정보를 반환합니다. *simple* 및 *detailed*. 오류 처리 로직은 두 형식을 모두 수용해야 합니다.





### 단순 오류 스키마

다음은 잘못된 `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'
```





Response:





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





Response:





```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 오류가 나와 있습니다. I/O 작업, 네트워킹 라이브러리, 또는 AWS에서 비 API 오류가 발생하는 경우, 일시적인 상태로 인해 발생할 수 있는 오류는 재시도를 고려해 보세요. 네트워크 정체, 일시적인 서버 중단, 또는 패킷 손실은 일반적으로 재시도가 필요합니다. 대부분의 네트워킹 라이브러리는 요청이 너무 오래 걸리면 `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="백오프 지터">
  동일한 오류 조건에서 복구되는 여러 디바이스 간의 요청 동기화를 방지하기 위해 백오프 타이밍에 임의성(지터)을 추가하는 것이 좋습니다. 이는 서비스 중단 후 수많은 디바이스가 동시에 재시도할 때 발생하는 [Thundering Herd 문제](https://medium.com/@venkteshsubramaniam/the-thundering-herd-distributed-systems-rate-limiting-9128d20e1f00)를 완화하는 데 도움이 됩니다. 0과 계산된 지연 시간의 절반 사이에 랜덤 오프셋을 추가하는 것이 좋은 방법입니다. `math.rand(0, delay // 2)`.
</Info>


지수 백오프는 속도 제한 오류에 필수적이지만, 일반적으로 네트워크 및 I/O 장애를 처리하는 데에도 유용합니다. 이 방식을 사용하면 재시도로 인한 추가 부하 없이 일시적인 리소스 제약 문제를 해결할 수 있습니다.





### 연결 끊김 상태 감지





네트워크 오류가 발생하면 다음과 같은 이유로 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
```





## 오류 표





다음 표는 Frame.io API 오류를 분류하고 처리 지침을 제공합니다. 각 열이 의미하는 바는 다음과 같습니다.

`message`: 오류 페이로드 메시지 식별자 `http code`: HTTP 상태 코드 `error type`: 개념적 오류 범주([descriptions](#descriptions) 섹션에 자세히 설명됨) `schema`: 오류 페이로드 형식([simple](#simple-error-schema) 또는 [detailed](#detailed-error-schema)) `retry`: 재시도 권장 사항(여러 번 시도할 경우 `yes`, 한 번 시도할 경우 `once`, 치명적인 오류의 경우 `no`) 별표(*)는 [descriptions](#descriptions) 섹션에 자세히 설명된 특별 고려 사항을 나타냅니다.

### Frame.io 오류 메시지




| 메시지 | 오류 유형 | HTTP 코드 | 스키마 | 다시 시도 |
| ----------------------------- | ---------------------------------------------------------- | ------------------ | ----------------- | ------- |
| &quot;access_denied&quot; | [AccessDenied](#accessdenied) | 401 | [simple](#simple-error-schema) | once |
| &quot;authorization_pending&quot; | [AuthorizationPending](#authorizationpending) | 400 | [simple](#simple-error-schema) | 네 |
| &quot;Channel Paused&quot; | [ChannelPaused](#channelpaused) | 409 | [simple](#simple-error-schema) | no |
| &quot;expired_token&quot; | [ExpiredToken](#expiredtoken) | 400 | [simple](#simple-error-schema) | no |
| &quot;Invalid Argument&quot; | [InvalidArgument](#invalidargument) | 422 | [detailed](#detailed-error-schema) | no |
| &quot;invalid_client&quot; | [InvalidClient](#invalidclient) | 400 | [simple](#simple-error-schema) | no |
| &quot;Invalid client version&quot; | [InvalidClientVersion](#invalidclientversion) | 400 | [simple](#simple-error-schema) | no |
| &quot;invalid_grant&quot; | [InvalidGrant](#invalidgrant) | 400 | [simple](#simple-error-schema) | no |
| &quot;invalid_request&quot; | [InvalidRequest](#invalidrequest) | 400 | [simple](#simple-error-schema) | once |
| &quot;Not Authorized&quot; | [UnauthorizedClient](#unauthorizedclient) | 401 | [detailed](#detailed-error-schema) | no |
| &quot;slow_down&quot; | [SlowDown](#slowdown) | 400 | [simple](#simple-error-schema) | 네 |
| &quot;unauthorized_client&quot; | [UnauthorizedClient](#unauthorizedclient) | 401 | [simple](#simple-error-schema) | yes* |




### Frame.io 상태 코드




| HTTP 코드 | 오류 유형 | 다시 시도 |
| --------------- | ------------------------------------------------------ | ------- |
| 400 | [InvalidRequest](#invalidrequest) | once |
| 401 | [UnauthorizedClient](#unauthorizedclient) | no |
| 422 | [InvalidContentType](#invalidcontenttype) | no |
| 429 | [SlowDown](#slowdown) | 네 |
| 500 | [InternalServerError](#internalservererror) | 네 |




### AWS 오류

자세한 설명은 [AWS 설명서를 참조](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ErrorCodeList)하세요.
| 오류 | 다시 시도 |
| ------------------------- | ------- |
| InternalError | 네 |
| OperationAborted | 네 |
| RequestTimeout | 네 |
| ServiceUnavailable | 네 |
| SlowDown | 네 |
| [기타 모든 오류] | once |



<Info title="유사한 AWS 오류 구문 분석">
  AWS의 `SlowDown` 및 `ServiceUnavailable`은 모두 요청 속도 문제를 나타내며 Frame.io의 `SlowDown` 오류와 유사하게 취급하여 지수 백오프를 구현할 수 있습니다. 마찬가지로 AWS의 `InternalError`는 개념적으로 저희 API의 `InternalServerError`에 해당합니다.
</Info>


### 설명





#### AccessDenied





사용자가 디바이스 페어링 중 인증을 거부할 때 반환됩니다.





#### AuthorizationPending

사용자가 아직 디바이스 페어링 코드를 입력하지 않았음을 나타냅니다. 디바이스 코드 응답에 지정된 `interval` 기간이 지난 후 폴링을 계속하세요.

#### ChannelPaused





에셋이 생성될 때 디바이스 채널이 일시 중지되었습니다. *이 에셋을 다시 업로드하려고 시도하지 마세요.*





#### ExpiredToken





디바이스 페어링 코드가 만료되었습니다. 새 코드를 생성하고 페어링 프로세스를 다시 시작하세요.





#### InternalServerError

예기치 않은 백엔드 문제를 나타냅니다. 한 번 재시도하고, 조사할 수 있도록 `500` 오류를 저희 팀에 보고해 주세요. 일부 알려진 문제는 `InvalidRequest`를 반환해야 할 때 `500` 오류를 반환합니다.
* 존재하지 않는 디바이스 채널에 업로드 시도
* 잘못된 사용자 지정 청크 수 요청




#### 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) 가이드를 검토해 주세요. [인증 및 권한 부여 과정](./implementing-c2c-authentication-and-authorization)에서 획득한 `access_token`이 필요합니다. 이 가이드는 [기본 업로드 가이드](./how-to-basic-upload) 및 [고급 업로드 가이드](./how-to-advanced-uploads)를 바탕으로 합니다.