Skip to main content

Operator API

The Operator API is the primary surface that powers the Nexa operations console. It is an API Composition / Backend-For-Frontend (BFF) that aggregates per-domain services (cases, booking, policies, wallet, notifications, audit, …) into a single operator-shaped surface, plus a real-time WebSocket presence channel for the operator swarm.

Base URL: https://na.api.nexastudio.io (or your regional equivalent). Audience: https://api.nexa/v1. Auth: OIDC JWT with https://nexa/airline, https://nexa/role, https://nexa/operator_urn claims. See Authentication.

Roles

RoleInherits
ADMINOPS_SUPERVISOR, AIRPORT_OPERATOR, FINANCE_AUDIT
OPS_SUPERVISORAIRPORT_OPERATOR
AIRPORT_OPERATOR
FINANCE_AUDIT— (parallel; read-only)

Per-route role requirements are declared at the proxy edge; the role symbol below is the minimum role required (e.g., AIRPORT_OPERATOR+ means the role plus anything that inherits from it).

REST endpoints

Cases

MethodPathMin roleNotes
POST/v1/casesOPS_SUPERVISOR+Manual disruption declaration
GET/v1/casesAIRPORT_OPERATOR+Cursor-paginated; filter by status, airport, from, to
GET/v1/cases/dashboardAIRPORT_OPERATOR+Aggregated dashboard view (composition route)
GET/v1/cases/{case_urn}AIRPORT_OPERATOR+Case detail
GET/v1/cases/{case_urn}/manifestAIRPORT_OPERATOR+Paginated sub-cases for the case
POST/v1/cases/{case_urn}/passenger-groupsAIRPORT_OPERATOR+Group passengers for allocation
POST/v1/cases/{case_urn}/allocateOPS_SUPERVISOR+Composition: validates via cases, dispatches to allocation
POST/v1/cases/{case_urn}/book-roomsOPS_SUPERVISOR+Composition: validates + dispatches to booking

Sub-cases (the saga unit)

MethodPathMin roleNotes
POST/v1/subcases/{sub_case_urn}/lockAIRPORT_OPERATOR+Acquire entity lock; returns fencing token
POST/v1/subcases/{sub_case_urn}/unlockAIRPORT_OPERATOR+Explicit release
POST/v1/subcases/{sub_case_urn}/compensateAIRPORT_OPERATOR+Submit compensation. Returns 202 Accepted in under 50 ms; saga runs async.

The lock surface is detailed in Operational Panic § Entity locks.

Hotels & booking

MethodPathMin roleNotes
POST/v1/hotels/searchAIRPORT_OPERATOR+Scatter-gather across vendors with 2 s time-box
GET/v1/hotels/listAIRPORT_OPERATOR+Curated catalog
GET/v1/hotels/cases/{case_urn}AIRPORT_OPERATOR+Hotels assigned to a case
GET/v1/booking/cases/{case_urn}/reservationsAIRPORT_OPERATOR+
GET/v1/booking/reservations/{reservation_urn}AIRPORT_OPERATOR+
GET/v1/booking/reservations/{reservation_urn}/voucher/htmlAIRPORT_OPERATOR+Rendered voucher (printable)
POST/v1/booking/manual-reservationsOPS_SUPERVISOR+Out-of-band manual booking
GET/v1/booking/manual-reviewOPS_SUPERVISOR+Review queue
POST/v1/booking/manual-review/{review_urn}/{assign|resolve|escalate}OPS_SUPERVISOR+Operator decisions on the review queue

Policies

MethodPathMin roleNotes
POST/v1/policiesOPS_SUPERVISOR+Create policy (manually-authored)
POST/v1/policies/generate-aiOPS_SUPERVISOR+AI-driven synthesis from natural-language description
GET/v1/policies/activeAIRPORT_OPERATOR+List active policies for the tenant
GET/v1/policies/resolveAIRPORT_OPERATOR+"Which policy applies here?" preview
POST/v1/policies/{policy_urn}/activateADMINAtomic activate (deactivates the previous active for the same scope)
POST/v1/policies/{policy_urn}/patchOPS_SUPERVISOR+Bumps version
POST/v1/policies/{policy_urn}/deactivateADMINArchive

Demand & allocation

MethodPathMin roleNotes
POST/v1/demand/case/{case_urn}AIRPORT_OPERATOR+Request rooms (INITIAL or incremental)
POST/v1/demand/{demand_urn}/approveOPS_SUPERVISOR+Wave approval
POST/v1/demand/{demand_urn}/rejectOPS_SUPERVISOR+
GET/v1/demand/pending/approvalsOPS_SUPERVISOR+Approval queue
POST/v1/allocation/allocateOPS_SUPERVISOR+Trigger allocation explicitly

Contracts (direct hotel deals)

MethodPathMin roleNotes
GET/v1/contractsOPS_SUPERVISOR+List active contracts
POST/v1/contractsOPS_SUPERVISOR+Create / update direct contract
POST/v1/contracts/analyze-pdfOPS_SUPERVISOR+AI wizard — extract contract terms from PDF or image

Audit & notifications

MethodPathMin roleNotes
GET/v1/audit/case/{case_urn}FINANCE_AUDIT, OPS_SUPERVISOR+Read-only audit log
GET/v1/audit/entity/{entity_urn}FINANCE_AUDIT, OPS_SUPERVISOR+
GET/v1/notifications/case/{case_urn}AIRPORT_OPERATOR+Per-case notification log
POST/v1/notifications/{case_urn}/sendAIRPORT_OPERATOR+Manual send (e.g., resend voucher)

Exception agent (LLM)

MethodPathMin roleNotes
POST/v1/agent/triageOPS_SUPERVISOR+Triage a stuck case — classify cause, propose next-best-action
POST/v1/agent/{agent_action_urn}/{approve|reject|execute}OPS_SUPERVISOR+Operator-in-the-loop

The agent is constrained to an explicit tool allow-list. It cannot perform irreversible operations (booking confirmation, payment capture) on its own — every action requires operator approval. See Exception Agent.

Airports

MethodPathMin roleNotes
GET/v1/airports/searchAIRPORT_OPERATOR+Airport catalog with coordinates

Orchestration progress

MethodPathMin roleNotes
GET/v1/orchestration/cases/{case_urn}/progressAIRPORT_OPERATOR+Runtime progress of saga steps for the case

Transport

MethodPathMin roleNotes
POST/v1/transport/cases/{case_urn}/transfers/requestAIRPORT_OPERATOR+Request ground transport (Uber / Cabify)

WebSocket presence channel

The operator UI uses a Socket.IO connection at wss://na.api.nexastudio.io/ws/cases to subscribe to real-time state changes.

Authentication

Pass the operator access token in the auth payload during the Socket.IO handshake:

import { io } from 'socket.io-client';

const socket = io('wss://na.api.nexastudio.io', {
path: '/ws/cases',
auth: { token: accessToken },
transports: ['websocket'],
});

The token is validated at the upgrade. Failures close the socket with code 4401.

Events

EventDirectionPayloadMeaning
case:updatedserver → client{ caseUrn, status, … }Case state changed (any sub-case transition fans up)
subcase:state-changedserver → client{ subCaseUrn, status, lockedBy? }Sub-case transition or lock change
graph:step:startedserver → client{ caseUrn, stepUrn, name, startedAt }Saga step started
graph:step:completedserver → client{ caseUrn, stepUrn, completedAt, result }Saga step completed
graph:step:progressserver → client{ caseUrn, stepUrn, progress, message? }Long-running step heartbeat
notification:dispatchedserver → client{ caseUrn, channel, recipient }Voucher dispatched on any channel

Lock state in the UI

When an operator on another browser locks a sub-case you are watching, you receive subcase:state-changed with lockedBy: "urn:user:auth0|…". Render a 🔒 indicator. When that operator's WebSocket drops, you receive a second event with lockedBy: null within 50–200 ms. There is no need to poll.

Reconnection

The Socket.IO client handles reconnect automatically. On reconnect, the server replays missed case:updated events for rooms you were subscribed to over the last 60 seconds.

Idempotency

Every POST accepts an Idempotency-Key header (UUID v4 recommended). Replays return the same response and status code without re-executing the side effect. Idempotency state is retained for 24 hours.

The compensate endpoint additionally deduplicates by the operator-supplied compensation payload's hash — replays return 202 with the same correlationUrn.

Pagination

Cursor-based:

GET /v1/cases?limit=50&cursor=eyJzdGFydCI6Mn0%3D
{
"items": [],
"nextCursor": "eyJzdGFydCI6NTJ9",
"hasMore": true
}

Pages are stable: a cursor returned at time T produces the same page at time T + δ even if new items are inserted, because cursors encode a snapshot offset.

Errors

RFC 7807 Problem Details. The instance field carries the W3C correlation URN. The most operationally important codes:

  • 409 Conflict on POST .../lock — sub-case is already locked. Body includes lockedBy and expiresInSeconds.
  • 409 Conflict on POST .../compensate — OCC version mismatch (someone else already moved the sub-case). Body includes the current version.
  • 503 Service Unavailable — downstream circuit open. Body includes failedDomain (e.g., booking). Honor Retry-After: 30. Do not auto-retry — the circuit breaker is what prevents a vendor outage from cascading.

Composition routes

A handful of routes fan out to multiple backends and join the responses (e.g., GET /v1/cases/dashboard, POST /v1/cases/{urn}/allocate). Properties:

  • Concurrent fan-out only. All sub-calls go in flight simultaneously.
  • Per-call timeout: 3 seconds. A failed sub-call returns a partial-failure section in the response with the failed domain identified — the rest of the response still renders.
  • No business logic in the join. The proxy assembles, the originating domain decides.

Where to next

Was this helpful?