api overview
The spirby api is a hand-designed rest surface — resource urls, standard http methods, standard status codes. No graphql, no rpc, no auto-generated wrapper around an internal contract.
- base url:
https://api.spirby.com - versioning:
/v1/*. additive changes only inside v1; breaking changes get a new prefix. - auth: bearer api keys, two scopes (
readandread:write). see authentication. - rate limits: per-key budget, surfaced on every response via
x-ratelimit-*headers. - webhooks: signed outbound deliveries for
post.*,vote.*,comment.*, andchangelog.*events. see webhooks. - machine-readable spec: openapi.json — generated from the same zod schemas the handlers validate with, so it never drifts.
- browseable reference: /api/reference — every endpoint with try-it.
response shapes
Section titled “response shapes”All responses use one of three shapes. Single-resource and collection envelopes are always { data } so clients only have one parser to write.
// success — single resource{ "data": { "id": "...", ... } }
// success — collection (cursor pagination){ "data": [ ... ], "nextCursor": "..." }
// error{ "error": { "code": "ERR_NOT_FOUND", "message": "post not found" } }Error details is omitted when empty. code is machine-readable and stable for the v1 lifetime; render your own copy from it. message is plain English suitable for logs.
stable error codes
Section titled “stable error codes”| code | http | when |
|---|---|---|
ERR_VALIDATION | 422 | zod validation failed on params, query, or body |
ERR_BAD_REQUEST | 400 | malformed input that survived zod (e.g. bad cursor) |
ERR_UNAUTHORIZED | 401 | missing, malformed, revoked, or expired api key |
ERR_SCOPE_INSUFFICIENT | 403 | key lacks the required scope (writes against a read key) |
ERR_FORBIDDEN | 403 | authorized but not allowed (e.g. trial-tier write while readonly) |
ERR_NOT_FOUND | 404 | resource missing or not in your org (never confirms cross-org existence) |
ERR_CONFLICT | 409 | slug collision, idempotency replay, already-voted |
ERR_RATE_LIMITED | 429 | per-key budget exhausted; Retry-After header set |
ERR_INTERNAL | 500 | unhandled error; details omitted in production |
Adding new codes is non-breaking; renaming or removing one is.
- new to the api? start with authentication, then run a curl from quickstart.
- shipping an integration? read webhooks and verify signatures before parsing payloads.
- need the spec? grab openapi.json or browse /api/reference.