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, LRObulkidempotencyETag.
  • JSON keys use camelCase.

1. Envelope (all responses)

Content-Type: application/json; charset=utf-8

{
  "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

  • okcodemeta 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_TAKENORDER_OUT_OF_STOCKPAYMENT_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

{
  "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)

{
  "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

{
  "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).

{
  "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)

{
  "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)

{
  "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

{
  "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

{
  "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

{
  "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 okcode, and meta{requestId,schemaVersion,generatedAt}.
  • Exactly one of data or error.
  • If error present: must include status (matches HTTP), titletype 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

{
  "$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

{
  "$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

{
  "$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

{
  "$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 okcodemeta{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.