Skip to content

Give errors stable machine-readable codes, additively #6

Description

@OliverD25

Errors are {"detail": "..."} — a human-readable string. Exactly one endpoint
returns something a caller can branch on:
src/crypto_processing_api/api/deposits.py:168 answers a pooled-address
exhaustion with

{"detail": {"code": "DEPOSIT_TEMPORARILY_UNAVAILABLE", "message": "..."}}

Everywhere else, a platform that wants to tell "below the asset minimum" from
"destination address is invalid" — both 422 — has to match on English prose
that is not part of any contract and that changes whenever somebody improves a
sentence.

What to build

A small catalogue of stable error codes, applied consistently.

This must be additive. detail keeps working exactly as it does today for
every existing caller. The wire format is pinned by
tests/integration/test_wire_bytes.py against a committed byte corpus, and
keeping that test green is as much the acceptance criterion here as any new
test. Adding a sibling field or a header is fine; changing the shape of
detail on an endpoint that returns a string today is not.

Things to work out first

  • Where the code goes. A code beside detail changes the envelope shape
    for every error. An X-Error-Code header changes no bytes in any body at
    all. Both are defensible — argue for one in this issue before writing much
    code.
  • How many codes. Small enough to memorize. Roughly: unknown asset,
    unknown resource, insufficient balance, below minimum, dust, invalid
    destination, idempotency conflict, idempotency body mismatch, upstream
    unavailable, upstream refused. Resist one code per message.
  • They belong in the OpenAPI document, which is committed at
    docs/reference/openapi.json and regenerated by
    python scripts/export_openapi.py. A code an SDK cannot see is a code
    nobody uses. src/crypto_processing_api/api/schemas.py holds the existing
    error models and the error_responses() helper.

Scope

API surface only. The exceptions raised in services/ already distinguish
these cases by type — this is about carrying that distinction across the
boundary, not about changing any of it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: apiHTTP surface: routers, schemas, middlewaregood first issueGood for newcomers

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions