API errors and rate limits
Every error code the API can return, what each one means, and the rate limits and plan limits that apply to a key.
The error shape
Every failure comes back in the same envelope, with the HTTP status telling you the broad category and code telling you exactly what happened:
{
"error": {
"code": "invalid_card",
"message": "cards[3].target must be at most 2000 characters.",
"param": "cards[3].target",
"request_id": "req_8fKd2mXp4LvNc7Bh"
}
}
codeis stable and safe to branch on in your script.messageis written for a human and may be reworded over time, so do not match on it.paramnames the exact field, including its index in a batch.request_idalso appears in our logs. Quote it if you contact support and we can find the exact request.
Error codes
Authentication - 401
| Code | Meaning |
|---|---|
invalid_api_key | The key is missing, malformed, or not one we issued |
revoked_api_key | The key was revoked. Create a new one in settings |
expired_api_key | The key passed its expiry date |
Permissions - 403
| Code | Meaning |
|---|---|
insufficient_scope | The key lacks the scope this endpoint needs. The message names it |
deck_not_owned | The deck exists but belongs to another account |
account_pending_deletion | The account is scheduled for deletion |
Not found - 404
| Code | Meaning |
|---|---|
deck_not_found | No deck with that doc_id |
card_not_found | No card with that id. param names which entry in a batch |
card_not_in_deck | The card exists but is in a different deck than the one in the URL |
custom_field_not_found | No custom field with that id on the deck |
not_found | No such endpoint |
unknown_api_version | The version in the URL does not exist. The message lists the ones that do |
Bad request - 400
| Code | Meaning |
|---|---|
invalid_request | A required field is missing, or a value is the wrong type or too long |
invalid_card | One card in the batch is invalid. param names which |
invalid_custom_field | A custom field key is not defined on the deck, or its value is invalid |
invalid_language | A language code is not in a recognised form |
batch_too_large | More than 100 cards in one request |
field_not_patchable | You tried to change a field the API does not let you change |
invalid_field_type | A custom field's type is not text or list |
too_many_custom_fields | The deck already has 50 custom fields |
deck_not_empty | A language change was attempted on a deck that already has cards |
invalid_cursor | The pagination cursor is not one we issued |
unknown_field / unknown_parameter | A field or query parameter we do not recognise |
invalid_json | The body is not valid JSON |
idempotency_key_reuse | The same Idempotency-Key was used for a different request |
Limits - 402, 413, 429
| Code | Status | Meaning |
|---|---|---|
quota_exceeded | 402 | You reached your plan's deck or card limit |
payload_too_large | 413 | The request body is over 256 KB |
rate_limited | 429 | Too many requests. See the rate limits below |
Conflicts - 409
| Code | Status | Meaning |
|---|---|---|
custom_field_exists | 409 | The deck already has a custom field with that name. The message gives you its id |
Everything else - 415, 503, 500
| Code | Status | Meaning |
|---|---|---|
unsupported_media_type | 415 | The body was not sent as application/json |
method_not_allowed | 405 | Wrong HTTP method for that path |
temporarily_unavailable | 503 | The API is paused for maintenance. Retry after the interval in Retry-After |
internal | 500 | Something broke on our side. Quote the request_id |
Nothing is written on a 503, and the request is safe to retry.
Rate limits
Limits are counted per key, not per account.
- 60 requests per minute
- 5000 requests per day
Requests are not all counted equally. A read counts as one; a write counts for more, because of the work it sets off behind it. Creating a deck, editing or deleting a batch of cards, and deleting a custom field are the heaviest - the last two because they each ripple out into your vocabulary and, for a card edit, into regenerated audio.
The practical consequence is that a batch request is much cheaper than the same work split into separate requests. Editing 100 cards in one call costs a fraction of editing them one at a time.
Every response, not just a 429, carries the current state:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed in the current window |
X-RateLimit-Remaining | How many you have left |
X-RateLimit-Reset | When the window resets, as a Unix timestamp in seconds |
On a 429 you also get Retry-After, in seconds.
Backing off
On a 429, wait for the interval in Retry-After and try again. For 500 and 503, use exponential backoff with a few retries, and pair it with an Idempotency-Key on writes so a retry after a timeout cannot duplicate anything.
Do not retry a 4xx other than 429: the request will fail the same way until you change it.
Plan limits
Rate limits cap how fast you can call the API. Plan limits cap how much you can store, and are the same limits the web app enforces:
| Free | Premium | |
|---|---|---|
| Decks | 100 | Unlimited |
| Cards | 10,000 | Unlimited |
Crossing one returns 402 quota_exceeded, with a message saying how many you have and how many you can still add. Nothing is written when a request is rejected for quota.
See free plan limits for the full picture, or Yalango Premium to lift them.