All billing-API errors are JSON: {"error": "<message>"}.
| HTTP |
Cause |
Typical message |
Retry? |
400 Bad Request |
Invalid request: malformed JSON, unknown field, value out of range, more than 5,000 usage records, revoking your own key |
invalid request: … |
No — fix the request |
401 Unauthorized |
Missing, malformed, unknown or revoked key, or the wrong kind of key (relay key on a backend route, backend key on /v1/usage) |
invalid api key |
No |
402 Payment Required |
Tenant has never paid |
payment required: add credit to activate this tenant |
After paying |
402 Payment Required |
Prepaid balance spent |
insufficient credit: top up to admit new sessions |
After topping up |
403 Forbidden |
Tenant suspended, archived or deleted |
tenant is not active |
No — contact support |
404 Not Found |
No such key or tenant |
not found |
No |
409 Conflict |
Already exists, or a tenant status change that is not allowed |
already exists / tenant status cannot change that way |
No |
429 Too Many Requests |
More than 5 signups per hour from one address |
too many signups from this address |
Later |
500 Internal Server Error |
MOQOM bug |
internal error |
Yes, with backoff |
502 Bad Gateway |
Payment provider (Stripe) returned an error |
Stripe’s message |
Yes, with backoff |
503 Service Unavailable |
Payments not configured on this deployment |
payments are not configured on this deployment |
No |
Authentication failures are deliberately uninformative: you are never told which part of a key
was wrong.
The Join endpoint returns plain-text errors.
| HTTP |
Cause |
400 |
Unreadable body, missing device key, mismatched key and thumbprint, invalid name |
401 |
Unknown credential, or a relay credential |
402 |
Tenant has no credit (never paid, or balance spent) |
403 |
Credential has no publish rights, or permission denied |
404 |
Room not found |
405 |
Method other than POST |
409 |
Room ended, or another precondition failed |
429 |
More than 30 requests per minute for this credential |
503 / 504 |
Temporarily unavailable / deadline exceeded |
500 |
Internal error |
| Code |
Cause |
Retry? |
UNAUTHENTICATED |
No credentials, empty credential, or unknown key |
No |
PERMISSION_DENIED |
Request names another tenant; relay key used on the admin API; actor lacks capability or standing over the target; minting beyond the key’s ceiling |
No |
INVALID_ARGUMENT |
Missing tenant or device_thumbprint; malformed name, CIDR or capability; token lifetime above 15 minutes; changing an immutable policy field |
No |
NOT_FOUND |
No such room, participant or watchdog — also returned for resources in another tenant |
No |
ALREADY_EXISTS |
Room already exists |
No |
FAILED_PRECONDITION |
Room has ended; room requires age verification; tenant has no credit (MintClientToken only) |
After fixing the cause |
CANCELED / DEADLINE_EXCEEDED |
Client cancelled or timed out |
Yes |
UNAVAILABLE |
Transient |
Yes — same idempotency key |
INTERNAL |
MOQOM bug; the message is kept to help diagnosis |
Report it |
| gRPC code |
Go sentinel |
Rust |
NOT_FOUND |
ErrNotFound |
Error::NotFound |
PERMISSION_DENIED |
ErrDenied |
Error::Denied |
UNAUTHENTICATED |
ErrUnauthenticated |
Error::Unauthenticated |
INVALID_ARGUMENT |
ErrInvalid |
Error::Invalid |
ALREADY_EXISTS |
ErrExists |
Error::Exists |
FAILED_PRECONDITION (room ended) |
ErrClosed |
Error::Closed |
UNAVAILABLE |
ErrUnavailable |
Error::Unavailable |
INTERNAL |
ErrInternal |
Error::Internal |
| Operation |
Without credit |
POST /join |
402 |
MintClientToken |
FAILED_PRECONDITION |
Self-hosted relay POST /v1/usage |
200, with "admitted": false |
Moderation (Kick, Ban, ForceMute, …) |
Always works |
| Rooms, events, keys, billing routes |
Always work |
TerminalEvent |
Meaning |
.moderated(code:) |
Kicked, banned or room closed by a moderator, with a moderation code |
.hostEnded |
The host ended the room or broadcast |
.networkFailure |
The network did not come back within the reconnect window |
.tokenExpired |
The token lapsed and was not renewed |
MoqomError.encryptionRequired is not terminal. startBroadcast and startAudio throw it when
the room requires end-to-end encryption and useEncryption has not been called; set a key and
try again. Incoming media dropped for the same reason is reported as
RoomEvent.encryptionRequired(participant). See End-to-end encryption.