Error Codes
Every REST error code with HTTP status, category, SDK error number, retryability and cause, generated from the public error-code definition.
How to handle errors
- Start with the HTTP status:
401/403check credentials, service activation and ban rules;404confirm the resource;
409 query the current state first; 415 add Content-Type; 429 back off; 5xx back off and keep the X-Request-Id.
- Then branch on
code: it is the stable machine code and is safe for retry decisions and alert classification. - On the SDK side branch on
sdkCode, so code migrating from Agora can reuse existing checks.
Error envelope
Every failed REST call returns the same envelope. code is the stable machine code (see the table below), message and reason are human-readable reasons (covering both the Agora RTC REST habit of reading message and the cloud-recording habit of reading reason), and sdkCode is the numeric error number exposed to clients.
{
"code": "recording_quota_exceeded",
"message": "recording concurrency limit reached",
"reason": "recording concurrency limit reached",
"sdkCode": 22,
"details": {
"limit": 5,
"active": 5
}
}The response always echoes X-Request-Id, which is the same id used in logs, callbacks and audit records; include it when troubleshooting.
Generated at build time from the public error-code definition: 42 error codes, plus the success entry ok (HTTP 200) which is not counted.
Full error-code table
retryable is currently always false: the field is advisory in the error-code definition and is not yet set per error code. The table keeps the original values (the retryable column); follow the HTTP-semantic guidance in the “Retry guidance” column, and see FAQ and limits.
| code | HTTP | sdkCode | SDK constant | Category | retryable | Meaning | Retry guidance |
|---|---|---|---|---|---|---|---|
invalid_channel_name | 400 | -102 | ERR_INVALID_CHANNEL_NAME | Validation | false | invalid channel name | Fix the request before retrying |
invalid_parameter | 400 | -2 | ERR_INVALID_ARGUMENT | Validation | false | invalid parameter | Fix the request before retrying |
RTC_TOKEN_INVALID_REQUEST | 400 | -2 | ERR_INVALID_ARGUMENT | Validation | false | invalid RTC token request | Fix the request before retrying |
RTC_UID_MAPPING_INVALID_REQUEST | 400 | -2 | ERR_INVALID_ARGUMENT | Validation | false | invalid RTC uid mapping request | Fix the request before retrying |
invalid_appid | 401 | -101 | ERR_INVALID_APP_ID | Authentication | false | invalid appid | Check credentials or refresh the token; do not retry unchanged |
invalid_credentials | 401 | -110 | ERR_INVALID_TOKEN | Authentication | false | invalid credentials | Check credentials or refresh the token; do not retry unchanged |
invalid_token | 401 | -110 | ERR_INVALID_TOKEN | Authentication | false | invalid token | Check credentials or refresh the token; do not retry unchanged |
missing_authorization | 401 | -110 | ERR_INVALID_TOKEN | Authentication | false | missing Authorization header | Check credentials or refresh the token; do not retry unchanged |
token_channel_mismatch | 401 | -110 | ERR_INVALID_TOKEN | Authentication | false | token does not match channel | Check credentials or refresh the token; do not retry unchanged |
token_expired | 401 | -109 | ERR_TOKEN_EXPIRED | Authentication | false | token expired | Check credentials or refresh the token; do not retry unchanged |
token_not_yet_valid | 401 | -110 | ERR_INVALID_TOKEN | Authentication | false | token not yet valid | Check credentials or refresh the token; do not retry unchanged |
token_privilege_expired | 401 | -109 | ERR_TOKEN_EXPIRED | Authentication | false | token privilege expired | Check credentials or refresh the token; do not retry unchanged |
token_uid_mismatch | 401 | -110 | ERR_INVALID_TOKEN | Authentication | false | token does not match uid | Check credentials or refresh the token; do not retry unchanged |
unauthorized | 401 | -9 | ERR_NO_PERMISSION | Authentication | false | authentication required | Check credentials or refresh the token; do not retry unchanged |
app_disabled | 403 | -101 | ERR_INVALID_APP_ID | Authentication | false | app is disabled | Check ban rules, service activation and key rotation |
banned | 403 | -17 | ERR_JOIN_CHANNEL_REJECTED | Authentication | false | user is banned by a kicking rule | Check ban rules, service activation and key rotation |
join_rejected | 403 | -17 | ERR_JOIN_CHANNEL_REJECTED | Authentication | false | join channel rejected | Check ban rules, service activation and key rotation |
RTC_TOKEN_DENIED | 403 | -17 | ERR_JOIN_CHANNEL_REJECTED | Authentication | false | RTC token denied | Check ban rules, service activation and key rotation |
service_not_enabled | 403 | -1 | ERR_FAILED | Authentication | false | service is not activated for this project | Check ban rules, service activation and key rotation |
channel_not_found | 404 | -113 | ERR_NOT_IN_CHANNEL | Not found | false | channel not found | Confirm the resource exists (channel / job / rule) |
collaborator_not_found | 404 | -1 | ERR_FAILED | Not found | false | collaborator not found | Confirm the resource exists (channel / job / rule) |
outbox_not_found | 404 | -1 | ERR_FAILED | Not found | false | outbox event not found | Confirm the resource exists (channel / job / rule) |
project_not_found | 404 | -1 | ERR_FAILED | Not found | false | project not found | Confirm the resource exists (channel / job / rule) |
recording_not_found | 404 | -1 | ERR_FAILED | Not found | false | recording task not found | Confirm the resource exists (channel / job / rule) |
streaming_not_found | 404 | -1 | ERR_FAILED | Not found | false | streaming task not found | Confirm the resource exists (channel / job / rule) |
user_not_found | 404 | -113 | ERR_NOT_IN_CHANNEL | Not found | false | user not in channel | Confirm the resource exists (channel / job / rule) |
webhook_not_found | 404 | -1 | ERR_FAILED | Not found | false | webhook endpoint not found | Confirm the resource exists (channel / job / rule) |
already_in_channel | 409 | -19 | ERR_ALREADY_IN_USE | Conflict | false | participant already in channel | Query the current state before deciding to retry |
project_exists | 409 | -1 | ERR_FAILED | Conflict | false | project already exists | Query the current state before deciding to retry |
recording_invalid_state | 409 | -8 | ERR_INVALID_STATE | Conflict | false | recording task is not in a valid state for this operation | Query the current state before deciding to retry |
streaming_invalid_state | 409 | -8 | ERR_INVALID_STATE | Conflict | false | streaming task is not in a valid state for this operation | Query the current state before deciding to retry |
unsupported_media_type | 415 | -4 | ERR_NOT_SUPPORTED | Validation | false | unsupported media type | Set Content-Type: application/json |
rate_limited | 429 | -12 | — | Rate limit | true | too many requests | Retry with exponential backoff (quota or rate limit) |
recording_quota_exceeded | 429 | -22 | — | Quota | true | recording concurrency quota exceeded | Retry with exponential backoff (quota or rate limit) |
streaming_quota_exceeded | 429 | -22 | — | Quota | true | streaming concurrency quota exceeded | Retry with exponential backoff (quota or rate limit) |
internal_error | 500 | -1 | ERR_FAILED | Internal error | true | internal server error | Retry with backoff; if it persists, report it with the X-Request-Id |
recording_upstream_error | 502 | -1 | ERR_FAILED | Upstream dependency | true | egress upstream error | Upstream (Egress/RTMP) failure; retry with backoff |
streaming_upstream_error | 502 | -1 | ERR_FAILED | Upstream dependency | true | rtmp upstream error | Upstream (Egress/RTMP) failure; retry with backoff |
no_server_resources | 503 | -103 | ERR_NO_SERVER_RESOURCES | Upstream dependency | true | no server resources | Dependency unavailable; retry with backoff |
RTC_PROVIDER_DISABLED | 503 | -1 | ERR_FAILED | Upstream dependency | true | RTC provider is disabled by configuration | Dependency unavailable; retry with backoff |
RTC_PROVIDER_UNAVAILABLE | 503 | -111 | ERR_CONNECTION_INTERRUPTED | Upstream dependency | true | RTC provider unavailable | Dependency unavailable; retry with backoff |
upstream_unavailable | 503 | -111 | ERR_CONNECTION_INTERRUPTED | Upstream dependency | true | upstream service unavailable | Dependency unavailable; retry with backoff |
Error numbers are stored as positive values in the interface definition; the SDK facade exposes the negative constant with the same absolute value through SdkErrorCode (for example invalid_appid → 101 → -101 ERR_INVALID_APP_ID). Same meaning, different sign: compare by absolute value across layers.
Agora error numbers visible to the SDK
The SDK facade (@nimeet/rtc-web and the equivalent platform wrappers) reports failures as { code, sdkCode, message, retryable }, where sdkCode uses the negative values below so applications migrating from Agora can keep their existing error branches. REST never returns negative values; it aligns through the positive sdkCode in the table above.
| sdkCode | Constant | Meaning | Matching REST error code |
|---|---|---|---|
-1 | ERR_FAILED | Generic failure (unmapped fallback) | No direct REST equivalent (connection layer) |
-2 | ERR_INVALID_ARGUMENT | Invalid argument | No direct REST equivalent (connection layer) |
-3 | ERR_NOT_READY | SDK is not ready | No direct REST equivalent (connection layer) |
-4 | ERR_NOT_SUPPORTED | Capability not supported on this platform | No direct REST equivalent (connection layer) |
-5 | ERR_REFUSED | Call refused | No direct REST equivalent (connection layer) |
-7 | ERR_NOT_INITIALIZED | Not initialised | No direct REST equivalent (connection layer) |
-8 | ERR_INVALID_STATE | Operation not allowed in the current state | No direct REST equivalent (connection layer) |
-9 | ERR_NO_PERMISSION | No permission | No direct REST equivalent (connection layer) |
-10 | ERR_TIMEDOUT | Timed out | No direct REST equivalent (connection layer) |
-17 | ERR_JOIN_CHANNEL_REJECTED | Join rejected | No direct REST equivalent (connection layer) |
-18 | ERR_LEAVE_CHANNEL_REJECTED | Leave rejected | No direct REST equivalent (connection layer) |
-19 | ERR_ALREADY_IN_USE | Already in the channel / resource in use | No direct REST equivalent (connection layer) |
-101 | ERR_INVALID_APP_ID | Invalid App ID | No direct REST equivalent (connection layer) |
-102 | ERR_INVALID_CHANNEL_NAME | Invalid channel name | No direct REST equivalent (connection layer) |
-103 | ERR_NO_SERVER_RESOURCES | Insufficient server resources | No direct REST equivalent (connection layer) |
-109 | ERR_TOKEN_EXPIRED | Token expired | No direct REST equivalent (connection layer) |
-110 | ERR_INVALID_TOKEN | Invalid token | No direct REST equivalent (connection layer) |
-111 | ERR_CONNECTION_INTERRUPTED | Connection interrupted (auto reconnect) | No direct REST equivalent (connection layer) |
-112 | ERR_CONNECTION_LOST | Connection lost (rejoin required) | No direct REST equivalent (connection layer) |
-113 | ERR_NOT_IN_CHANNEL | Not in a channel | No direct REST equivalent (connection layer) |
The facade adds codes that do not come from Agora: RTC_RECONNECTING, RTC_PUBLISH_DENIED and RTC_DEVICE_NOT_FOUND. Disconnect reason -111 reconnects automatically; -112 requires joining again.