Skip to Content
ReferenceResponse Format

Response Format

New Vitae.ai endpoints should use explicit JSON:API serializers at the controller boundary. The goal is that every API response is predictable, regardless of whether the frontend, CLI, MCP server, or a customer integration consumes it.

Serializers must live in packages/serializers, not inline in controllers or API modules. Controllers should call serializeSingle(request, Serializer, doc) or serializeCollection(request, Serializer, paginatedResult).

Single resource

{ "data": { "id": "client_01h...", "type": "client", "attributes": { "name": "Acme Corp", "createdAt": "2026-05-15T13:00:00.000Z" } }, "links": { "self": "/v1/clients/client_01h..." } }

Resource list

{ "data": [ { "id": "job_01h...", "type": "job", "attributes": { "title": "Senior Backend Engineer" } } ], "links": { "self": "/v1/jobs?page=1&limit=25", "pagination": { "page": 1, "limit": 25, "total": 125, "totalPages": 5, "hasNextPage": true, "hasPreviousPage": false } } }

Action result

Workflow commands still return a resource envelope. The changed resource is returned in data; secondary entities can be included in meta.

{ "data": { "id": "inbox_item_01h...", "type": "inbox_item", "attributes": { "status": "accepted" } } }

Errors

{ "error": { "code": "validation_error", "message": "Validation failed", "details": [ { "field": "email", "message": "must be a valid email" } ], "requestId": "req_01h..." } }

Serializer rules

  • Every resource exposes id as a string and type as a stable resource type.
  • Public fields live under attributes.
  • Relationships live under relationships when serialized.
  • Internal database fields like _id, __v, raw ObjectIds, and deleted flags are not returned unless explicitly part of the public contract.
  • Dates are ISO 8601 strings.
  • Field names are camelCase.
  • Lists are always arrays under data.
  • Pagination lives under links.pagination.
  • Errors always use the error envelope.

Migration note

The API has a global compatibility serializer that normalizes normal controller returns into { data, meta } while endpoints are migrated. JSON:API documents returned from explicit serializers bypass that compatibility wrapper.

Endpoints that manually write to the Express response object, stream files, or intentionally opt out with @SkipResponseSerialization() are compatibility surfaces and must document their custom response format.

Current manual-response surfaces:

  • Stripe webhooks
  • CSV, XLSX, attachment, and QR-code downloads
  • OAuth redirects and revocation callbacks
  • short URL redirects
  • streaming agent chat
  • candidate search streaming/export responses
Last updated on