Introduction
This guide covers error handling when interacting with the C2C API. Gracefully handling HTTP errors is an essential component of a robust integration with any third-party service.
Types of Errors
Errors in your integration can originate from various sources, which we can categorize into four main groups:
- I/O Errors: Originate from hardware operations on your device, such as failed read/write operations
- Application Errors: Arise from issues within your application code
- Network Errors: Occur within the networking stack and are communicated by your networking library
- API Errors: Generated by Frame.io's backend services
Each error category requires specific handling considerations. This guide primarily focuses on API errors, though we'll address general strategies for the other categories as well.
How API Errors are Returned
Frame.io's API communicates errors through two primary mechanisms:
- Status Codes: HTTP error codes that indicate the nature of the problem
- Error Messages: Payload content that provides additional error details, especially when multiple error conditions share the same status code
Error Status Codes
HTTP status codes are standardized numerical responses that communicate the outcome of an HTTP request. For more information, see Mozilla's HTTP status code documentation or HTTP Cats for a more visual approach.
Each Frame.io API endpoint specifies an expected success status code—typically 200 (OK)
, 201 (Created)
, or 204 (No Content)
. You can verify success either by checking for these specific codes or by confirming the code falls within the 200-299 range.
Status codes above 399 indicate errors. Most API errors return 4XX codes (400-499), signifying client-side issues. Errors outside this range typically originate from network infrastructure between your device and our service, with the notable exception of 500 (Internal Server Error)
, which indicates an unexpected issue within our server.
Similarly, a 404 (Not Found)
response might be generated by intermediate services rather than our backend, despite being a 4XX code.
If you encounter unexpected status codes, please notify our team.
Error Payload Schemas
Frame.io returns error details in two formats: simple and detailed. Your error handling logic should accommodate both formats.
Simple Error Schema
Here's an example of a failed request with an incorrect client_secret
:
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:
HTTP/2 400
...
{"error":"invalid_client"}
The simple schema contains just a single field for error identification.
Detailed Error Schema
For comparison, here's a request without proper authorization:
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:
{
"code": 409,
"errors": [
{
"code": 409,
"detail": "The channel you're uploading from is currently paused.",
"status": 409,
"title": "Channel Paused"
}
],
"message": "Channel Paused"
}
Detailed errors contain a message
field that identifies the error type.
Determining Error Type
When handling Frame.io errors, first check for an error payload and then fall back to the HTTP status code if no payload is present.
Here's a basic error handling implementation:
# 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)
The error lookup tables referenced in this example are provided at the end of this guide.
AWS Errors
When uploading file chunks, you interact directly with AWS S3, which has its own error format. See AWS's common error documentation for details. As a general rule, non-fatal AWS errors should be retried at least once.
AWS errors are returned as 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>
The Code
element identifies the error type.
Error Tables
The following tables categorize Frame.io API errors and provide handling guidance. Here's what each column represents:
message
: The error payload message identifier
http code
: The HTTP status code
error type
: A conceptual error category (detailed in the descriptions section)
schema
: The error payload format (simple or detailed)
retry
: Retry recommendation (yes
for multiple attempts, once
for a single retry, no
for fatal errors)
Asterisks (*) indicate special considerations detailed in the descriptions section.
Frame.io Status Codes
HTTP Code | Error Type | Retry |
---|---|---|
400 | InvalidRequest | once |
401 | UnauthorizedClient | no |
422 | InvalidContentType | no |
429 | SlowDown | yes |
500 | InternalServerError | yes |
AWS Errors
See AWS's documentation for detailed descriptions.
Error | Retry |
---|---|
InternalError | yes |
OperationAborted | yes |
RequestTimeout | yes |
ServiceUnavailable | yes |
SlowDown | yes |
[All Other Errors] | once |
Both SlowDown
and ServiceUnavailable
from AWS indicate request rate issues and can be treated similar to Frame.io's SlowDown
error, implementing exponential backoff. Similarly, AWS's InternalError
corresponds conceptually to InternalServerError
in our API.
Descriptions
AccessDenied
Returned when a user declines authorization during device pairing.
AuthorizationPending
Indicates a user hasn't yet entered the device pairing code. Continue polling after the interval
period specified in the device code response.
ChannelPaused
The device channel was paused when the asset was created. Do not attempt to upload this asset again.
ExpiredToken
The device pairing code has expired. Generate a new code and restart the pairing process.
InternalServerError
Indicates an unexpected backend issue. Retry once, and please report 500
errors to our team for investigation.
Note that some known issues return 500
errors when they should return InvalidRequest
:
- Attempting to upload to a non-existent device channel
- Requesting an invalid custom chunk count
InvalidArgument
A payload parameter contained an invalid value. Verify the parameter values match API expectations.
InvalidContentType
The request's Content-Type
header is unsupported. The API generally accepts:
form/multipart
(authorization endpoints only)application/x-www-form-urlencoded
(all endpoints)application/json
(non-authorization endpoints)
InvalidClient
The provided credentials (client_id
, client_secret
, etc.) were not recognized. Verify your integration credentials.
InvalidClientVersion
The x-client-version
header was either duplicated or contains an invalid semantic version.
InvalidGrant
The authorization grant type is invalid. Review the authorization guides for correct values.
InvalidRequest
The request parameters or payload format is incorrect. Verify field names and value formats.
If received during token refresh, your refresh token has expired and you must restart the authorization process.
SlowDown
You've exceeded request rate limits. Implement exponential backoff for subsequent requests. Note that making multiple device code requests on the same TCP connection can trigger this error—create new connections for each pairing request.
UnauthorizedClient
Typically indicates an expired or missing access_token
. If you receive this error, refresh your token before retrying.
If encountered during token refresh, you must restart the authorization process and prompt the user to reconnect.
This error can also occur when accessing resources outside your device's authorization scope or when a project has disabled C2C devices. Verify that you've requested appropriate scopes during authorization.
If this error occurs during token refresh, the entire authorization process must be restarted with user intervention.