# API Response Spec v1.0
## 0. Scope & Goals
- Envelope **always** for success & errors.
- **Hybrid error codes**: tiny global set + domain-prefixed codes.
- Reserve optional, ignorable **ui** hints with actions.
- Support **cursor & offset** pagination, **LRO**, **bulk**, **idempotency**, **ETag**.
- JSON keys use **camelCase**.
---
## 1. Envelope (all responses)
**Content-Type:** `application/json; charset=utf-8`
```json
{
"ok": true,
"code": "MACHINE_CODE",
"data": {},
"error": null,
"links": {
"self": "/v1/…",
"next": "/v1/…",
"prev": "/v1/…",
"first": "/v1/…",
"last": "/v1/…"
},
"ui": {
"messageKey": "…",
"messageFallback": "…",
"severity": "info|success|warning|error",
"presentation": "toast|banner|dialog|inline",
"actions": []
},
"meta": {
"requestId": "req_…",
"schemaVersion": "1.0",
"generatedAt": "2025-08-26T00:00:00Z",
"traceId": "…",
"spanId": "…",
"locale": "en-IN",
"etag": "W/\"…\"",
"idempotencyKey": "…"
}
}
```
**Rules**
- `ok`, `code`, `meta` **always present**.
- Exactly one of `data` **or** `error` must be present.
- Use **HTTP status** correctly (2xx success, 4xx client/domain error, 5xx server fault).
---
## 2 Codes (Hybrid Policy)
### 2.1 Global codes (small, canonical)
```
UNAUTHENTICATED, FORBIDDEN, NOT_FOUND, CONFLICT,
VALIDATION_FAILED, PRECONDITION_FAILED, FAILED_DEPENDENCY,
RATE_LIMITED, TIMEOUT, SERVICE_UNAVAILABLE, INTERNAL_ERROR
```
### 2.2 Domain codes (prefixed)
- Format: `USER_*`, `ORDER_*`, `PAYMENT_*`, `INVENTORY_*`, etc.
- Examples: `USER_EMAIL_TAKEN`, `ORDER_OUT_OF_STOCK`, `PAYMENT_AUTH_DECLINED`.
- Naming rule (lint): `^[A-Z]+(_[A-Z]+)*
, ≤4 segments, must start with a prefix.
**Governance**
- Global list is centralized; domain lists live in each service README and must not overlap global names.
---
## 3 Success Shapes
### 3.1 Single resource
```json
{
"ok": true,
"code": "USER_FETCHED",
"data": { "id": "usr_123", "name": "Ada", "createdAt": "…" },
"links": { "self": "/v1/users/usr_123" },
"meta": { "requestId": "req_1", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
### 3.2 Collections (pagination)
#### Cursor mode (preferred)
```json
{
"ok": true,
"code": "USERS_LISTED",
"data": {
"items": [ { "id": "usr_1" }, { "id": "usr_2" } ],
"page": {
"mode": "cursor",
"cursor": "base64…",
"nextCursor": "base64…",
"size": 50
}
},
"links": { "self": "/v1/users?cursor=…&size=50", "next": "/v1/users?cursor=…&size=50" },
"meta": { "requestId": "req_2", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
#### Offset mode
```json
{
"ok": true,
"code": "USERS_LISTED",
"data": {
"items": [ { "id": "usr_1" }, { "id": "usr_2" } ],
"page": {
"mode": "offset",
"offset": 200,
"limit": 50,
"hasMore": true
}
},
"links": { "self": "/v1/users?offset=200&limit=50", "next": "/v1/users?offset=250&limit=50" },
"meta": { "requestId": "req_3", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
> No totals. Choose one pagination mode per endpoint.
---
## 4 Errors (Problem Object embedded)
**Content-Type:** `application/json` (you may also set `application/problem+json`).
```json
{
"ok": false,
"code": "VALIDATION_FAILED",
"error": {
"type": "https://errors.example.com/validation/invalid-argument",
"title": "Validation failed",
"status": 422,
"detail": "One or more fields are invalid.",
"instance": "req_abc123",
"code": "VALIDATION_FAILED",
"errors": [
{ "path": "email", "reason": "INVALID_FORMAT", "message": "Must be a valid email" },
{ "path": "password", "reason": "TOO_SHORT", "min": 12 }
],
"hint": "Fix the highlighted fields and retry.",
"docsUrl": "https://docs.example.com/errors/validation",
"supportUrl": "https://support.example.com/tickets/new",
"retryAfterSeconds": 0
},
"ui": {
"messageKey": "errors.validation_failed",
"messageFallback": "Some details need your attention.",
"severity": "warning",
"presentation": "inline",
"actions": [
{ "type": "retry", "label": "Try again" },
{ "type": "link", "label": "View limits", "href": "https://docs.example.com/rate-limits" }
]
},
"meta": { "requestId": "req_abc123", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
**HTTP ↔ code guidance**
- 401→`UNAUTHENTICATED`, 403→`FORBIDDEN`, 404→`NOT_FOUND`, 409→`CONFLICT`, 412→`PRECONDITION_FAILED`, 422→`VALIDATION_FAILED`, 429→`RATE_LIMITED`, 5xx→`INTERNAL_ERROR`/`SERVICE_UNAVAILABLE`/`TIMEOUT`.
---
## 5. Bulk Operations (partial success)
```json
{
"ok": true,
"code": "BULK_RESULT",
"data": {
"successes": [ { "index": 0, "id": "usr_1" }, { "index": 2, "id": "usr_3" } ],
"failures": [
{
"index": 1,
"error": {
"type": "https://errors.example.com/validation/unique",
"title": "Validation failed",
"status": 422,
"code": "VALIDATION_FAILED",
"detail": "Email is already taken",
"errors": [{ "path": "email", "reason": "UNIQUE" }]
}
}
]
},
"meta": { "requestId": "req_bulk_1", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
---
## 6. Reserved `ui` (success & error)
```json
{
"messageKey": "user.created",
"messageFallback": "User created",
"severity": "info|success|warning|error",
"presentation": "toast|banner|dialog|inline",
"actions": [
{ "type": "link", "label": "Open profile", "href": "/me" },
{ "type": "route", "label": "Open in app", "route": "Profile", "payload": { "id": "usr_123" } },
{ "type": "retry", "label": "Try again" },
{ "type": "copy", "label": "Copy ID", "copyText": "usr_123" },
{ "type": "support", "label": "Contact support", "payload": { "category": "billing", "priority": "normal" } }
]
}
```
**Notes**
- Entire `ui` block is optional and ignorable.
- Prefer `messageKey` for future localization; `messageFallback` for default text.
---
## 7. Long-Running Operations (LRO)
**Start**
```json
{
"ok": true,
"code": "OPERATION_ACCEPTED",
"data": {
"name": "operations/op_789",
"done": false,
"metadata": {
"kind": "user.import",
"submittedAt": "2025-08-26T09:05:00Z",
"progress": { "completed": 0, "total": 1000 }
}
},
"meta": { "requestId": "req_lro_1", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
**Poll – success**
```json
{
"ok": true,
"code": "OPERATION_SUCCEEDED",
"data": { "name": "operations/op_789", "done": true, "response": { "imported": 995, "skipped": 5 } },
"meta": { "requestId": "req_lro_2", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
**Poll – error**
```json
{
"ok": false,
"code": "OPERATION_FAILED",
"error": {
"type": "https://errors.example.com/job/failed",
"title": "Job failed",
"status": 500,
"detail": "Worker crashed on shard 3"
},
"meta": { "requestId": "req_lro_3", "schemaVersion": "1.0", "generatedAt": "…" }
}
```
---
## 8) Concurrency & Idempotency
### ETags
- Writes return `meta.etag`.
- Clients send `If-Match: <etag>`.
- On mismatch: HTTP 412 + `code=PRECONDITION_FAILED` and problem `error`.
### Idempotency (selected endpoints)
- Clients send `Idempotency-Key: <uuid>`.
- Server echoes `meta.idempotencyKey`.
- Replays return the **original** envelope.
---
## 9) Rate Limits
Headers:
```
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
Retry-After (when 429/503)
```
Body on 429 uses error pattern with `code=RATE_LIMITED` and may include `retryAfterSeconds`.
---
## 10) Security & Redaction
- Don’t leak internals in `detail`/`errors`.
- Keep 5xx messages generic; correlate via `meta.requestId`.
- Apply PII masking by policy.
---
## 11) Versioning
- `meta.schemaVersion` tracks **envelope** version (start at `"1.0"`).
- Resource payload versions are independent (route or content negotiation).
---
## 12) Validation & Lint Rules
- Envelope contains `ok`, `code`, and `meta{requestId,schemaVersion,generatedAt}`.
- Exactly one of `data` or `error`.
- If `error` present: must include `status` (matches HTTP), `title`, `type` URI.
- Pagination: `data.page.mode` ∈ `{cursor, offset}` with corresponding fields.
- `ui.severity` ∈ `{info, success, warning, error}`; `ui.presentation` ∈ `{toast, banner, dialog, inline}`.
- `ui.actions[]` must follow type-specific required fields.
---
# JSON Schemas (v1)
## schemas/envelope.v1.json
```json
{
"$id": "https://schema.example.com/envelope.v1.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["ok", "code", "meta"],
"properties": {
"ok": { "type": "boolean" },
"code": { "type": "string" },
"data": {},
"error": { "$ref": "https://schema.example.com/problem.v1.json" },
"links": {
"type": "object",
"properties": {
"self": { "type": "string" },
"next": { "type": "string" },
"prev": { "type": "string" },
"first": { "type": "string" },
"last": { "type": "string" }
},
"additionalProperties": false
},
"ui": { "$ref": "https://schema.example.com/ui.v1.json" },
"meta": {
"type": "object",
"required": ["requestId", "schemaVersion", "generatedAt"],
"properties": {
"requestId": { "type": "string" },
"schemaVersion": { "type": "string" },
"generatedAt": { "type": "string", "format": "date-time" },
"traceId": { "type": "string" },
"spanId": { "type": "string" },
"locale": { "type": "string" },
"etag": { "type": "string" },
"idempotencyKey": { "type": "string" }
},
"additionalProperties": true
}
},
"additionalProperties": false,
"allOf": [
{
"if": { "properties": { "ok": { "const": true } } },
"then": { "required": ["data"], "not": { "required": ["error"] } }
},
{
"if": { "properties": { "ok": { "const": false } } },
"then": { "required": ["error"], "not": { "required": ["data"] } }
}
]
}
```
## schemas/problem.v1.json
```json
{
"$id": "https://schema.example.com/problem.v1.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["type", "title", "status"],
"properties": {
"type": { "type": "string", "format": "uri" },
"title": { "type": "string" },
"status": { "type": "integer" },
"detail": { "type": "string" },
"instance": { "type": "string" },
"code": { "type": "string" },
"errors": {
"type": "array",
"items": {
"type": "object",
"required": ["path", "reason"],
"properties": {
"path": { "type": "string" },
"reason": { "type": "string" },
"message": { "type": "string" }
},
"additionalProperties": true
}
},
"hint": { "type": "string" },
"docsUrl": { "type": "string", "format": "uri" },
"supportUrl": { "type": "string", "format": "uri" },
"retryAfterSeconds": { "type": "integer", "minimum": 0 }
},
"additionalProperties": true
}
```
## schemas/page.v1.json
```json
{
"$id": "https://schema.example.com/page.v1.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"oneOf": [
{
"type": "object",
"required": ["mode", "cursor", "size"],
"properties": {
"mode": { "const": "cursor" },
"cursor": { "type": "string" },
"nextCursor": { "type": ["string", "null"] },
"size": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
},
{
"type": "object",
"required": ["mode", "offset", "limit", "hasMore"],
"properties": {
"mode": { "const": "offset" },
"offset": { "type": "integer", "minimum": 0 },
"limit": { "type": "integer", "minimum": 1 },
"hasMore": { "type": "boolean" }
},
"additionalProperties": false
}
]
}
```
## schemas/ui.v1.json
```json
{
"$id": "https://schema.example.com/ui.v1.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"messageKey": { "type": "string" },
"messageFallback": { "type": "string" },
"severity": { "enum": ["info", "success", "warning", "error"] },
"presentation": { "enum": ["toast", "banner", "dialog", "inline"] },
"actions": {
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"required": ["type", "label", "href"],
"properties": {
"type": { "const": "link" },
"label": { "type": "string" },
"href": { "type": "string" }
},
"additionalProperties": false
},
{
"type": "object",
"required": ["type", "label", "route"],
"properties": {
"type": { "const": "route" },
"label": { "type": "string" },
"route": { "type": "string" },
"payload": { "type": "object", "additionalProperties": true }
},
"additionalProperties": false
},
{
"type": "object",
"required": ["type", "label"],
"properties": {
"type": { "const": "retry" },
"label": { "type": "string" }
},
"additionalProperties": false
},
{
"type": "object",
"required": ["type", "label", "copyText"],
"properties": {
"type": { "const": "copy" },
"label": { "type": "string" },
"copyText": { "type": "string" }
},
"additionalProperties": false
},
{
"type": "object",
"required": ["type", "label"],
"properties": {
"type": { "const": "support" },
"label": { "type": "string" },
"payload": { "type": "object", "additionalProperties": true }
},
"additionalProperties": false
}
]
}
}
},
"additionalProperties": false
}
```
---
## Appendix: Implementation Rules
1. Always return the envelope with `ok`, `code`, `meta{requestId,schemaVersion,generatedAt}`.
2. Exactly one of `data` or `error`.
3. On error, set HTTP 4xx/5xx and embed problem object under `error`.
4. Choose one pagination mode per list endpoint; document it.
5. If endpoint is idempotent, require `Idempotency-Key` and echo in `meta`.
6. Writes return `meta.etag`; honor `If-Match`.
7. `ui` is optional and ignorable by clients.