Errors

Every error response, from every endpoint, uses the same envelope. This includes unknown routes, malformed JSON and server crashes.

{
  "error": {
    "code": "mau_cap_exceeded",
    "message": "This workspace has reached its plan's monthly active users.",
    "doc_url": "https://poolse.dev/docs/api/errors#mau_cap_exceeded",
    "details": { "count": 1000, "cap": 1000 }
  }
}
FieldTypeDescription
error.codestringMachine-readable. Switch on this.
error.messagestringHuman-readable explanation. Log it. It is not meant for end-users and its wording can change.
error.doc_urlstringA link to this code's section on this page.
error.detailsobject, optionalExtra data. For validation_failed, a map of field name to a list of messages. For quota codes, the numbers involved.

Retrying

  • 5xx: retry with exponential backoff and jitter.
  • 429: wait for Retry-After (seconds) or details.retry_after_ms, then retry.
  • Other 4xx: do not retry unchanged. Fix the request, the credential or the plan.

Use an Idempotency-Key so that a retried POST does not create a second resource.

Codes by status

StatusCodes
400bad_request, invalid_parameter, invalid_cursor, invalid_token, missing_token
401missing_authorization, malformed_authorization, invalid_api_key, revoked_api_key, invalid_user_token, token_revoked, user_blocked, unauthorized
402mau_cap_exceeded, user_cap_exceeded, attachment_too_large, attachment_total_cap_exceeded, webhook_count_cap_exceeded, webhooks_not_in_plan, subscription_inactive, call_minutes_exhausted
403forbidden, cross_tenant, attachments_not_in_plan
404not_found, user_not_found, message_not_found, no_live_call
406not_acceptable
409message_deleted, call_full, call_not_answered, read_receipts_fixed
410call_finished
413payload_too_large
422validation_failed, byte_size_too_large, member_add_failed, send_failed, edit_failed, reaction_failed, user_scope_required, upload_request_failed, create_failed, replay_failed
429rate_limited, lazy_provision_rate_limited
5xxinternal_error, storage_error, sfu_not_configured, auth_pipeline_misconfigured

Request errors

bad_request

400. The request could not be read. Two causes:

  • the body is not valid JSON;
  • an id in the body or query string is not a valid UUID, for example quoted_message_id, reply_to_id, an attachment_ids or mentions entry, message_id on mark read, the reply id on POST /v1/notifications/reply, or cursor on GET /v1/audit_logs. The message is then An id in the request is not a valid UUID.

An id in the path that is not a UUID is 404 not_found instead.

not_found

404. The thing does not exist in your tenant. Also returned for an unknown route, and for any id in the path that is not a UUID. Some endpoints also return it when a conversation exists but you cannot see it: the Call history conversation endpoint does this. Most conversation and message endpoints return 403 forbidden to a non-member instead.

not_acceptable

406. The Accept header asks for a format other than JSON.

payload_too_large

413. The request body is larger than the server accepts.

invalid_parameter

400. A query parameter has the wrong format: conversation_id not a UUID, or since / until not an ISO 8601 time. Returned by the Call history endpoints.

invalid_cursor

400. The cursor passed to a Call history list is not one the server issued.

validation_failed

422. The body failed validation. details maps each field to its problems:

{
  "error": {
    "code": "validation_failed",
    "message": "Request failed validation.",
    "doc_url": "https://poolse.dev/docs/api/errors#validation_failed",
    "details": { "external_id": ["can't be blank"] }
  }
}

Some endpoints return this code without details (for example Devices and Notification actions). The message then says which field is wrong.

byte_size_too_large

422. POST /v1/attachments/upload-url with a byte_size over the hard limit of 100 MiB, whatever your plan.

member_add_failed

422. Adding a member failed for a reason other than validation: a direct conversation already has 2 members, or a group has reached its member_limit. The message names the user.

send_failed

422. A message could not be sent. The most common causes: reply_to_id or quoted_message_id names no message, the quoted message is in another conversation, or an attachment id is unknown, already used on another message, or was uploaded by someone else. When an attachment's stored file is larger than the byte_size declared for it, the message contains attachment_size_mismatch.

edit_failed

422. A message edit failed for a reason other than validation.

reaction_failed

422. Adding or removing a reaction failed for a reason other than validation.

user_scope_required

422. The action needs an end-user (reactions, mark read). It is not returned to normal JWT callers.

upload_request_failed

422. An attachment upload URL could not be issued for a reason not covered by another code.

create_failed

422. A webhook could not be created for a reason not covered by another code.

replay_failed

422. A webhook delivery could not be replayed.

Authentication errors

missing_authorization

401. No Authorization header.

malformed_authorization

401. The Authorization header is not Bearer <credential>.

invalid_api_key

401. The API key is malformed, unknown, or its secret does not match.

revoked_api_key

401. The API key was revoked in the dashboard.

invalid_user_token

401. The end-user JWT has a bad signature, has expired, or its user no longer exists. Mint a new one.

token_revoked

401. The end-user JWT was revoked with POST /admin/v1/tokens/revoke.

user_blocked

401. The user behind the JWT or notification reply grant has is_blocked: true. (The call grant endpoints report a blocked user as unauthorized instead.) See Users.

unauthorized

401. A call grant, call-control token or reply grant is missing, expired, of the wrong kind, or for a different call. Returned by the grant endpoints on Calls and Notification actions. On the call grant endpoints (answer, decline, hang up, reconnect, ring) it is also returned, with the message User is blocked., when the user behind the credential (ring grant, control token, or the JWT decline accepts) is blocked, even for a grant issued before the block.

invalid_token

400. POST /admin/v1/tokens/revoke was given a token that does not verify, has already expired, or lacks the required claims.

missing_token

400. POST /admin/v1/tokens/revoke was called without a token field.

Permission errors

forbidden

403. You are authenticated but not allowed. The causes:

  • the API key lacks the scope the endpoint needs (the message names it);
  • the user is not a member of the conversation, or lacks the role the action needs;
  • a message send while your subscription is inactive.

cross_tenant

403. POST /admin/v1/tokens/revoke was given a token from another tenant.

attachments_not_in_plan

403. Your plan does not include attachments.

Plan errors

These are quotas tied to your plan. Upgrading lifts them. See Limits & quotas.

mau_cap_exceeded

402. The workspace already has its plan's number of monthly active users this month, and this user has not been active yet this month. Returned by any request made with an end-user JWT, and by the call-grant and notification-action routes; the socket refuses to connect for the same reason. Users already active this month are never refused. details: { count, cap }. See Limits & quotas.

user_cap_exceeded

402. Creating a user would exceed a cap on how many users a workspace may have. No current plan has such a cap (plans are sized by monthly active users instead), so you will not see it today. It would come from POST /v1/users, POST /v1/conversations or POST /v1/conversations/:conversation_id/members creating users, with details: { count, cap }.

attachment_too_large

402. The file is larger than your plan's per-file limit for hosted storage (Free 1 MB, Pro and Scale 25 MB). Not returned while the workspace has its own bucket connected. details: { bytes, cap }.

attachment_total_cap_exceeded

402. The file would push your tenant's attachments in hosted storage over your plan's total (Free 300 MB, Pro and Scale 500 MB). Not returned while the workspace has its own bucket connected; see Bring your own storage. details: { used, would_use, cap }.

webhook_count_cap_exceeded

402. You already have as many webhooks as your plan allows. details: { count, cap }.

webhooks_not_in_plan

402. Your plan does not include webhooks.

subscription_inactive

402. Your subscription is past due or cancelled. Returned when creating webhooks and requesting attachment uploads.

State errors

user_not_found

404. POST /v1/users/:user_id/tokens with a user_id that is not a user in your tenant. user_id is the poolse id, not your external_id.

message_not_found

404. POST /v1/conversations/:conversation_id/read with a message_id that is not in that conversation.

no_live_call

404. GET /v1/conversations/:conversation_id/call when nothing is live on the conversation. 409 from POST /v1/conversations/:conversation_id/call-token for the same reason: start a call or room first.

call_minutes_exhausted

402. The workspace has used this month's call minutes for the medium. Returned when placing a call, starting or joining a room, joining a live call, ringing more people in, or fetching a media token. details: { media, used, cap }, media being audio or video. Calls already connected carry on. See Limits & quotas.

message_deleted

409. Editing or reacting to a deleted message.

call_full

409. Joining would take the call past the workspace's people-per-call limit. Someone already in the call can always rejoin.

call_not_answered

409. POST /v1/calls/:id/ring on a call nobody has answered yet.

read_receipts_fixed

409. PATCH /v1/me with read_receipts while the workspace does not let users choose. See Current user.

call_finished

410. The call has ended, or the server no longer knows the call.

Rate limits

These are abuse limits, the same on every plan. They reset on their own.

rate_limited

429. Too many POST /v1/users/:user_id/tokens calls for this API key: the limit is 60 per minute. The Retry-After header gives the seconds to wait.

lazy_provision_rate_limited

429. The end-user referenced more than 50 unknown external_ids within an hour, through member_external_ids or external_ids. The Retry-After header gives the seconds to wait, and details: { retry_after_ms } the same in milliseconds.

Server errors

internal_error

500. Something failed on the server. Retry with backoff.

storage_error

502. The storage bucket did not issue a download URL. Retry. With your own bucket connected, also check that the stored key still works (Test connection on the dashboard's Storage page).

sfu_not_configured

503. This deployment has no media server, so call endpoints cannot issue media credentials.

auth_pipeline_misconfigured

500. A server-side configuration bug. Report it if you see it.

Errors · API · poolse docs