yappy docs Portal

Errors and rate limits

The envelope

Every failure, from every endpoint, has the same shape:

json
{
  "error": {
    "code": "missing_permission",
    "message": "You cannot send messages here",
    "details": { "permission": "SEND_MESSAGES" },
    "retryAfter": null
  }
}

Switch on code. Never switch on message: messages are user-facing copy, they get reworded, and they will eventually be localised. details is present only when there is something structured worth saying, and its shape depends on the code.

Codes

400 and 422, the request was wrong

CodeMeans
bad_requestMalformed in a way the schema did not catch
validation_failedA field failed validation. details names the fields
unsupported_media_typeWrong content type
payload_too_largeBody over the limit
unprocessableWell formed, but not a thing that can be done
edit_window_expiredThe message is too old to edit
message_deletedThe target message is gone

401, we do not know who you are

CodeMeans
unauthenticatedMissing or unparseable credential
token_expiredValid token, past its lifetime
token_revokedRotated, logged out, or the bot token was regenerated

A bot token does not expire on a clock. token_revoked on a bot means it was rotated or the application was deleted, so re-read your configuration rather than retrying.

403, we know, and no

CodeMeans
forbiddenNot allowed, without a more specific reason
missing_permissionThe permission bit is absent. details.permission names it
blockedThe other account has blocked this one
privacy_restrictedTheir privacy settings do not allow this
account_suspendedThe account is suspended. Reads still work, writes do not

404, nothing here

CodeMeans
not_foundNo such thing
not_a_memberThe conversation exists but you are not in it

A conversation you are not in usually answers not_found rather than not_a_member or 403. Confirming that a private conversation exists is itself a leak, so non-membership is answered with absence.

409, a conflict with what already is

CodeMeans
conflictThe state does not permit this
already_existsDuplicate of something unique, such as a handle
already_a_memberAlready joined
call_already_endedThe call is over
call_fullThe call is at capacity

429, slow down

CodeMeans
rate_limitedBucket empty. retryAfter is seconds
slow_modeThe conversation has slow mode on. retryAfter is seconds

500 and 503, our fault

CodeMeans
internal_errorUnexpected. Safe to retry with backoff
service_unavailableA dependency is down. Retry with backoff

Rate limits

Limits are token buckets: a burst capacity that refills at a steady rate. Bots are keyed on the bot account, so your limits are yours and are not shared with the humans in a conversation.

ActionBurstSustained
Send a message305 per second
Edit a message202 per second
Delete a message302 per second
Add a reaction5010 per second
Forward a message101 per two seconds
Create an invite101 per minute
Search201 per second

A well behaved bot never sees these. They exist for the loop that gets away from you.

When you do get a 429, both the Retry-After header and error.retryAfter carry the same number of seconds. Wait it out. Retrying immediately consumes capacity you do not have and pushes the recovery further away.

Retrying safely

POST /conversations/:id/messages requires a nonce, and that is what makes a retry safe: replaying the same nonce returns the original message with 200 instead of posting a second copy. Derive the nonce from the logical send, not from the attempt. A fresh random value per attempt turns your retry logic into a duplicate-message generator.

Retry on 429, 500 and 503. Do not retry on 4xx otherwise: the request will fail the same way every time, and the loop is just noise in both our logs.