Conventions
Patterns that hold across the whole API.
Error envelope
Every error is a JSON object with a machine‑readable error code. Variants add
fields:
| Situation | Status | Body |
|---|---|---|
| Validation failed | 400 | { "error": "validation_error", "issues": [{ "path", "message" }] } (Fastify: "validation_failed") |
| Malformed JSON | 400 | { "error": "invalid_json", "message": "…" } |
| Unauthorized | 401 | { "error": "unauthorized" } |
| Entitlement required | 403 | { "error": "entitlement_required", "code", "feature", "current", "limit", "planId", "status", "upgradeRequired": true } |
| Not found | 404 | { "error": "<resource>_not_found" } |
| Method not allowed | 405 | { "error": "method_not_allowed" } |
| Conflict | 409 | { "error": "<reason>" } (e.g. library_name_conflict, track_set_mismatch) |
| Structural validation | 422 | { "error": "<x>_structural_validation_failed" } |
| Rate limited | 429 | { "error": "analytics_rate_limited" } |
| Native route not implemented | 501 | { "error": "native_route_not_implemented", "message", "path" } |
| Dependency unavailable | 503 | { "error": "<x>_database_unavailable" | "native_runtime_required", "message" } |
The Fastify Core API never leaks thrown messages: unhandled errors collapse to
{ "error": "internal_error" } with a 5xx status, and typed service errors
are mapped to specific codes in‑handler.
Response headers
Set by the runtime on every Core response:
| Header | Meaning |
|---|---|
x-swayzio-core-runtime | vercel (served natively) or proxy (forwarded to Fastify) |
x-swayzio-native-route | dotted route label, e.g. tracks.detail, packs.tracks.add |
x-swayzio-proxy-fallback | 1 when a native route fell back to the proxy |
Cache-Control | no-store (default on all responses) |
Proxy passthrough also forwards accept-ranges, content-range, etag,
location. The AI catalog route sets x-swayzio-ai-thread-id.
Pagination
Most list endpoints return a flat envelope:
{ "records": [ /* … */ ] }Track listing (GET /v1/tracks) and the search/analytics surfaces support
limit + cursor (or nextCursor) style paging where noted on each endpoint.
Internal drain‑eligible listing is cursor‑paged (limit, cursor → nextCursor).
Idempotency & safety
- Writes are owner‑scoped and validated against a Zod request schema; unknown
fields are rejected on
.strict()schemas (billing, agent‑tool inputs). - Uploads are a two‑step intent → finalize flow (a signed
PUT, then a finalize call) so large media never transits the API. - Conversions / sales tracking dedupe upstream (Dub); webhook ingestion is idempotent on provider event id.
Runtime modes (native vs proxy)
The /v1 surface has two implementations behind one contract. As a caller you
don’t choose — the platform routes natively on Vercel and proxies anything not
yet ported to the Fastify Core API. The x-swayzio-core-runtime header tells you
which path served a given response. A handful of surfaces are native‑only and
never proxy: /v1/analytics*, /v1/catalog-analysis, /v1/webhooks/*,
/v1/media-intelligence/callback, and /v1/internal/*. See
Architecture.