Skip to main content

API contracts

The platform exposes a single, documented HTTP surface, API-first. The API is described in two complementary places:

  1. The live OpenAPI document at GET /api/v1/_openapi.json (HTML viewer at GET /api/v1/_docs). This is the source of truth for the runtime — generated at startup from the Zod schemas in @endora-commerce/contracts that Fastify itself validates against, so it cannot drift from the running server.
  2. Per-domain contract stubs, listed under Contract stubs below — these document the intent of each surface in human terms (status codes, error envelopes, lifecycle constraints) and predate the running implementation. They remain the authoritative reference for non-runtime concerns: error code catalogue, idempotency contracts, audit-row guarantees, lifecycle invariants.

Live OpenAPI​

In a development environment:

pnpm run dev:infra && pnpm --filter backend run dev
# in another shell:
curl http://localhost:3001/api/v1/_openapi.json | jq .info
open http://localhost:3001/api/v1/_docs # Swagger UI in the browser

Every Fastify route is auto-registered into the document on boot via the onRoute hook in packages/platform/src/http/openapi.ts; modules may opt into a richer schema for any one route by calling openApiRegistry.registerPath({...}) directly.

Contract stubs​

DomainStub
Catalogcatalog.contract.md
Quote Requestsquote_requests.contract.md
Ordersorders.contract.md
Organizationsorganizations.contract.md
Credit Limitscredit_limits.contract.md

The stubs encode the why — what an error means, what a state machine looks like, what counts as a breaking change. Use them when you need to understand the rules of a surface; use the live OpenAPI when you need the exact request/response shapes a running build serves today.

Error envelope​

Every non-2xx response uses one shape:

{
"error": {
"code": "API_KEY_OUT_OF_SCOPE",
"message": "Human-readable explanation.",
"details": [{ "path": "scopes[0]", "issue": "must be a known scope" }],
"requestId": "req_abc123…"
}
}

code values come from the central catalogue in packages/contracts/src/errors.ts. The requestId mirrors the X-Request-Id response header — quote it in support tickets to make server logs traceable.

Pagination​

All list endpoints use cursor pagination:

GET /api/v1/orders?limit=50&cursor=eyJpZCI6IjAwMC...

The response shape is:

{
"data": [...],
"pagination": { "limit": 50, "nextCursor": "...", "hasMore": true }
}

Schemas live in packages/contracts/src/pagination.ts.