Skip to content

Thread the API error code through to typed AppErrors (replaces string-sniffing) #94

Open

Description

@Adron

Discovered while implementing #33, and it affects every feature module — filing it rather than
smuggling a repo-wide refactor into an unrelated PR.

The problem

Every InterlinedList API error comes back as:

{ "error": "<human message>", "code": "<snake_case_code>" }

The app throws the code away. ErrorDto in :core:network models only { "error": ... }, and
safeApiCall maps HTTP status → AppError, with the AppError subclasses in :core:common
carrying just a message.

Two concrete costs:

  1. Distinct failures collapse. :feature:ai had to define its own module-local
    AiError/AiResult (see AI: status, gating and quota plumbing (:feature:ai scaffold) #4 / PR feat(ai): :feature:ai scaffold — status, gating, quota and preview→confirm (#4) #92) purely because the shared AppError cannot tell
    quota_exceeded from rate_limited — both are HTTP 429 — nor no_provider_configured from a
    generic conflict. Every future module with meaningful error codes will face the same fork.
  2. safeApiCall currently string-sniffs. It decides SubscriptionRequired with a
    message.contains("subscription") heuristic. That is a user-visible behaviour hanging off
    English prose in a server response — it breaks the day the copy is reworded.

What to do

  • Add code to ErrorDto.
  • Widen AppError (or add a typed code alongside the message) so call sites can branch on the
    server's own vocabulary.
  • Replace the contains("subscription") heuristic with the real code.
  • Fold :feature:ai's module-local AiError back onto the shared type once it can express
    quota_exceeded / rate_limited (+ Retry-After) / no_provider_configured.

Why it is filed rather than done

It touches every AppError construction site across all feature modules, so it needs a quiet
moment with no parallel branches in flight — otherwise it conflicts with everything. Best done
after the current parity wave merges.

Observed codes so far: bad_request (rejected viewingPreference), unauthorized
(GET /api/auth/accounts to a bearer token), plus quota_exceeded, rate_limited and
no_provider_configured documented at https://interlinedlist.com/help/api/ai-integration.

Activity

  1. added
    P2Completeness / settings surface
    on Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Completeness / settings surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions