code. Use this page to decide what your code does with each one.
The error shape
A path under
/api that is not an endpoint returns the same shape with 404 not_found, so your client never has to parse an HTML error page.
The SDKs raise this as an exception: YungleApiError in JavaScript and YungleError in Python, each with status, code, details and retryable.
Error codes
| Code | Status | What to do |
|---|---|---|
unauthorized | 401 | Check the key. Missing, malformed, unknown, revoked and expired all look the same on purpose. |
upgrade_required | 402 | The workspace is on the free plan. Nothing to retry. |
out_of_credit | 402 | API uploads past the monthly allowance are paid from prepaid credit, and there is none left. Top up in the dashboard, then retry. |
insufficient_scope | 403 | The key lacks a scope. details.required names it; create a key that has it. |
not_found | 404 | No such resource, or it belongs to another workspace — deliberately the same answer. Also returned for a path that is not an endpoint. |
invalid_request | 400 | Bad body. details.issues lists field paths and reasons. |
folder_error | 400 | Duplicate or invalid folder name, or too deep. details.code says which. |
conflict | 409 | Already exists — a contact with that email, typically. Usually safe to skip. With details.reason set, it is about your Idempotency-Key instead; see below. |
not_editable | 410 | The transfer has been sent, revoked or expired. Composing is over. |
quota_exceeded | 413 | Storage is full. details carries used and total bytes. Delete something. |
transfer_too_large | 413 | One transfer exceeded the plan ceiling. details.limitBytes is the cap. |
rate_limited | 429 | Back off. Honour the Retry-After header, or throttle on the RateLimit header before you get here. |
email_budget_exhausted | 429 | Daily outbound email or invite budget spent. Nothing was sent and nothing changed. For a transfer, finalize again without recipients to get the link now, or retry tomorrow. |
password_required | 403 | The shared link is password protected. Send password with the request. |
wrong_password | 403 | Wrong password for the shared link. Attempts are budgeted per workspace and per link; after too many you get rate_limited. |
guests_only | 403 | The collection is open to invited guests only. A guest opens it in a browser, signed in; the link alone is not enough. |
link_unavailable | 410 | The shared link has expired, was revoked, or is under a hold. Nothing to retry. |
e2ee_unsupported | 409 | End-to-end encrypted. Yungle holds only ciphertext and never the key, so it cannot give you usable bytes — open the full link, # part included, in a browser. |
internal_error | 500 | Ours. details.requestId ties it to our logs — quote it if you get in touch. |
How to handle each class
Not found is never forbidden
A resource that exists but belongs to another workspace returns404, not 403. A 403 would confirm the id is real and let anyone probe for other workspaces’ ids. You cannot tell the two cases apart, and that is intended.
Retrying
Retry only these:429: after the number of seconds in theRetry-Afterheader. Most429bodies also carrydetails.retryAfterSeconds.500and dropped connections: with exponential backoff, a few times.409withdetails.reasonidempotency_in_progress: afterRetry-After, to get the first request’s result.
4xx is deterministic: the same request fails the same way.
Before you retry a POST, make it safe to repeat by sending an Idempotency-Key. Pagination and idempotency explains how. The SDKs already retry 429 and 5xx responses, and send an Idempotency-Key on every POST.
Next steps
Pagination and idempotency
Walk long lists and make retries safe.
Limits
The limits behind
429, 413 and email_budget_exhausted.