Scope: Frontend API client evolution — typed error handling, smart retry policy, response mapping compatibility, and deploy-safe change procedures. Covers the
ApiClientclass infrontend/src/lib/api-client.ts.
The frontend communicates with the SplitNaira backend exclusively through
ApiClient. API evolution changes (new endpoints, response shape changes,
error contract updates) must preserve backward compatibility and keep the
onboarding and split management flows uninterrupted.
All non-2xx responses now throw ApiError instead of a plain Error.
Consumers can branch on typed helpers rather than parsing message strings.
import { ApiError } from "@/lib/api";
try {
await client.getSplit(projectId);
} catch (err) {
if (err instanceof ApiError) {
if (err.isNotFound) { /* show "project not found" UI */ }
if (err.isUnauthorized) { /* prompt wallet reconnect */ }
if (err.isServerError) { /* show retry banner */ }
console.log(err.code); // e.g. "project_exists", "validation_error"
}
}| Property | Type | Description |
|---|---|---|
status |
number |
HTTP status code |
message |
string |
Human-readable message (from server body or fallback) |
code |
string | undefined |
Machine-readable error code from error or code field |
isClientError |
boolean |
status >= 400 && status < 500 |
isServerError |
boolean |
status >= 500 |
isNotFound |
boolean |
status === 404 |
isUnauthorized |
boolean |
status === 401 || status === 403 |
ApiClient uses withRetry with a shouldRetry predicate to avoid
retrying errors that will never succeed:
| Error type | Retried? | Rationale |
|---|---|---|
| Network failure / timeout | ✅ Yes (3×) | Transient — worth retrying |
| 5xx Server Error | ✅ Yes (3×) | Server-side transient failure |
| 429 Too Many Requests | ✅ Yes (3×) | Rate limit — back off and retry |
| 4xx Client Error (400, 401, 403, 404, 422…) | ❌ No | Bad request — retrying won't help |
This prevents unnecessary load on the backend and faster error surfacing to the user for client errors.
ApiError instances are captured with enriched tags:
section: "api-client"
path: "/splits/admin/status"
httpStatus: "400"
errorCode: "validation_error"
This allows filtering Sentry issues by HTTP status or backend error code without parsing message strings.
mapProjectToCamelCase in api-client.ts handles both camelCase and
snake_case field names from the backend. This ensures the frontend remains
functional during a backend field-naming migration:
projectId: p.projectId ?? p.project_id
projectType: p.projectType ?? p.project_type
totalDistributed: p.totalDistributed ?? p.total_distributed
distributionRound: p.distributionRound ?? p.distribution_round
collaborators[].basisPoints: c.basisPoints ?? c.basis_pointsWhen removing snake_case support: coordinate with the backend team, verify the backend has shipped camelCase responses to production, then remove the fallback in a separate PR.
- Add the method to
ApiClientinapi-client.ts. - Export a convenience function from
api.ts. - Add a test in
frontend/src/__tests__/api-evolution.test.tscovering:- Happy path response mapping
- Error body parsing (4xx with
message+errorfields) - Retry behaviour (does it retry on 5xx? does it not retry on 4xx?)
- Run
npm run test -w frontendandnpm run build -w frontend.
| Change type | Safe? | Action required |
|---|---|---|
| Add optional field | ✅ Yes | No frontend change needed |
| Rename field | Add fallback in mapProjectToCamelCase; remove after backend ships |
|
| Remove field | Add ?? defaultValue guard in mapping; coordinate deploy order |
|
| Change field type | ❌ Breaking | Requires coordinated backend + frontend deploy |
-
npm run test -w frontendpasses (includingapi-evolution.test.ts) -
npm run build -w frontendpasses -
npm run lint -w frontendpasses with zero warnings - New
ApiErrorusages in components handleisNotFoundandisServerError - No
process.envreads bypassinggetEnv()introduced
If an API evolution change breaks the frontend:
- Revert the frontend deploy (Vercel / Render → previous deployment).
- If the backend shipped a breaking response change simultaneously, revert the backend deploy first (see Mainnet Launch Runbook).
- Verify the split list loads:
GET /splitsreturns projects and the Projects tab renders without errors.
| Change | Downtime | Risk |
|---|---|---|
Add ApiError classification |
None | Low — additive only |
| Smart retry predicate | None | Low — reduces unnecessary retries |
| Response field rename with fallback | None | Low — dual-read during transition |
| Remove snake_case fallback | None | Medium — coordinate with backend |