Skip to main content

Conventions

Rules that apply across the whole API. Reading this page once will save you most of the surprises.

Pagination

Most list endpoints use offset pagination:

ParameterDefaultMeaning
page11-based page number
page_size10Records per page (20 for /notifications/in-app)
{
"total_records": 42,
"current_page": 1,
"total_pages": 5,
"content": [ ... ]
}

Empty results return 204, not an empty page

warning

When a list endpoint has no results it returns 204 No Content with no response body — not a 200 with an empty content array. Clients that assume a JSON body on every 2xx will break.

resp = requests.get(url, headers=headers)
if resp.status_code == 204:
items = []
else:
items = resp.json()["content"]

Not every list endpoint does this. These always return 200 with an empty content array: GET /notifications/in-app, GET /attachments, GET /me/proof-packages, and GET /assets/{asset_id}/prices/{price_type}. Handling both shapes, as above, is the safe approach.

Proof packages use cursor pagination

GET /me/proof-packages is the one exception. It takes limit (default 50, min 1, max 200) and an opaque cursor, and returns a next_cursor. Treat the cursor as opaque — do not parse or construct it — and keep requesting until next_cursor is null.

Errors

Failures return a two-field body:

{ "error": "ItemNotFoundError", "message": "Oops! Vault not found" }
StatuserrorMeaning
400ValidationErrorMalformed or rejected input
402InsufficientCreditsErrorNot enough credits — see below
403ForbiddenErrorBad credentials, or not permitted
404ItemNotFoundErrorNot found, or not visible to you
409ConflictErrorConflicts with existing state
422MissingWalletCredentialErrorA wallet credential is required but absent
500InternalErrorServer-side failure

The Oops! prefix appears on 404 and 500 messages only; 400, 403, 409 and 422 carry the bare message.

Two shapes do not follow this envelope:

  • Request validation failures return the framework's {"detail": [...]} shape.
  • Authentication failures — a missing or invalid x-api-key / x-api-secret — also return {"detail": "..."}, not the envelope. The ForbiddenError code above covers authorization ("you are known but not permitted"), not authentication.
warning

A 500 body interpolates the underlying exception text into message. It is not a stable contract and may expose internal detail — log it, but never parse or branch on it.

402 carries extra fields

Creating a vault, stream or data attachment runs a credit check first. If it fails:

{
"error": "InsufficientCreditsError",
"message": "Insufficient credits",
"required": 1.0,
"balance": 0.0,
"has_subscription": true
}

Ledgers

Where a ledger value is accepted, the canonical values are:

XRPL · POLYGON_ZKEVM · POLYGON_POS · ETHEREUM

note

The enum names do not map cleanly onto the underlying networks in every environment — for example POLYGON_ZKEVM resolves to Polygon Amoy in development. Treat these as opaque identifiers and use the value the API returned to you rather than constructing your own. Some aliases (amoy, polygon) are accepted on input, but the canonical value above is what you get back.

Response headers

Every response carries Cache-Control: no-cache. Fields that are null are serialised explicitly rather than omitted, so an optional field you have not set comes back as "field": null.