- task-discipline: require explicit target platform/language before implementation - go-api: bounded context deadline on every outbound network/DB call - controls-selection: visible busy state for any control triggering such a call - import-export: enumerate real format variants before writing a parser - app-binary: embedded assets do not hot-reload; verify rebuild before diagnosing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.3 KiB
3.3 KiB
Contract: REST API Conventions (Go Web Applications)
Version: 1.2
URL Naming
- Resources are plural nouns:
/api/assets,/api/components,/api/pricelists. - Nested resources use parent path:
/api/assets/:id/components. - Actions that are not CRUD use a verb suffix:
/api/pricelists/:id/export-csv,/api/tasks/:id/cancel,/api/sync/push. - No verbs in resource paths: use
/api/assets+ DELETE, not/api/delete-asset.
HTTP Methods and Status Codes
| Operation | Method | Success | Notes |
|---|---|---|---|
| List | GET | 200 | |
| Get one | GET | 200 / 404 | |
| Create | POST | 201 | Return created resource |
| Update | PUT / PATCH | 200 | Return updated resource |
| Delete / Archive | DELETE | 200 or 204 | |
| Async action | POST | 202 | Return {task_id} |
| Validation error | POST/PUT | 422 | Return field errors |
| Server error | any | 500 | Log full error server-side |
| Not found | any | 404 | |
| Unauthorized | any | 401 |
- Never return
200 OKfor validation errors — use422 Unprocessable Entity. - Never return
200 OKwith{"error": "..."}in the body — use the correct status code.
Error Response Format
All non-2xx responses return a consistent JSON body:
{
"error": "human-readable message",
"fields": {
"serial_number": "Serial number is required",
"price": "Must be greater than 0"
}
}
error— always present, describes what went wrong.fields— optional, present only for validation errors (422). Keys match form field names.- Never expose raw Go error strings, stack traces, or SQL errors to the client.
List Response Format
{
"items": [...],
"total_count": 342,
"page": 2,
"per_page": 50,
"total_pages": 7
}
- Always include pagination metadata even if the client did not request it.
itemsis always an array — nevernull, use[]for empty.
Request Conventions
- Accept
application/jsonfor API endpoints. - HTML form submissions (htmx) use
application/x-www-form-urlencodedormultipart/form-data. - File uploads use
multipart/form-data. - Query parameters for filtering and pagination:
?page=1&per_page=50&search=abc&status=active.
Timeouts
- Every outbound network or database call must run under a bounded
context.Contextdeadline. No call reachable from a user action may block indefinitely. - The deadline is set by the handler or service that owns the request, not by the caller of the app.
- On timeout, return a real error status (
504for an upstream timeout,500for an internal one) with a human-readable message. Never let a timed-out call fall through as an empty success. - The UI side of the same rule — visible busy state for the duration of the call — is owned by
controls-selection.
Health and Utility Endpoints
Every application must expose:
GET /health → 200 {"status": "ok"}
GET /api/db-status → 200 {"ok": true} or 500 {"ok": false, "error": "..."}
/healthmust respond even if DB is down (for load balancer probes)./api/db-statuschecks the actual DB connection and returns its state.
Async Actions
Long-running operations return 202 {"task_id": "..."} immediately and the client polls
GET /api/tasks/:id. Full flow, statuses, and task struct: go-background-tasks contract.