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