Conventions
Response envelope, error format, and cursor pagination.
Every /v1 response follows the same shape, regardless of resource. Once
you've integrated one endpoint, you've integrated all of them.
Response envelope
Successful responses wrap the payload in data and carry pagination
hints in meta:
{
"data": { "status": "ok" },
"meta": {
"cursor": null,
"hasMore": false
}
}data— the resource or list of resources. Never null on success.meta.cursor— opaque string; pass it to the next request to continue paginating.nullwhen there's no next page.meta.hasMore—trueif more pages exist.
Errors
Errors return a structured body regardless of HTTP status:
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid Bearer token.",
"details": {}
}
}code— stable machine-readable identifier ("unauthorized","not_found","rate_limited","invalid_argument", etc.). Safe to switch on.message— human-readable string. Good for logs; not safe to show verbatim in end-user UIs without translation.details— optional, per-code. For validation errors, contains a field-keyed map of specifics.
Common HTTP statuses:
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Invalid request (body shape, missing required fields) |
| 401 | Missing / invalid / revoked token |
| 403 | Authenticated but not authorized (User key without permissions) |
| 404 | Resource does not exist, or belongs to another organization |
| 409 | State conflict (exceeded a limit, duplicate) |
| 429 | Rate limit exceeded (Rate limits) |
| 5xx | Server error — treat as retryable with backoff |
Pagination
List endpoints accept two query parameters:
limit— integer, default20, max100. Values above100are clamped silently.cursor— opaque string from a previous response'smeta.cursor. Omit on the first request.
Pagination is forward-only and cursor-based — there are no page numbers. The cursor encodes sort position; it is stable across new records being added.
GET /v1/clients?limit=50
GET /v1/clients?limit=50&cursor=eyJpZCI6MTIzfQWhen meta.hasMore is false, stop paginating.
Multi-tenancy
Every request is scoped to the organization that owns the Bearer key.
Objects that belong to a different organization return 404 (not
403) — Salfio does not leak the existence of other orgs' data.