> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yungle.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> The error shape every Yungle API failure shares, every error code with its status, and which errors are worth retrying.

Every failed request returns the same JSON shape with a stable `code`. Use this page to decide what your code does with each one.

## The error shape

```json theme={null}
{
  "error": {
    "code": "quota_exceeded",
    "message": "This upload would exceed your storage quota.",
    "details": { "usedBytes": 268435456000, "quotaBytes": 268435456000 },
    "docs": "https://docs.yungle.co/errors#quota_exceeded"
  }
}
```

| Field | What it is |
| - | - |
| `code` | Stable. Branch on this. |
| `message` | Written for a person reading a log. It can change, so never parse it. |
| `details` | Present only when there is something you can act on. Its fields depend on the code. |
| `docs` | A link to this code's row in the table below. |

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

<table>
  <thead>
    <tr>
      <th>Code</th>
      <th>Status</th>
      <th>What to do</th>
    </tr>
  </thead>

  <tbody>
    <tr id="unauthorized">
      <td>`unauthorized`</td>
      <td>401</td>
      <td>Check the key. Missing, malformed, unknown, revoked and expired all look the same on purpose.</td>
    </tr>

    <tr id="upgrade_required">
      <td>`upgrade_required`</td>
      <td>402</td>
      <td>The workspace is on the free plan. Nothing to retry.</td>
    </tr>

    <tr id="out_of_credit">
      <td>`out_of_credit`</td>
      <td>402</td>
      <td>API uploads past the monthly allowance are paid from prepaid credit, and there is none left. Top up in the dashboard, then retry.</td>
    </tr>

    <tr id="insufficient_scope">
      <td>`insufficient_scope`</td>
      <td>403</td>
      <td>The key lacks a scope. `details.required` names it; create a key that has it.</td>
    </tr>

    <tr id="not_found">
      <td>`not_found`</td>
      <td>404</td>
      <td>No such resource, or it belongs to another workspace — deliberately the same answer. Also returned for a path that is not an endpoint.</td>
    </tr>

    <tr id="invalid_request">
      <td>`invalid_request`</td>
      <td>400</td>
      <td>Bad body. `details.issues` lists field paths and reasons.</td>
    </tr>

    <tr id="folder_error">
      <td>`folder_error`</td>
      <td>400</td>
      <td>Duplicate or invalid folder name, or too deep. `details.code` says which.</td>
    </tr>

    <tr id="conflict">
      <td>`conflict`</td>
      <td>409</td>
      <td>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.</td>
    </tr>

    <tr id="not_editable">
      <td>`not_editable`</td>
      <td>410</td>
      <td>The transfer has been sent, revoked or expired. Composing is over.</td>
    </tr>

    <tr id="quota_exceeded">
      <td>`quota_exceeded`</td>
      <td>413</td>
      <td>Storage is full. `details` carries used and total bytes. Delete something.</td>
    </tr>

    <tr id="transfer_too_large">
      <td>`transfer_too_large`</td>
      <td>413</td>
      <td>One transfer exceeded the plan ceiling. `details.limitBytes` is the cap.</td>
    </tr>

    <tr id="rate_limited">
      <td>`rate_limited`</td>
      <td>429</td>
      <td>Back off. Honour the `Retry-After` header, or throttle on the `RateLimit` header before you get here.</td>
    </tr>

    <tr id="email_budget_exhausted">
      <td>`email_budget_exhausted`</td>
      <td>429</td>
      <td>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.</td>
    </tr>

    <tr id="password_required">
      <td>`password_required`</td>
      <td>403</td>
      <td>The shared link is password protected. Send `password` with the request.</td>
    </tr>

    <tr id="wrong_password">
      <td>`wrong_password`</td>
      <td>403</td>
      <td>Wrong password for the shared link. Attempts are budgeted per workspace and per link; after too many you get `rate_limited`.</td>
    </tr>

    <tr id="guests_only">
      <td>`guests_only`</td>
      <td>403</td>
      <td>The collection is open to invited guests only. A guest opens it in a browser, signed in; the link alone is not enough.</td>
    </tr>

    <tr id="link_unavailable">
      <td>`link_unavailable`</td>
      <td>410</td>
      <td>The shared link has expired, was revoked, or is under a hold. Nothing to retry.</td>
    </tr>

    <tr id="e2ee_unsupported">
      <td>`e2ee_unsupported`</td>
      <td>409</td>
      <td>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.</td>
    </tr>

    <tr id="internal_error">
      <td>`internal_error`</td>
      <td>500</td>
      <td>Ours. `details.requestId` ties it to our logs — quote it if you get in touch.</td>
    </tr>
  </tbody>
</table>

## How to handle each class

| Status | Class | What your code does |
| - | - | - |
| `400` | Your request is wrong | Fix it. `details.issues` lists each bad field as `path` and `message`. Do not retry unchanged. |
| `401` | No valid key | Check the key. See [Authentication](https://docs.yungle.co/authentication.md). |
| `402` | Plan or credit | `upgrade_required`: the endpoint needs a paid plan. `out_of_credit`: top up API credit, then retry. |
| `403` | Not allowed | `insufficient_scope`: use a key with the scope in `details.required`. Password and guest-only links: see the table above. |
| `404` | Not found | The resource does not exist or belongs to another workspace. Do not retry. |
| `409` | Conflict | Usually safe to skip, such as a contact that already exists. With `details.reason`, it is about your `Idempotency-Key`. |
| `410` | Gone | The transfer is sent, revoked or expired, or a shared link is no longer available. Do not retry. |
| `413` | Too large | Storage is full or the transfer exceeds the plan's ceiling. `details` has the numbers. |
| `429` | Slow down | Wait for `Retry-After`, then retry. `email_budget_exhausted` is different: nothing was sent and nothing changed. For a transfer, finalize again without `recipients` to get the link now, or retry tomorrow. |
| `500` | Our fault | Retry with backoff. If it persists, quote `details.requestId` when you get in touch. |

### Not found is never forbidden

A resource that exists but belongs to another workspace returns `404`, 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 the `Retry-After` header. Most `429` bodies also carry `details.retryAfterSeconds`.
* **`500`** and dropped connections: with exponential backoff, a few times.
* **`409`** with `details.reason` `idempotency_in_progress`: after `Retry-After`, to get the first request's result.

Every other `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](https://docs.yungle.co/pagination-and-idempotency.md#idempotency) explains how. The SDKs already retry `429` and `5xx` responses, and send an `Idempotency-Key` on every `POST`.

## Next steps

> **[Pagination and idempotency](https://docs.yungle.co/pagination-and-idempotency.md)**
>
> Walk long lists and make retries safe.

> **[Limits](https://docs.yungle.co/limits.md)**
>
> The limits behind `429`, `413` and `email_budget_exhausted`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.