EmofyEmofy Developers
Conventions

API Conventions

The three API surfaces, versioning, response envelopes, idempotency, and validation — the agreements every endpoint follows.

The backend exposes one HTTP API with three audiences, all generated from a shared DocumentBuilder (apps/backend/src/config/openapi-documents.ts). Knowing which surface you're on tells you the auth model and the response shape.

The three surfaces

SurfacePathAuthEnvelopeAudience
Internal/api/...User JWT{ success, data, error, meta }Web/mobile dashboards
Developer (v1)/api/v1/...API key{ data, meta } / { error }Partner integrations
EMA/api/ema/...EMA tokenEMA schemaEmbedded Mini Apps

Each surface has its own OpenAPI document and reference: Internal · Developer.

Versioning

Routing uses URI versioning under the global /api prefix:

/api/v1/tasks      explicit version 1 (partner API)
/api/tasks         version-neutral (default)

The partner API is the one with an explicit version contract and a deprecation lifecycle — see Versioning & deprecation. The committed OpenAPI snapshot makes the developer surface diffable, which powers the API changelog.

Response envelopes

Developer (v1) — data plus metadata:

{
  "data": { "id": "task_h4n2k9x...", "title": "..." },
  "meta": {
    "requestId": "req-2c91...",
    "timestamp": "2026-06-17T12:00:00.000Z",
    "pagination": { "total": 42, "page": 1, "limit": 5, "hasMore": true }
  }
}

Internal — adds an explicit success flag:

{ "success": true, "data": { }, "error": null, "meta": { } }

Both shapes are produced by the TransformInterceptor — handlers return plain objects and the envelope is applied centrally.

Errors

Exceptions are caught by the global filters and shaped consistently. Validation failures (from the global ValidationPipe) return 400 with a flattened details array:

{
  "error": {
    "message": "Validation failed",
    "details": [{ "field": "dueDate", "message": "must be a valid date" }]
  }
}

5xx responses are reported to Sentry with the request's correlation ID.

Idempotency

Mutating requests may send an Idempotency-Key header (PRD-224). A replay of the same key returns the original result with an Idempotency-Replayed header instead of performing the action twice; concurrent replays get a 409 with Retry-After.

POST /api/v1/tasks
Idempotency-Key: 2c91-4f7a-...

Validation & DTOs

  • Every input is a DTO class validated with class-validator; the global pipe runs with whitelist + forbidNonWhitelisted, so unknown fields are rejected.
  • IDs are validated with @IsPrefixedId() to enforce the prefixed-nanoid shape.
  • transform: true coerces and instantiates nested DTOs.

Custom headers

The CORS policy allows a specific set of platform headers:

HeaderPurpose
X-Org-IdTarget org (super-admin act-as-org only).
X-Org-RoleTarget role (super-admin only).
X-Experience-IdEMA/experience context.
X-Acting-User-IdImpersonation context.
Idempotency-KeyIdempotent mutations.

See Multi-tenancy for why X-Org-Id is honored only for super-admins.

Next

On this page