Skip to main content

Response envelope

Successful responses carry a literal success: true alongside the payload:
The success field is typed as the literal true in the schema rather than as a boolean, so a generated client can discriminate on it without a runtime check.

Errors

Errors return a plain object with a message:
Validation failures add the field-level detail:
Two rules the server holds itself to, which clients can rely on:
  • Routes never throw. An unhandled condition becomes a structured error response, not a stack trace or an HTML error page.
  • Raw database objects are never returned. Every response is projected to a DTO, so internal fields, tokens and unrelated columns cannot leak through an over-broad select.

DTOs, not entities

Response shapes are named DTOs — EmployeeOverviewDTO, PayslipDTO, CompensationViewDTO — generated from the Zod output schemas. They are a deliberate projection of the domain model, and they are the compatibility surface. The database schema behind them can change without breaking a client, and does.

Result size

The portal contract is self-scoped — every response covers one person — so its list endpoints take no limit parameter and return no pagination block today. Do not build a client that expects one; if pagination is introduced it will arrive as added fields, per the versioning rule below. Underneath, results are bounded regardless: every findMany in an API route must carry a take or a cursor, enforced in CI rather than left to review. The platform’s internal list endpoints — the ones behind the dashboard, not this contract — clamp a limit parameter and return a pagination: { total, limit, offset, hasMore } block.

Path parameter validation

Path parameters are constrained in the schema, not only in the handler. A payslip period, for example, is validated against ^\d{4}-\d{2}$ and a malformed value returns 400 with a message naming the expected YYYY-MM shape.

Idempotency

The platform supports an Idempotency-Key header on cost-bearing operations — the ones that move money or produce an irreversible external side effect, such as creating a payment batch. A retry after a network blip replays the stored response instead of producing a second batch. Keys are scoped per route so the same key from two endpoints cannot collide, and are retained long enough to cover any realistic retry window.
No endpoint in the portal contract is cost-bearing in that sense, so the header has no effect here. It is documented because it is the platform-wide convention, and because a future portal write that does carry an external side effect will honour it.

Locale

Content is returned in the caller’s locale where the content is translatable. Clients do not need to request a language — the session carries it.

Versioning

The employee endpoints are versioned in their path (/api/v1/portal/…). The two session endpoints sit at /api/auth/… and are not part of that versioning. Within a version:
  • Fields may be added to a response.
  • Fields are not removed and their types do not change.
  • New endpoints may appear.
A breaking change means a new version, not an in-place edit.
The specification is the contract. It is generated from the same schemas the server validates against and checked byte-for-byte in CI, so anything documented here is enforced at runtime rather than asserted in prose.