# ADR 0005: API Error Envelope and Idempotency Response Contract

- Status: Accepted
- Date: 2026-03-11
- Owners: API Platform Team, Architecture Group
- Related:
  - `docs/adr/0001-app-boundaries.md`
  - `docs/adr/0004-idempotency-and-locking-strategy.md`
  - `docs/non-functional-slo.md`
  - `docs/phase-tracker.md`

## Context
Clients, gateway policies, and internal services need deterministic retry behavior. Without a standardized error envelope and idempotency response contract, retries can become unsafe and client implementations diverge.

This ADR defines one API response model and status semantics for money movement and other write endpoints.

## Decision
Adopt a canonical JSON error envelope and explicit idempotency-aware response semantics for all public/internal APIs served by `wallet_web`.

All write endpoints must:
1. Accept and validate `Idempotency-Key` (where required by domain policy).
2. Return deterministic response codes and envelope fields for duplicate/retry cases.
3. Include traceability identifiers in success and error responses.

## Canonical Error Envelope
```json
{
  "error": {
    "code": "TRANSFER_CONFLICT",
    "message": "Transfer state transition conflict",
    "category": "business",
    "retryable": false,
    "details": {
      "transfer_id": "trf_123",
      "state": "completed"
    }
  },
  "meta": {
    "request_id": "req_...",
    "correlation_id": "corr_...",
    "idempotency_key": "idem_...",
    "timestamp": "2026-03-11T10:30:00Z"
  }
}
```

Envelope requirements:
- `error.code`: stable machine-readable identifier.
- `error.category`: one of `validation|business|auth|rate_limit|dependency|internal`.
- `error.retryable`: explicit retry guidance.
- `meta.request_id` and `meta.correlation_id`: always included.
- `meta.idempotency_key`: included for write endpoints when provided.

## Success Envelope (Write Endpoints)
```json
{
  "data": {
    "transfer_id": "trf_123",
    "status": "completed"
  },
  "meta": {
    "request_id": "req_...",
    "correlation_id": "corr_...",
    "idempotency_key": "idem_...",
    "idempotency_replayed": false,
    "timestamp": "2026-03-11T10:30:00Z"
  }
}
```

Success metadata:
- `idempotency_replayed=true` when returning prior result for duplicate request.

## HTTP Status and Idempotency Semantics
Write endpoints standard behavior:
1. First-time accepted and completed:
- `200` or `201` with `idempotency_replayed=false`.

2. Duplicate with same key and same payload:
- Return prior semantic result (`200`/`201`) with `idempotency_replayed=true`.

3. Same key with different payload hash:
- `409 Conflict` with `error.code=IDEMPOTENCY_KEY_PAYLOAD_CONFLICT`.

4. Missing required idempotency key:
- `400 Bad Request` with `error.code=IDEMPOTENCY_KEY_REQUIRED`.

5. In-progress request for same key:
- `409 Conflict` or `425 Too Early` (gateway policy dependent) with `error.retryable=true`.
- Recommended code: `409` for broad client compatibility.

6. Lock contention / transition race:
- `409 Conflict` with `error.code=STATE_TRANSITION_CONFLICT` and `retryable` as policy indicates.

7. Rate limited:
- `429 Too Many Requests`, include retry guidance headers.

## Required Response Headers
- `X-Request-ID`
- `X-Correlation-ID`
- `Idempotency-Key` (echo when present)
- `Retry-After` for rate limits or temporary contention where applicable

## Error Code Registry (Initial)
Validation:
- `INVALID_REQUEST`
- `IDEMPOTENCY_KEY_REQUIRED`
- `UNSUPPORTED_CURRENCY`

Business:
- `INSUFFICIENT_FUNDS`
- `TRANSFER_LIMIT_EXCEEDED`
- `STATE_TRANSITION_CONFLICT`
- `ACCOUNT_FROZEN`
- `IDEMPOTENCY_KEY_PAYLOAD_CONFLICT`

Auth/Security:
- `UNAUTHORIZED`
- `FORBIDDEN`
- `OTP_REQUIRED`
- `OTP_INVALID`

Dependency/Internal:
- `UPSTREAM_TIMEOUT`
- `UPSTREAM_UNAVAILABLE`
- `INTERNAL_ERROR`

## Client Retry Contract
Safe retry conditions:
- network timeout/transport failure with same idempotency key
- `error.retryable=true`
- `429` with `Retry-After`

Unsafe retry conditions:
- validation errors
- `IDEMPOTENCY_KEY_PAYLOAD_CONFLICT`
- explicit non-retryable business failures unless user action changes state

## Observability and Audit
- Log all error responses with `error.code`, `retryable`, `request_id`, `correlation_id`.
- Metric labels must include status code, error code, category, retryable flag.
- Track replay ratio (`idempotency_replayed=true`) by endpoint.

## Governance Rules
1. New error codes require registry update and changelog entry.
2. Backward-incompatible envelope changes require API version bump.
3. Public API docs and contract tests must be updated for any semantic change.

## Test Requirements
1. Contract tests for all standard error codes and envelope fields.
2. Replay tests for duplicate write requests with same key.
3. Conflict tests for same key with different payload.
4. Header propagation tests for request/correlation/idempotency IDs.

## Consequences
Positive:
- Predictable client behavior and safer retries.
- Consistent observability and debugging across domains.
- Reduced accidental duplicate operations.

Trade-offs:
- Stricter API governance overhead.
- Client adaptation effort to consume normalized envelopes.

## Acceptance Criteria
1. All write endpoints emit canonical success/error envelopes.
2. Duplicate same-key same-payload requests return replayed semantic result.
3. Same-key different-payload requests return deterministic conflict.
4. Contract tests enforce envelope schema and header behavior in CI.
