Technical reference
Mental model
| Concept | Description |
|---|---|
| Session | One conversation thread; bound to an agent profile snapshot at creation |
| Turn | POST /v1/sessions/{id}/messages — one user input, internal tool loop, assistant output |
| client_id | Tenant scope for sessions, memory, credentials (from API key allow-list) |
| Trajectory | Append-only audit log of turns, tool calls, and system events |
Orbita is API-first: orchestrators (Cursor, custom agents, cron) call HTTP; there is no required human chat UI.
Authentication
Every protected route:
Authorization: Bearer orb_...
x-orbita-client-id: my-project
Admin routes (/v1/admin/*, /admin) use header x-orbita-admin-token: <ORBITA_ADMIN_TOKEN> — not Authorization: Bearer.
Pre-issued API keys support an optional key label (short name) and purpose in the admin console and POST/PATCH /v1/admin/api-keys; GET /v1/whoami returns key_label for the caller's own key.
Agent profiles
Static bundles of system prompt, skills, and allowed tools. List:
GET /v1/profiles
Important: profile snapshot is fixed when the session is created. After changing profiles or tools, create a new session.
Built-in profiles include default, research, and marketing.
Memory
- Session context: messages + optional compression summary
- Client memory: key/value store with pgvector semantic retrieval (
PUT/GET /v1/memories/{key}) - Tools:
memory_put,memory_geton supported profiles
Tools & HTTP policy
Tools are registered per profile. HTTP tools respect:
- Deployment allow-list (
ORBITA_HTTP_ALLOWED_DOMAINSor admin settings) - Per-client credentials vault for outbound API calls
Scheduler
Cron and webhook jobs can deliver payloads or run agent_message turns on bound sessions.
Inbound email endpoint
Not for human command-by-email. Adapter-only:
POST /v1/inbound/email
x-orbita-inbound-token: <shared secret>
{
"from": "[email protected]",
"to": "[email protected]",
"subject": "...",
"text": "...",
"message_id": "optional"
}
Requires ORBITA_INBOUND_EMAIL_TOKEN on the API host.
OpenAPI
Machine-readable spec: GET /v1/openapi.json (public, no auth). The document covers caller-facing routes only; /v1/admin/* and /v1/inbound/* are intentionally omitted from the spec.
Compare with live health version: GET /v1/health
MCP (/v1/mcp)
Streamable HTTP MCP for agents. Caller auth matches REST (Authorization + x-orbita-client-id).
With tickets disabled (default), the server exposes 16 tools: orbita_whoami, memory_*, note_*, portfolio_brief, trigger_automation, and read-only github_* helpers. When ORBITA_TICKETS_ENABLED=1, the same 16 tools remain unchanged and additional ticket_* tools are registered (see Ticket executors).
Executor safety rules and daily loop: Ticket executors.
MCP access log
Every /v1/mcp request writes one info line, mcp request: HTTP method and status, JSON-RPC method names, and for each response message whether it is a result or an error (error code and message, at most 300 characters). tools/list results include the tool count only. GET SSE streams log status and headers and do not read the body. The line never includes Authorization, tokens, params, tool arguments, or result bodies. ORBITA_MCP_ACCESS_LOG defaults on; set it to 0 to disable without a code deploy (1 or unset keeps it on; any other value fails startup).
Tickets lane (optional, ORBITA_TICKETS_ENABLED)
The ticket system (mandate → epic → task hierarchy, leases, idempotency) ships off by default. When ORBITA_TICKETS_ENABLED is unset or not 1, the API applies only init.sql — no tickets tables and no /v1/tickets routes.
When the flag is 1 at migration time, startup also runs apps/orbita-api/migrations/optional-tickets.sql (mirrored from packages/lane-tickets/drizzle/). DDL is additive (IF NOT EXISTS only). Production enable is a founder decision (D-005); verify with bash scripts/e2e-tier-a.sh (GitHub CI) before flipping Zeabur env.
Postgres repository (PgTicketRepository): writes for a client_id are serialised with pg_advisory_xact_lock(hashtextextended(client_id, 0)) inside a short transaction (transaction-scoped only — never session-level advisory locks). The implementation loads the tenant into memory, runs the same transition engine as the fake repository, and flushes only mutated rows. This whole-tenant pattern is intended for one tenant per server at modest scale (hundreds of tickets per tenant). A per-row SQL repository is the upgrade path if tenants grow large or hot.
Proposals (ticket_propose / ticket_resolve): Bot suggestions escalate as decision tickets with decision_class=proposal (not a new role or task type). Charter proposal_policy and max_open_proposals gate who may resolve which proposal_type and how many open proposals a mandate may hold. Proposal text is returned to founder/integrator readers with an untrusted_author marker; executors only see proposals in their mandate subtree.
Roles (REST / MCP): Orbita derives founder, integrator, or executor from the authenticated key (deriveTicketActor). ORBITA_TICKETS_FOUNDER_KEY_IDS and ORBITA_TICKETS_INTEGRATOR_KEY_IDS are comma-separated API key id lists; ORBITA_TICKETS_APPROVER_KEY_IDS is a deprecated alias of the founder list when founder ids are unset. All other keys are executors; bind them to mandates with ORBITA_TICKETS_KEY_MANDATES (JSON map api_key_id → mandate uuid array). Executors cannot create or modify mandates or charters. Epic ticket_approve runs structural precheckEpic; integrators require precheck.ok, founders may pass override_precheck. Invalid JSON for key mandates fails API startup when ORBITA_TICKETS_ENABLED=1. Request bodies never include actor.
Exception tasks: Charter field exception_types allows task_class=exception tasks parented directly on an active mandate (no epic). They always require integrator ticket_review after auto-approve rules run. Caps: per-type max_open and mandate hard_limits.max_open_exceptions.
REST surface: GET/POST /v1/tickets, GET /v1/tickets/{id}, and transition endpoints mirror MCP verbs. List filters support mandate subtree queries used by executors and integrators.