Skip to main content

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:

  1. What happened
  2. Whether the user’s work is safe
  3. 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, or result MUST be present.
    • Exception: for PARTIAL_SUCCESS, data MAY be included if the endpoint has an established shape for partial payloads.
  • kind is the authoritative discriminator — clients must not infer state from HTTP status alone.
  • correlation_id is generated by middleware and used for tracing/logging.
  • support_ref is a short, user-facing reference derived from correlation_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 | SYSTEM
  • retryable: Whether the exact same request can be safely retried.
  • idempotency: One of:
    • REQUIRED – endpoint requires Idempotency-Key; retries must use the same key.
    • SUPPORTED – endpoint accepts Idempotency-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.

ValueMeaning
SAFEUser input is persisted and durable
LOCAL_ONLYInput is held client-side only; do not refresh
NOT_SAVEDRequest rejected before any data was retained
UNKNOWNServer 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)​

TypeIntentPayloadWhen Emitted
CREATE_NEW_VERSIONRECOVERY{ "evaluation_id", "from_version_id" }Edit attempt on published version
REFRESH_AND_RETRYRECOVERY{ "message"? }Optimistic concurrency failure

4.2 Delivery & Runtime (Golden Journey B)​

TypeIntentPayloadWhen Emitted
RETRYRECOVERY{ "retry_after_ms"? }Transient delivery sync failure
RESUME_SESSIONNAVIGATION{ "session_id" }Active session already exists
GO_TO_DASHBOARDNAVIGATION{ "destination": "DASHBOARD" | "ASSIGNMENTS" }Attempt closed / limit reached
REAUTHENTICATERECOVERY{ "method": "magic_link" }Auth/session expired

4.3 Asynchronous & Batch Operations​

TypeIntentPayloadWhen Emitted
POLL_JOBNAVIGATION{ "job_id" }Async job started
VIEW_BATCH_STATUSNAVIGATION{ "batch_id" }Partial remediation outcome
DOWNLOAD_DIAGNOSTICSDIAGNOSTICS{ "bundle_id" }Only when a redacted, time-limited bundle exists and caller is authorised.

4.4 Permissions & Validation​

TypeIntentPayloadWhen Emitted
REQUEST_ACCESSRECOVERY{ "missing_capabilities": [...], "resource_type"?, "resource_id"?, "copy_request_text"? }Capability-based denial
RESOLVE_VALIDATIONRECOVERY{ "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)​

  1. Correlation Middleware: Inject correlation_id into request context and response.
  2. Central Response Writer: All handlers use a single writeResponse(...) helper.
  3. Layered Error Mapping: Map errors from specific to general: Domain → Auth → Validation → Constraint → Fallback (generic system error).
  4. Typed Service Errors: Services must return typed errors (e.g. ErrVersionLocked, ErrAttemptClosed) to enable precise mapping.
  5. No Raw Errors: Handlers must never serialize raw error values 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_state on relevant error responses.
  • Classify idempotency scope against docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md.
  • For in-scope durable writes, enforce Idempotency-Key and replay/conflict semantics.
  • Use only registry-approved ActionSpec types.

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