> This page is for API.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.spreadspace.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.spreadspace.app/_mcp/server.

# Errors

Every error response (auth failures, validation rejections, rate limits,
server faults) carries the same JSON envelope:

```json
{
  "error": {
    "type": "not_found",
    "message": "No borrower with that id exists in this workspace.",
    "request_id": "req_0123456789abcdef01234567"
  }
}
```

* **`type`**: a stable, machine-readable identifier. Branch on this, never on
  `message`.
* **`message`**: human-readable detail. Wording can change without notice.
* **`request_id`**: matches the `X-Request-ID` response header. Include it
  when contacting support; it lets us find the exact request.
* **`details`**: optional object of string values carrying the narrow,
  contract-level context a caller needs in order to act (for example
  `current_version` on a `409` version conflict, or the reservation figures on
  `insufficient_credits`). Absent when there is nothing actionable to add. On
  `validation_failed` it maps each offending field to its message.

Adding a **new** `type` is a non-breaking change; renaming or removing one is
breaking and versioned. Treat an unrecognized `type` according to its HTTP
status class.

## Status codes

| Status | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The request is malformed or fails validation (`invalid_request`, `validation_failed`, `invalid_cursor`, `invalid_limit`, `invalid_version`, …)                                                                                                                                                                                                                                                                                |
| `401`  | Missing or invalid credentials (`unauthorized`)                                                                                                                                                                                                                                                                                                                                                                               |
| `402`  | The workspace has no active subscription or cannot fund the call (`subscription_required`, `billing_required`, `spend_cap_reached`, `insufficient_credits`)                                                                                                                                                                                                                                                                   |
| `403`  | Authenticated but not allowed (`forbidden`, `insufficient_scope`, plan gates)                                                                                                                                                                                                                                                                                                                                                 |
| `404`  | The resource does not exist in your workspace (`not_found`). Also returned rather than `403` for ids you cannot see                                                                                                                                                                                                                                                                                                           |
| `405`  | The path exists but does not accept the request's method; the `Allow` header lists the methods it does (`invalid_request`)                                                                                                                                                                                                                                                                                                    |
| `409`  | The write conflicts with current state (`conflict`, `idempotency_request_mismatch`, `idempotency_response_too_large`)                                                                                                                                                                                                                                                                                                         |
| `413`  | Over a size cap: a synchronous export over its line-item cap (`doc_too_large`), or a request body over 16 MiB sent with an `Idempotency-Key` (`invalid_request`)                                                                                                                                                                                                                                                              |
| `415`  | The request body's `Content-Type` is not one the operation accepts. Send a JSON body as `application/json` (`invalid_request`)                                                                                                                                                                                                                                                                                                |
| `423`  | A finalized spread or memo refuses the write until it is reopened. `spread_locked` from spread board writes made from the workspace or an embed session, and from `PUT /api/borrowers/{borrower_id}/extractions/fin-ebitda-addbacks` while a finalized spread holds the entity the add-backs belong to (any finalized spread on the loan holds the whole-loan scope); `memo_locked` from a render request on a finalized memo |
| `429`  | Rate limited or throttled (`rate_limited`, `usage_throttled`, `idempotency_in_progress`). Honor `Retry-After`. See [Rate limits](/api/rate-limits)                                                                                                                                                                                                                                                                            |
| `5xx`  | Server fault (`internal_error`). A mutation sent with an `Idempotency-Key` replays the fault under that key rather than re-running; see [Idempotency](/api/idempotency) before resending                                                                                                                                                                                                                                      |

## Common error types

| `type`                                                                                                     | Status        | When                                                                                                                                                                                                              |
| ---------------------------------------------------------------------------------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unauthorized`                                                                                             | 401           | Missing, malformed, revoked, or wrong-environment API key                                                                                                                                                         |
| `forbidden`                                                                                                | 403           | The caller's role or session kind cannot perform this action                                                                                                                                                      |
| `insufficient_scope`                                                                                       | 403           | The API key lacks a required scope. The message names it                                                                                                                                                          |
| `mode_mismatch`                                                                                            | 403           | The key's mode cannot call this endpoint. The sandbox reset, for example, runs on test-mode keys only                                                                                                             |
| `test_key_remint_required` / `sandbox_missing`                                                             | 403           | The test key has no usable sandbox. Create a new test key in the dashboard                                                                                                                                        |
| `billing_required`                                                                                         | 402           | The workspace has no active subscription. Set up billing, then retry                                                                                                                                              |
| `subscription_required`                                                                                    | 402           | The workspace has no active or trialing subscription. Returned on every `/api` path except `/api/me` and the paths under `/api/organizations/` and `/api/sandbox/`; `GET /api/organizations` itself is not exempt |
| `spend_cap_reached`                                                                                        | 402           | Upload and intake only: the workspace's monthly usage limit is reached                                                                                                                                            |
| `insufficient_credits`                                                                                     | 402           | Upload and intake only: prepaid credits cannot cover the reservation. `details` carries `balance_usd`, `reserved_usd`, `in_flight_docs`, `incoming_docs` and `per_doc_reservation_usd` as decimal strings         |
| `usage_throttled`                                                                                          | 429           | Upload and intake only: the workspace is under an admission throttle. Honor `Retry-After`                                                                                                                         |
| `embed_not_in_plan` / `counterparty_not_in_plan` / `spreads_not_in_plan` / `statement_mapping_not_in_plan` | 403           | The feature is not in the workspace's plan: embed sessions, the owners read on a borrower's extractions, the chart of accounts and its export map, and the statement mapping reads                                |
| `rate_limited`                                                                                             | 429           | Over budget for the endpoint's policy, or throttled. See [Rate limits](/api/rate-limits)                                                                                                                          |
| `invalid_request`                                                                                          | 400           | Failed validation; `message` names the field                                                                                                                                                                      |
| `invalid_request`                                                                                          | 405, 413, 415 | The request could not be accepted as sent; the status says why: the method, a body over the size cap sent with an `Idempotency-Key`, or the body's `Content-Type`                                                 |
| `validation_failed`                                                                                        | 400           | A request field failed validation; `details` maps each field to its message                                                                                                                                       |
| `invalid_include`                                                                                          | 400           | The `include` parameter on a loan read carries an unknown value; the message lists the supported values                                                                                                           |
| `invalid_request` with message `borrower_loan_mismatch`                                                    | 400           | The upload request's `borrower_id` is not the borrower of the loan named by `loan_id`. In the batch presign the same string is the per-file `error` value                                                         |
| `spreadsheet_upload_disabled`                                                                              | 400           | Spreadsheet uploads are not accepted; submit the document as a PDF                                                                                                                                                |
| `invalid_cursor` / `cursor_expired` / `invalid_limit`                                                      | 400           | Pagination inputs: restart iteration without a cursor                                                                                                                                                             |
| `invalid_version`                                                                                          | 400           | Unrecognized `SpreadSpace-Version` header value                                                                                                                                                                   |
| `not_found`                                                                                                | 404           | No such resource in your workspace                                                                                                                                                                                |
| `not_found` with message `loan_not_found`                                                                  | 404           | The presign request named a loan that does not exist in your workspace. In the batch presign the same string is the per-file `error` value                                                                        |
| `conflict`                                                                                                 | 409           | Stale version or duplicate state transition; `details.current_version` when applicable                                                                                                                            |
| `idempotency_in_progress`                                                                                  | 429           | A request with the same `Idempotency-Key` is still executing. Retry after `Retry-After`                                                                                                                           |
| `idempotency_request_mismatch`                                                                             | 409           | The same `Idempotency-Key` was reused with a different request body                                                                                                                                               |
| `idempotency_response_too_large`                                                                           | 409           | The original request under this key completed, but its response exceeded the 1 MiB replay cap and cannot be replayed. Read the resource instead of resending                                                      |
| `doc_too_large`                                                                                            | 413           | The synchronous single-document export exceeds its line-item cap; create an export instead                                                                                                                        |
| `internal_error`                                                                                           | 500           | Unexpected server fault. Contact support with the `request_id`. Under an `Idempotency-Key` the fault is replayed; confirm the resource state before resending with a new key                                      |

Embed sessions have their own `type` family. At mint time
(`POST /api/embed/sessions`): `embed_scope_exceeds_key` and
`embed_scope_not_allowed`, both 400, and `embed_origin_not_allowed`, 403, when
the mint request carries an `Origin` header that is outside the key's allowed
origins. When a session token is used: `invalid_embed_token`,
`embed_token_expired` and `embed_session_not_found` (a revoked session), all
401, and `embed_session_binding_mismatch`, 403, when the token reaches a loan
or borrower it was not minted for. On the iframe handle exchange:
`invalid_session_handle`, `session_handle_already_used` and
`session_handle_expired`, all 401, and `same_origin_required`, 403. The
[Embed flow](/embed/overview) describes when each arises.