Evalium API Response Contract & Action Registry (v1)
This document defines the Elite API response standard for the Evalium backend.
Its purpose is to ensure that every non-trivial backend response clearly communicates:
- What happened
- Whether the user’s work is safe
- The single best next action
This allows the frontend to remain presentation-only, rendering backend intent without encoding business logic.
For canonical idempotency scope and retry semantics, see:
docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md.
0. Core Principles (Non-Negotiable)
- Backend owns intent: the frontend renders actions; it does not infer them.
- No RLS inference: RLS-denied resources are indistinguishable from non-existent ones.
- Work safety is explicit: users are never left guessing if their work is lost.
- One best action per response in v1.
- Forward-compatible: contracts can evolve without breaking deployed clients.
1. Universal Response Envelope
All API responses MUST conform to this envelope. JSON fields are snake_case everywhere.
{
"kind": "SUCCESS | ERROR | JOB | PARTIAL_SUCCESS",
"data": { ... },
"error": { ... },
"job": { ... },
"result": { ... },
"correlation_id": "uuid",
"support_ref": "EV-X9Y2"
}
1.1 Envelope Semantics
- Exactly one of
data,error,job, orresultMUST be present.- Exception: for
PARTIAL_SUCCESS,dataMAY be included if the endpoint has an established shape for partial payloads.
- Exception: for
kindis the authoritative discriminator — clients must not infer state from HTTP status alone.correlation_idis generated by middleware and used for tracing/logging.support_refis a short, user-facing reference derived fromcorrelation_id.
2. Error Object (Elite Error Spec)
Errors provide context, safety, and recovery guidance.
"error": {
"title": "Version Locked",
"code": "VERSION_LOCKED",
"category": "CONFLICT",
"message": "This evaluation version is published and cannot be edited.",
"retryable": false,
"idempotency": "NONE",
"work_state": "SAFE | LOCAL_ONLY | NOT_SAVED | UNKNOWN",
"action": { ... }
}
2.1 Required Fields
title: Short, user-facing heading for consistent UI rendering.code: Stable, machine-readable identifier (never free text).category: One of:INPUT | PERMISSION | CONFLICT | STATE | TRANSIENT | AUTH | SYSTEMretryable: Whether the exact same request can be safely retried.idempotency: One of:REQUIRED– endpoint requiresIdempotency-Key; retries must use the same key.SUPPORTED– endpoint acceptsIdempotency-Key; retry-safe behavior is implemented.NONE– retry unsafe.
For REQUIRED, the backend must reject in-scope requests missing the key.
Current backend behavior for missing key is 400 validation_error.
2.2 Idempotency conflict signaling
When keyed idempotency is in effect, error code should preserve these semantics:
idempotency_conflict– same key reused with a different logical payload.idempotency_in_progress– same key hit while original request has not finalized yet.
2.3 Work Safety (work_state)
This is not cache freshness. It describes user input safety.
| Value | Meaning |
|---|---|
SAFE | User input is persisted and durable |
LOCAL_ONLY | Input is held client-side only; do not refresh |
NOT_SAVED | Request rejected before any data was retained |
UNKNOWN | Server acknowledgement uncertain |
work_state MUST be present on any endpoint that accepts user-entered content and can fail after the user has typed (e.g., delivery answer submission, authoring saves). It MUST NOT be present on read-only GET endpoints.
3. ActionSpec (Backend → Frontend Intent)
An ActionSpec tells the frontend the single best next step.
"action": {
"type": "CREATE_NEW_VERSION",
"version": "v1",
"intent": "RECOVERY | NAVIGATION | DIAGNOSTICS",
"payload": { ... }
}
3.1 ActionSpec Fields
type: Canonical string from the Action Registry.version: Payload schema version (default:"v1").intent: UI hint only — affects presentation, not logic.payload: Typed, documented per action/version.
⚠ Rule: ActionSpec payloads must prefer IDs and semantics, not URLs. URLs may be included only as optional hints.
4. Action Registry v1 (Canonical)
This is the only set of actions the frontend is guaranteed to understand.
4.1 authoring & Concurrency (Golden Journey A)
| Type | Intent | Payload | When Emitted |
|---|---|---|---|
CREATE_NEW_VERSION | RECOVERY | { "evaluation_id", "from_version_id" } | Edit attempt on published version |
REFRESH_AND_RETRY | RECOVERY | { "message"? } | Optimistic concurrency failure |
4.2 Delivery & Runtime (Golden Journey B)
| Type | Intent | Payload | When Emitted |
|---|---|---|---|
RETRY | RECOVERY | { "retry_after_ms"? } | Transient delivery sync failure |
RESUME_SESSION | NAVIGATION | { "session_id" } | Active session already exists |
GO_TO_DASHBOARD | NAVIGATION | { "destination": "DASHBOARD" | "ASSIGNMENTS" } | Attempt closed / limit reached |
REAUTHENTICATE | RECOVERY | { "method": "magic_link" } | Auth/session expired |
4.3 Asynchronous & Batch Operations
| Type | Intent | Payload | When Emitted |
|---|---|---|---|
POLL_JOB | NAVIGATION | { "job_id" } | Async job started |
VIEW_BATCH_STATUS | NAVIGATION | { "batch_id" } | Partial remediation outcome |
DOWNLOAD_DIAGNOSTICS | DIAGNOSTICS | { "bundle_id" } | Only when a redacted, time-limited bundle exists and caller is authorised. |
4.4 Permissions & Validation
| Type | Intent | Payload | When Emitted |
|---|---|---|---|
REQUEST_ACCESS | RECOVERY | { "missing_capabilities": [...], "resource_type"?, "resource_id"?, "copy_request_text"? } | Capability-based denial |
RESOLVE_VALIDATION | RECOVERY | { "errors": [{ "path", "message" }] } | Validation failure |
5. Job & Partial Success Responses
5.1 Async Job
{
"kind": "JOB",
"job": {
"id": "job-123",
"state": "QUEUED | RUNNING | COMPLETED | FAILED"
}
}
5.2 Partial Success
{
"kind": "PARTIAL_SUCCESS",
"result": {
"succeeded": 18,
"failed": 6
}
}
HTTP 207 MAY be used but is not required. 200 OK with kind: "PARTIAL_SUCCESS" is also acceptable.
6. Security Guardrail: No RLS Inference
Rule If a database query returns zero rows due to RLS, the API MUST respond identically to a true “not found”.
- Response:
404 Not Found - No ActionSpec implying permission or org mismatch
- No side-channel signalling
This prevents tenant and resource enumeration.
7. Implementation Requirements (Backend)
- Correlation Middleware: Inject
correlation_idinto request context and response. - Central Response Writer: All handlers use a single
writeResponse(...)helper. - Layered Error Mapping: Map errors from specific to general: Domain → Auth → Validation → Constraint → Fallback (generic system error).
- Typed Service Errors: Services must return typed errors (e.g.
ErrVersionLocked,ErrAttemptClosed) to enable precise mapping. - No Raw Errors: Handlers must never serialize raw
errorvalues to the client.
8. Governance Rule (Mandatory)
Any new feature or endpoint that accepts user input, mutates state, or affects scores/submissions MUST:
- Define domain error types.
- Map them to the contract in the central response writer.
- Specify
work_stateon relevant error responses. - Classify idempotency scope against
docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md. - For in-scope durable writes, enforce
Idempotency-Keyand replay/conflict semantics. - Use only registry-approved
ActionSpectypes.
PRs that violate this may be rejected.
9. Summary
✔ Backend-driven recovery ✔ Explicit work safety ✔ Secure multi-tenant behaviour ✔ Forward-compatible contract ✔ Predictable, dumb frontend