# Archive a version Source: https://docs.aui.io/api-reference/agent-versions/archive-a-version https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/agents/{agentId}/versions/{versionId}/archive Archive a version to retire it from everyday use. The agent's live version can't be archived — publish another version first. # Create a draft version Source: https://docs.aui.io/api-reference/agent-versions/create-a-draft-version https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/agents/{agentId}/versions Create a new draft version — empty, from a template, or cloned from an existing version (``source`` selects which). Drafts stay editable until published. # List versions Source: https://docs.aui.io/api-reference/agent-versions/list-versions https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/agents/{agentId}/versions List an agent's versions, newest first. Filter by status, tag, label, or version number; set ``exclude_revisions`` to see only base versions. # Publish a version Source: https://docs.aui.io/api-reference/agent-versions/publish-a-version https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/agents/{agentId}/versions/{versionId}/publish Make this version the agent's live version. Publishing a draft freezes it first; publishing an already-published version re-activates it — the same call handles shipping, switching, and rolling back. # Pull a configuration bundle Source: https://docs.aui.io/api-reference/agent-versions/pull-a-configuration-bundle https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/agents/{agentId}/versions/{versionId}/pull Download a version's configuration bundle — the current revision, or a specific one via ``version_tag``. # Push a configuration bundle Source: https://docs.aui.io/api-reference/agent-versions/push-a-configuration-bundle https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/agents/{agentId}/versions/{versionId}/push Upload a configuration bundle to a version, committing a new revision. The response carries the new revision's tag and content digest. # Update a version Source: https://docs.aui.io/api-reference/agent-versions/update-a-version https://api-v3.aui.io/apollo-api/openapi.json patch /management/v1/agents/{agentId}/versions/{versionId} Update a version's metadata: label, tags, and notes. The configuration itself changes through push, never here. # Create agent Source: https://docs.aui.io/api-reference/agents/create-agent https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/projects/{projectId}/agents Create an agent in the project. New agents start without a live version — create and publish a version to make the agent answer. # Delete an agent Source: https://docs.aui.io/api-reference/agents/delete-an-agent https://api-v3.aui.io/apollo-api/openapi.json delete /management/v1/agents/{agentId} Delete an agent and all of its versions. This cannot be undone. # Get agent usage Source: https://docs.aui.io/api-reference/agents/get-agent-usage https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/agents/{agentId}/usage Usage metrics for one agent, optionally bounded by a date range. # Get an agent Source: https://docs.aui.io/api-reference/agents/get-an-agent https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/agents/{agentId} Fetch one agent, including which version is currently live. # List agents Source: https://docs.aui.io/api-reference/agents/list-agents https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/projects/{projectId}/agents List the project's agents, optionally filtered by name. # Update an agent Source: https://docs.aui.io/api-reference/agents/update-an-agent https://api-v3.aui.io/apollo-api/openapi.json patch /management/v1/agents/{agentId} Rename an agent. re-publishes the published version with the updated name. To change which version is live, publish a version instead. # Get an access token Source: https://docs.aui.io/api-reference/auth/get-an-access-token https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/auth/token Exchange a credential for a short-lived access token (OAuth 2.0, RFC 6749). Supported grant types: `publishable_key`. Refresh and other grants will be added on the same endpoint. # Start a channel thread Source: https://docs.aui.io/api-reference/channels/start-a-channel-thread https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/channels/{channel}/threads Send the opening message on ``channel`` and bind the recipient's phone number to a conversation thread. The agent is identified by your access token. Omit ``thread_id`` to start a new thread (its id is returned); pass it to continue an existing one. Template fields apply to WhatsApp only. # Health check Source: https://docs.aui.io/api-reference/health/health-check https://api-v3.aui.io/apollo-api/openapi.json get /health Liveness probe. Returns ``{"status": "ok"}`` when the service is up. # Get follow-up suggestions Source: https://docs.aui.io/api-reference/messaging/get-follow-up-suggestions https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/followup-suggestions Generate suggested follow-up prompts from a context you provide — useful for offering the end user quick next questions. With no context, returns the agent's static suggestions instead of calling the LLM. # Get the welcome message Source: https://docs.aui.io/api-reference/messaging/get-the-welcome-message https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/welcome-message The welcome message of the agent's live version — what to show before the first user message. Pass ``placeholders`` in the body to seed ``dynamic_welcome_message``; otherwise the static ``welcome_message`` is returned. # Get trace by interaction Source: https://docs.aui.io/api-reference/messaging/get-trace-by-interaction https://api-v3.aui.io/apollo-api/openapi.json get /messaging/v1/interactions/{interactionId}/trace The reasoning trace of a single interaction, resolved by its id. # Get trace by thread Source: https://docs.aui.io/api-reference/messaging/get-trace-by-thread https://api-v3.aui.io/apollo-api/openapi.json get /messaging/v1/threads/{threadId}/trace Every interaction trace in the thread. # List thread messages Source: https://docs.aui.io/api-reference/messaging/list-thread-messages https://api-v3.aui.io/apollo-api/openapi.json get /messaging/v1/threads/{threadId}/messages The thread's full transcript, in chronological order. # Rerun an interaction Source: https://docs.aui.io/api-reference/messaging/rerun-an-interaction https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/threads/{threadId}/rerun Regenerate ``interaction_id`` on the thread against the agent's live version, then replay ``text`` onto the resulting new thread. The response's ``thread_id`` is the new thread's id, not the original. # Send a message Source: https://docs.aui.io/api-reference/messaging/send-a-message https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/messages Send a message; auto-create the thread when ``thread_id`` is omitted. For a live token stream use ``POST /messages/stream`` (SSE). The resolved thread id is returned in the body. # Stream a message Source: https://docs.aui.io/api-reference/messaging/stream-a-message https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/messages/stream Send a message and stream the reply token-by-token over Server-Sent Events. The thread is created automatically when ``thread_id`` is omitted — the resolved id arrives as the first ``thread`` event. Resume a dropped stream with the standard ``Last-Event-ID`` header: missed events replay without running the turn again. # Create project Source: https://docs.aui.io/api-reference/projects/create-project https://api-v3.aui.io/apollo-api/openapi.json post /management/v1/projects Create a project in your organization. Projects group agents. # Delete a project Source: https://docs.aui.io/api-reference/projects/delete-a-project https://api-v3.aui.io/apollo-api/openapi.json delete /management/v1/projects/{projectId} Delete a project. It disappears from listings immediately. # Get a project Source: https://docs.aui.io/api-reference/projects/get-a-project https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/projects/{projectId} Fetch one project. # Get project usage Source: https://docs.aui.io/api-reference/projects/get-project-usage https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/projects/{projectId}/usage Usage metrics aggregated across every agent in the project, optionally bounded by a date range. # List projects Source: https://docs.aui.io/api-reference/projects/list-projects https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/projects List your organization's projects. # Get a thread Source: https://docs.aui.io/api-reference/threads/get-a-thread https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/threads/{threadId} Fetch one thread's details — title, participants, and status. # Get an interaction's trace Source: https://docs.aui.io/api-reference/threads/get-an-interactions-trace https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/interactions/{interactionId}/trace The reasoning trace of a single interaction, resolved by its id. # List thread messages Source: https://docs.aui.io/api-reference/threads/list-thread-messages https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/threads/{threadId}/messages The thread's full transcript, in chronological order. # List thread traces Source: https://docs.aui.io/api-reference/threads/list-thread-traces https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/threads/{threadId}/trace The reasoning trace of every interaction in the thread — what the agent understood, which rules fired, and the decisions it took. # List threads Source: https://docs.aui.io/api-reference/threads/list-threads https://api-v3.aui.io/apollo-api/openapi.json get /management/v1/threads List your organization's conversation threads, newest first. Repeatable filters (``tool``, ``rule``, ``param``, ``created``) OR within a field and AND across fields. ``runtime_version`` other than '1' lists runtime-v2 threads instead — that engine's threads are indexed per agent, so an ``agent_id`` (or ``user_id``) filter is required there. # Update a thread Source: https://docs.aui.io/api-reference/threads/update-a-thread https://api-v3.aui.io/apollo-api/openapi.json patch /management/v1/threads/{threadId} Partial update — only ``title`` is updatable for now. Omitted fields are left unchanged; the full updated thread is returned. # Send an event to trigger the agent Source: https://docs.aui.io/api-reference/webhooks/send-an-event-to-trigger-the-agent https://api-v3.aui.io/apollo-api/openapi.json post /messaging/v1/events Enqueues an event for the agent bound to your webhook credential. Pass `text` and/or `agent_variables` as the agent's input, and an optional `thread_id` (your own conversation id) to continue an existing conversation — omit it to start a new one. A `202` means the event was durably accepted — processing happens asynchronously. # Authentication Source: https://docs.aui.io/api/authentication Exchange a publishable key for an access token, then send it as a Bearer token. The Apollo API uses short-lived **access tokens** obtained from an OAuth 2.0 token endpoint. You exchange a credential once, then send the token on every request: ```bash theme={"dark"} Authorization: Bearer ``` ## Get an access token Exchange your **publishable key** at the token endpoint. The endpoint itself is unauthenticated — the credential travels in the body: ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/auth/token" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "publishable_key", "publishable_key": "pk_..." }' ``` ```json Response theme={"dark"} { "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 3600, "agent_id": "6a344d186d3160971ec62eb6", "organization_id": "5f2b8c1a9d4e3f0012ab34cd" } ``` The token to send as `Authorization: Bearer `. Token lifetime in seconds. Request a new token when it expires — exchanges are cheap, so exchanging shortly before expiry is the simplest strategy. The agent your messaging calls will run against. The token carries the agent, so messaging requests never include an `agent_id` in the body. Your publishable key is issued in the AUI Console. Publishable keys are safe to use from client-side code — that's what makes them "publishable" — and can additionally be restricted to allowed web origins and IP ranges. ## Which credential for which surface | Surface | Paths | Accepted credentials | | ---------- | -------------------- | --------------------------------------------------------------- | | Messaging | `/messaging/v1/...` | Bearer token (from a publishable key) | | Management | `/management/v1/...` | Bearer token (from a login session) **or** organization API key | ### Organization API key (management only) Server-to-server management calls can skip the token flow and authenticate with your organization API key in a header: ```bash theme={"dark"} curl "https://api-v3.aui.io/apollo-api/management/v1/projects" \ -H "x-organization-api-key: " ``` Unlike publishable keys, the organization API key must stay server-side. Never ship it in client code. ## WebSocket authentication The WebSocket session authenticates on the upgrade request with the same Bearer token: ``` GET wss://api-v3.aui.io/apollo-api/messaging/v1/session Authorization: Bearer ``` Invalid or expired credentials close the connection with WebSocket close code `1008` (policy violation). See [WebSocket](/api/messaging/websocket). ## Failed authentication Requests without a valid credential return `401` in the standard [error envelope](/api/errors): ```json 401 theme={"dark"} { "error": { "code": "unauthorized", "message": "Missing or invalid credentials", "request_id": "req_..." } } ``` Token-endpoint failures follow the OAuth 2.0 error convention instead: ```json 400 theme={"dark"} { "error": "invalid_grant", "error_description": "invalid_publishable_key" } ``` ## Best practices Keep keys out of source control. ```bash theme={"dark"} export AUI_PUBLISHABLE_KEY="pk_..." ``` Exchange once and reuse the token until `expires_in` elapses rather than exchanging per request. If your publishable key is used from a browser, restrict it to your web origins in the Console so it can't be replayed elsewhere. # Errors Source: https://docs.aui.io/api/errors One error envelope with stable, machine-readable codes. Every Apollo API error — any surface, any status — renders as the same envelope: ```json theme={"dark"} { "error": { "code": "agents.not_found", "message": "Agent 6a344d186d3160971ec62eb6 was not found", "request_id": "req_8f14e45f", "details": [ { "field": "agentId", "message": "no agent with this id" } ] } } ``` The stable, machine-readable code your integration branches on. Codes are append-only: new codes may be added, existing ones are never removed or repurposed. A human-readable explanation for logs and debugging. **Not part of the contract** — the wording can change at any time, so never parse it. Correlation id for the request. Include it when contacting support. Optional structured sub-errors — for validation failures, one entry per offending field (`field`, `message`). ## Error codes Codes come in two levels. Every status has a **generic code**, and some failures carry a **namespaced code** that is more specific (e.g. `agents.not_found` rather than `not_found`). Always keep a default branch — the set is open and grows over time. | HTTP status | Generic code | When | | ----------- | ------------------ | ------------------------------------------------------- | | `400` | `bad_request` | The request was malformed. | | `401` | `unauthorized` | Missing or invalid credentials. | | `403` | `forbidden` | Authenticated, but not allowed to act on this resource. | | `404` | `not_found` | The resource doesn't exist. | | `409` | `conflict` | The request conflicts with current state. | | `422` | `validation_error` | A field failed validation — see `details`. | | `429` | `rate_limited` | Too many requests — back off and retry. | | `5xx` | `internal_error` | Something went wrong on our side. Retry with backoff. | ## Handling errors Branch on `error.code`, fall back on the status class: ```javascript Node.js theme={"dark"} const response = await fetch(url, options); if (!response.ok) { const { error } = await response.json(); switch (error.code) { case "unauthorized": // refresh the access token and retry break; case "validation_error": error.details?.forEach(({ field, message }) => console.error(`${field}: ${message}`) ); break; case "rate_limited": // back off and retry break; default: console.error(`${error.code}: ${error.message} (${error.request_id})`); } } ``` ```python Python theme={"dark"} response = requests.post(url, headers=headers, json=body) if not response.ok: error = response.json()["error"] if error["code"] == "unauthorized": ... # refresh the access token and retry elif error["code"] == "validation_error": for detail in error.get("details") or []: print(f"{detail.get('field')}: {detail['message']}") elif error["code"] == "rate_limited": ... # back off and retry else: print(f"{error['code']}: {error['message']} ({error.get('request_id')})") ``` ## Exceptions to the envelope Two places deliberately speak a different error dialect: * **The token endpoint** (`POST /management/v1/auth/token`) follows the OAuth 2.0 convention: `{ "error": "invalid_grant", "error_description": "..." }`. See [Authentication](/api/authentication). * **Streaming transports** deliver errors in-stream: SSE as an `error` event, WebSocket as a `type: "error"` envelope. The connection stays open where recovery is possible. See [WebSocket](/api/messaging/websocket). # Agents Source: https://docs.aui.io/api/management/agents Create and manage agents inside your projects. An **agent** is a deployed conversational AI, owned by a [project](/api/management/projects). What the agent actually does is defined by its [versions](/api/management/versions) — exactly one version is live at a time, referenced by the agent's `live_version_id`. *** ## Create an agent ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/projects/{projectId}/agents" \ -H "Content-Type: application/json" \ -H "x-organization-api-key: " \ -d '{ "name": "Order support" }' ``` ### POST `/management/v1/projects/{projectId}/agents` Agent name — 2 to 50 characters, and not only whitespace. New agents start without a live version — create and [publish a version](/api/management/versions) to make the agent answer. ```json 201 theme={"dark"} { "id": "6a344d186d3160971ec62eb6", "project_id": "5f2b8c1a9d4e3f0012ab34cd", "name": "Order support", "live_version_id": null, "created_at": "2026-07-13T14:02:11Z", "updated_at": "2026-07-13T14:02:11Z" } ``` *** ## List agents ### GET `/management/v1/projects/{projectId}/agents` Lists the project's agents as a [paginated list](/api/management/projects#pagination). Case-insensitive substring match on the agent name. ## Get an agent ### GET `/management/v1/agents/{agentId}` Returns one agent, including which version is currently live (`live_version_id`). ## Rename an agent ### PATCH `/management/v1/agents/{agentId}` The new agent name — 2 to 50 characters, and not only whitespace. Renaming is the only update here — to change which version is live, [publish a version](/api/management/versions#publish-a-version) instead. ## Delete an agent ### DELETE `/management/v1/agents/{agentId}` Deletes the agent **and all of its versions**. This cannot be undone. Returns `204`. ## Agent usage ### GET `/management/v1/agents/{agentId}/usage` Usage metrics for this agent — see [Usage](/api/management/usage). # Projects Source: https://docs.aui.io/api/management/projects Projects group your agents within an organization. A **project** groups agents within your organization — typically one project per product, environment, or team. All management endpoints accept a Bearer token from a login session or your organization API key (see [Authentication](/api/authentication)). *** ## Create a project ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/projects" \ -H "Content-Type: application/json" \ -H "x-organization-api-key: " \ -d '{ "name": "Support agents" }' ``` ### POST `/management/v1/projects` Project name — 2 to 100 characters, and not only whitespace. ```json 201 theme={"dark"} { "id": "5f2b8c1a9d4e3f0012ab34cd", "name": "Support agents", "created_at": "2026-07-13T14:02:11Z", "updated_at": "2026-07-13T14:02:11Z" } ``` *** ## List projects ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/management/v1/projects?page[size]=20" \ -H "x-organization-api-key: " ``` ### GET `/management/v1/projects` Returns your organization's projects as a paginated list (see [Pagination](#pagination) below). *** ## Get a project ### GET `/management/v1/projects/{projectId}` Returns one project. ## Delete a project ### DELETE `/management/v1/projects/{projectId}` Deletes a project; it disappears from listings immediately. Returns `204`. ## Project usage ### GET `/management/v1/projects/{projectId}/usage` Usage metrics aggregated across every agent in the project — see [Usage](/api/management/usage). *** ## Pagination Every list endpoint on the management surface shares one cursor-based envelope: #### Query parameters Items per page. Cursor from a previous response's `meta.after_cursor` — fetches the next page. Mutually exclusive with `page[before]`. Cursor from `meta.before_cursor` — fetches the previous page. Field to sort by. `asc` or `desc`. #### Response envelope ```json theme={"dark"} { "results": [ ... ], "meta": { "has_more": true, "after_cursor": "b2Zmc2V0PTIw", "before_cursor": null }, "links": { "next": "/management/v1/projects?page[size]=20&page[after]=b2Zmc2V0PTIw", "prev": null } } ``` Always echo back the cursor you were given (`meta.after_cursor` / `meta.before_cursor` or the ready-made `links`) rather than constructing your own. # Threads Source: https://docs.aui.io/api/management/threads List, filter, inspect, and rename conversation threads across your organization. The management surface gives you an organization-wide view of conversation [threads](/api/messaging/threads) — for back-office tools, QA, and analytics. *** ## List threads ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/management/v1/threads?tool=create_dispute&created=2026-07-01T00:00:00Z&created=2026-07-13T00:00:00Z" \ -H "x-organization-api-key: " ``` ### GET `/management/v1/threads` Lists your organization's threads, newest first, as a [paginated list](/api/management/projects#pagination). Filters **OR within a field and AND across fields** — repeat a parameter to OR values: Filter by project. Filter by agent — repeatable. Filter by end-user id. RFC 3339 timestamp — repeat it twice for a `[start, end]` range. Threads where this tool was activated — repeatable. Threads where a rule was evaluated: `code`, or `code:outcome` with outcome one of `fired`, `blocked`, `passed`. Repeatable. Threads by computed parameter: `key:value`, `key:*` (any value), or a comparison like `amount>100`. Repeatable. Which runtime to list threads from. Pass the runtime version your agent runs on (e.g. `0.8.0`; any value outside the `1.x` family selects runtime v2) to list its threads. Runtime v2 listings are always scoped — include an `agent_id` or `user_id` filter — and only `agent_id`, `user_id`, and `created` (with `agent_id`) apply there; the runtime v1–only filters return a clear 400 on the runtime v2 path. Omit it to list threads of [runtime v1 agents](/sdk/runtime-v1-agents) (the default), where every filter above applies. Each item carries the thread's `id`, `title`, `created_at`, and `version_tag` — the agent version the conversation ran on, so you can tell at a glance which release of your agent handled it. ```json 200 theme={"dark"} { "results": [ { "id": "68e78d0dc5a4b19a030d03d6", "title": "Dispute a charge", "created_at": "2026-07-12T09:14:02Z", "version_tag": "v3.2" } ], "meta": { "has_more": false, "after_cursor": null, "before_cursor": null }, "links": { "next": null, "prev": null } } ``` *** ## Get a thread ### GET `/management/v1/threads/{threadId}` Returns one thread — its `title`, the `user_id` it belongs to, its `welcome_message`, and current `followup_suggestions`. The runtime the thread lives on — pass the runtime version the agent runs on (e.g. `0.8.0`; any value outside the `1.x` family selects runtime v2). Required for runtime v2 threads (UUID ids); omit for threads of [runtime v1 agents](/sdk/runtime-v1-agents) (24-character hex ids), whose runtime is the default. A thread lives on the runtime that created it — asking the wrong one returns 404. Applies to the rename and transcript endpoints below as well. ## Rename a thread ### PATCH `/management/v1/threads/{threadId}` ```bash cURL theme={"dark"} curl -X PATCH "https://api-v3.aui.io/apollo-api/management/v1/threads/{threadId}" \ -H "Content-Type: application/json" \ -H "x-organization-api-key: " \ -d '{ "title": "Renamed conversation" }' ``` The new title. Fields you omit are left unchanged; the full updated thread is returned. ## Read a thread's transcript ### GET `/management/v1/threads/{threadId}/messages` The thread's full transcript, in chronological order — the same message shape as the [messaging surface](/api/messaging/threads). ## Inspect reasoning ### GET `/management/v1/threads/{threadId}/trace` ### GET `/management/v1/interactions/{interactionId}/trace` The reasoning [traces](/api/messaging/traces) for a whole thread or one interaction. Traces are available for threads of **[runtime v1 agents](/sdk/runtime-v1-agents) only** today. # Usage Source: https://docs.aui.io/api/management/usage Interaction and token metrics per agent or across a project. Usage endpoints report **metrics** — interaction counts and token volume — for one agent or aggregated across a project, over a time window you choose. *** ## Get usage ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/management/v1/agents/{agentId}/usage?created_from=2026-07-01T00:00:00Z&created_to=2026-07-13T00:00:00Z" \ -H "x-organization-api-key: " ``` ### GET `/management/v1/agents/{agentId}/usage` ### GET `/management/v1/projects/{projectId}/usage` Same parameters and response for both — the project variant aggregates across every agent in the project. #### Query parameters Start of the usage window (RFC 3339). Omit for no lower bound. End of the usage window (RFC 3339). Omit for no upper bound. #### Response Number of interactions in the window. Total input tokens. Total output tokens. Total cached (read) input tokens. Total reasoning tokens. Sum of input, output, cache-read, and reasoning tokens. ```json 200 theme={"dark"} { "interaction_count": 1284, "input_tokens": 512400, "output_tokens": 98211, "cache_read_tokens": 240100, "reasoning_tokens": 15320, "total_tokens": 866031 } ``` # Versions Source: https://docs.aui.io/api/management/versions An agent's behavior, versioned: draft, publish, roll back. A **version** is a snapshot of an agent's configuration. Versions move through a simple lifecycle: ``` draft ──publish──▶ published (live) ──publish another──▶ still published │ archive (only when not live) ``` * **Draft** — editable. Push configuration to it as often as you like. * **Published** — frozen. Exactly one published version is the agent's **live** version, the one conversations run against. * **Archived** — retired from everyday use. *** ## Create a draft version ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/agents/{agentId}/versions" \ -H "Content-Type: application/json" \ -H "x-organization-api-key: " \ -d '{ "source": "version", "from_version": "6b455e297e4271a82fd73fc7", "label": "checkout-flow-v2" }' ``` ### POST `/management/v1/agents/{agentId}/versions` Where the draft starts from: `empty`, `template`, or `version` (clone an existing version). Required when `source` is `version` — the version to clone. Required when `source` is `template`. Free-form label for your own bookkeeping. Free-form tags. Notes shown alongside the version. ```json 201 theme={"dark"} { "id": "6c566f3a8f5382b93fe84fd8", "agent_id": "6a344d186d3160971ec62eb6", "version_number": 4, "version_revision_number": 0, "version_tag": "v4.0", "status": "draft", "parent_version_id": "6b455e297e4271a82fd73fc7", "label": "checkout-flow-v2", "tags": [], "runtime_version": "0.8.0", "created_at": "2026-07-13T14:02:11Z", "published_at": null } ``` Every version also reports the `runtime_version` build its bundle targets (e.g. `0.8.0`, stamped on push). A `0.x` build means the agent runs on runtime v2; a `1.x` value or no value means [runtime v1](/sdk/runtime-v1-agents). *** ## List versions ### GET `/management/v1/agents/{agentId}/versions` Lists the agent's versions as a [paginated list](/api/management/projects#pagination), filterable by: `draft`, `published`, `archived`. Partial match on the version tag (e.g. `v3`). Exact match. Partial match on the label. Only return base versions (revision 0). ## Update a version ### PATCH `/management/v1/agents/{agentId}/versions/{versionId}` Updates a version's metadata — `label`, `tags`, `notes`. The configuration itself changes through [push](#push-a-configuration-bundle), never here. *** ## Publish a version ### POST `/management/v1/agents/{agentId}/versions/{versionId}/publish` Makes this version the agent's **live** version. One verb covers the whole story: * Publishing a **draft** freezes it, then makes it live (shipping). * Publishing an **already-published** version re-activates it (rolling back or switching). Returns the now-live version. ## Archive a version ### POST `/management/v1/agents/{agentId}/versions/{versionId}/archive` Retires a version from everyday use. The live version can't be archived — publish another version first. *** ## Push a configuration bundle Upload configuration to a version, committing a new revision: ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/agents/{agentId}/versions/{versionId}/push" \ -H "Content-Type: application/json" \ -H "x-organization-api-key: " \ -d '{ "caller": "cli", "commit_message": "Tighten the dispute-window rule", "bundle": { "schema_version": "1", "general_settings": { ... } } }' ``` ### POST `/management/v1/agents/{agentId}/versions/{versionId}/push` What is pushing: `agent_builder`, `ui`, or `cli`. Describe the change (up to 4000 characters). The versioned agent configuration bundle. Must include `schema_version` and `general_settings`. The bundle is validated before it's committed — invalid configurations return a `422` with field-level details. The response carries the new revision's identity and content digest (`new_version_tag`, `revision_id`, `sha256`, `size`). ## Pull a configuration bundle ### GET `/management/v1/agents/{agentId}/versions/{versionId}/pull` Downloads a version's configuration bundle — the current revision, or a specific one: Revision tag (e.g. `v3.2`). Omit for the version's current tag. # Channels (WhatsApp & SMS) Source: https://docs.aui.io/api/messaging/channels Reach users on WhatsApp or SMS — your agent handles the conversation. Channels let your agent hold conversations over WhatsApp and SMS. You initiate the thread with an opening message; from then on, the user's replies flow to the agent and the agent's replies flow back on the same channel — no polling or webhooks needed on your side. *** ## Start a channel thread Send the opening message on a channel and bind the recipient's phone number to a conversation thread. Pass `sender_id` to send from a number you connected in the [Playground](https://apollo.aui.io); omit it to use the platform default sender. ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/channels/whatsapp/threads" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "phone_number": "+14155551234", "agent_display_name": "Aria", "sender_id": "67c1a2b3d4e5f67890123456" }' ``` ### POST `/messaging/v1/channels/{channel}/threads` `whatsapp` or `sms`. #### Request body Recipient phone number in E.164 format (e.g. `+14155551234`). This is who receives the opener — not the number you send from. MongoDB ObjectId of a sender you already connected in the [Playground](https://apollo.aui.io) (SMS or WhatsApp). The opener is sent **from that connected number**. Omit it to use the platform default sender. This is not the recipient phone number. Continue an existing thread instead of starting a new one. Omitted, a new thread is created and its id returned. Your identifier for the user on a new thread. Defaults to the phone number's digits. Opening message body for SMS. WhatsApp ignores it — the opener uses your approved WhatsApp template. WhatsApp only: the agent name substituted into the template's first variable. #### Response The conversation thread the opener landed on — new or continued. The provider's message id for the sent opener, for delivery tracking. The connected number the opener was sent from. Present when you passed `sender_id`; omitted when the platform default sender was used. ```json 200 theme={"dark"} { "thread_id": "68e78d0dc5a4b19a030d03d6", "message_sid": "SM8f14e45fceea167a5a36dedd4bea2543", "from": "+14155559876" } ``` *** ## How the conversation continues Once the thread exists: 1. The recipient replies on WhatsApp/SMS. 2. The reply is routed to your agent on the bound thread. 3. The agent's answer is delivered back on the same channel. The thread is a regular conversation thread — read its transcript with [`GET /messaging/v1/threads/{threadId}/messages`](/api/messaging/threads) and inspect its reasoning with [traces](/api/messaging/traces). WhatsApp requires the opening message of a business-initiated conversation to use a pre-approved template; `text` applies to SMS only. Connect a phone number in the [Playground](https://apollo.aui.io), then pass that sender's ObjectId as `sender_id` to start the conversation from that number. # Send Messages Source: https://docs.aui.io/api/messaging/send-messages Send messages to your agent — complete responses, token streaming, and reruns. Send a message and get the agent's reply. The agent is identified by your access token, and the conversation thread is created automatically on the first message — there is no separate "create thread" call. Two ways to receive the reply: | Transport | Endpoint | Best for | | ------------------ | ------------------------------------ | ------------------------------------------------------------------------------ | | REST | `POST /messaging/v1/messages` | Backends; one request, one complete reply | | Server-sent events | `POST /messaging/v1/messages/stream` | Live typing UIs — token-by-token over plain HTTP, one call per turn, resumable | [Runtime v1 agents](/sdk/runtime-v1-agents) additionally accept a bidirectional [WebSocket session](/api/messaging/websocket) at `wss .../messaging/v1/session`. *** ## Send a message ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "text": "I want to dispute a charge from May 30th", "user_id": "my-user-123" }' ``` ### POST `/messaging/v1/messages` #### Request body The message to send to the agent. Your identifier for the end user. New threads are attributed to this user. Continue an existing thread. Omit it to start a new one — the created thread's id is returned in the response. Signed image URL for vision input. Per-message values for the agent's configured context variables. Values substituted into the agent's static context `{{placeholders}}`. Values for the variables of the agent's dynamic-context API calls. On a key collision with `static`, the `dynamic` value wins. Advanced — pins the turn to a specific runtime build (e.g. `0.8.0`). Normally omit it: the platform uses the agent's own build. Ignored by [runtime v1 agents](/sdk/runtime-v1-agents). Also accepted on the stream and rerun endpoints. #### Response The thread the message landed on — newly created when the request omitted `thread_id`. Pass it back to continue the conversation. The agent's reply: `id` (the interaction id — used for reruns and traces), `text`, `cards`, `followup_suggestions`, and token counts. ```json 200 theme={"dark"} { "thread_id": "68e78d0dc5a4b19a030d03d6", "message": { "id": "507f1f77bcf86cd799439011", "created_at": "2026-07-13T14:02:11Z", "text": "I can help with that. Our dispute window is 8 days, so a charge from May 30th is outside it — but here's what you can do instead.", "cards": [], "followup_suggestions": [ "What other options do I have?", "Show me my recent transactions" ], "input_tokens": 412, "output_tokens": 96 } } ``` #### Cards When the agent surfaces entities — products, bookings, search results — the reply carries them as `cards`. Each card is the same entity in two self-contained representations, so you pick the one that fits your stack: The card as a ready-to-render JSX string — drop it straight into a React UI. The card as structured JSON — build your own UI in any framework (Vue, Angular, mobile, ...). `entity` is a flat key–value map of the card's fields; `sub_entities` holds nested groups such as product variants, each with a `name` and a list of `items` in the same key–value shape. Whether the agent flagged this card as its top recommendation. The card's position in the reply. The card's authored title. Optional; may be `null`. Which of the agent's capabilities produced the card — useful for picking a widget template. Optional; may be `null`. The specific record the card is about, if any. Optional; may be `null`. ```json Example card theme={"dark"} { "rendered_jsx": "...", "is_recommended": true, "index": 0, "title": "Honda CR-V, LX", "capability": "vehicle-search", "instance": "inventory-10958", "json_data": { "entity": { "product-name": "Honda CR-V, LX", "year": 2023, "price": 25582, "mileage": 10958, "image": "https://cdn.example.com/crv.jpg" }, "sub_entities": [ { "name": "Variants", "items": [ { "item-number": "B01HI7WP0U", "price": 329.00 } ] } ] } } ``` The keys inside `entity` and `sub_entities[].items` are defined by your agent's card templates — treat them as data, not as a fixed schema. ```javascript Node.js theme={"dark"} const response = await fetch( "https://api-v3.aui.io/apollo-api/messaging/v1/messages", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${accessToken}`, }, body: JSON.stringify({ text: "I want to dispute a charge from May 30th", user_id: "my-user-123", }), } ); const { thread_id, message } = await response.json(); console.log(thread_id); // continue the conversation with this console.log(message.text); ``` ```python Python theme={"dark"} import requests response = requests.post( "https://api-v3.aui.io/apollo-api/messaging/v1/messages", headers={"Authorization": f"Bearer {access_token}"}, json={ "text": "I want to dispute a charge from May 30th", "user_id": "my-user-123", }, ) body = response.json() print(body["thread_id"]) # continue the conversation with this print(body["message"]["text"]) ``` *** ## Stream the reply (SSE) `POST /messaging/v1/messages/stream` takes the **same request body** and streams the reply token-by-token over server-sent events. It's plain HTTP — the same auth as the REST call, no upgrade handshake, no connection to keep alive between turns — and a dropped stream resumes with the standard `Last-Event-ID` header instead of re-running the turn. ```bash cURL theme={"dark"} curl -N -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/messages/stream" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ -H "Authorization: Bearer " \ -d '{ "text": "Hello!", "user_id": "my-user-123" }' ``` Events are JSON objects discriminated on `type`: | `type` | When | Payload | | ------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `thread` | First event of every stream | `data.thread_id` — the resolved thread (you can't read headers mid-stream) | | `event` | While the agent works | Token deltas carry the text under `data.text` | | `message` | Turn complete | The full agent reply — same `message` object as the REST response | | `suggestions` | After the terminal `message`, when available | `data.followup_suggestions` — ready-made follow-up prompts for your UI, with the `thread_id` and `interaction_id` they belong to. Emitted by runtime v2 agents; [runtime v1 agents](/sdk/runtime-v1-agents) return `followup_suggestions` on the message instead | | `error` | Something failed mid-turn | `data.message` and, when available, a status code | The stream ends with a standalone `data: [DONE]` terminator. ```text Example stream theme={"dark"} data: {"type":"thread","data":{"thread_id":"3f9c2a1e-7b0d-4c52-9e1a-6d2f8b4c0a17"}} data: {"type":"event","seq":1,"data":{"text":"I can"}} data: {"type":"event","seq":2,"data":{"text":" help with that."}} data: {"type":"message","seq":3,"data":{"id":"507f1f77...","text":"I can help with that.", "followup_suggestions":[]}} data: {"type":"suggestions","seq":4,"data":{"thread_id":"3f9c2a1e-7b0d-4c52-9e1a-6d2f8b4c0a17","interaction_id":"507f1f77...","followup_suggestions":["What other options do I have?"]}} data: [DONE] ``` ### Resuming a dropped stream `event` and `message` frames carry a monotonic `seq`. If the connection drops, reconnect with the standard `Last-Event-ID` header set to the last `seq` you saw — missed events replay without running the turn again: ```bash theme={"dark"} curl -N -X POST ".../messaging/v1/messages/stream" \ -H "Last-Event-ID: 2" \ ... ``` *** ## Rerun an interaction Regenerate a previous interaction — with the original or edited text — against the agent's live version. The rerun happens on a **new thread** branched from the original, so the source conversation is untouched. ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/threads/{threadId}/rerun" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "interaction_id": "507f1f77bcf86cd799439011", "text": "Actually, the charge was from June 1st" }' ``` ### POST `/messaging/v1/threads/{threadId}/rerun` #### Request body The interaction to regenerate — the `message.id` from a previous response. The message to replay: the original text, or an edited variant. End user to attribute the rerun to. Omitted, the thread's original user is kept. Signed image URL on the replayed message. Pin the rerun to a specific agent version instead of the live one. Pin to a specific version tag (e.g. `v3.2`). #### Response Same shape as send: `thread_id` is the **new** thread the rerun created, and `message` is the regenerated reply. ```json 200 theme={"dark"} { "thread_id": "68e79a41c5a4b19a030d0f12", "message": { "id": "507f1f77bcf86cd799439099", "text": "A charge from June 1st is within our 8-day dispute window — let's start the dispute.", "followup_suggestions": ["What details do you need from me?"], "cards": [] } } ``` # Threads Source: https://docs.aui.io/api/messaging/threads Conversations are threads — created automatically, readable any time. A **thread** is one conversation between an end user and your agent. Threads are created automatically when you send a message without a `thread_id` — the created id comes back in the response, and you pass it on subsequent messages to continue the conversation. Coming from an earlier integration? Threads are what used to be called *tasks*, and there is no longer a separate "create task" step. *** ## Read a thread's transcript ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/messaging/v1/threads/{threadId}/messages" \ -H "Authorization: Bearer " ``` ### GET `/messaging/v1/threads/{threadId}/messages` Returns the thread's full transcript in chronological order — one entry per message, both the user's and the agent's. Agent messages that surfaced entities carry them as `cards`, each with a ready-to-render `rendered_jsx` string and its structured `json_data` counterpart — see [Cards](/api/messaging/send-messages#cards). The runtime the thread lives on — pass the runtime version the agent runs on (e.g. `0.8.0`; any value outside the `1.x` family selects runtime v2, and it doesn't have to match the thread's exact build). Required for runtime v2 threads (UUID ids); omit for threads of [runtime v1 agents](/sdk/runtime-v1-agents) (24-character hex ids), whose runtime is the default. A thread lives on the runtime that created it — asking the wrong one returns 404. ```json 200 theme={"dark"} [ { "id": "507f1f77bcf86cd799439011", "created_at": "2026-07-13T14:02:11Z", "text": "I want to dispute a charge from May 30th", "sender": { "id": "my-user-123", "type": "user" }, "cards": [], "followup_suggestions": [] }, { "id": "507f1f77bcf86cd799439012", "created_at": "2026-07-13T14:02:14Z", "text": "I can help with that. Our dispute window is 8 days...", "sender": { "type": "agent" }, "cards": [ { "rendered_jsx": "...", "is_recommended": true, "index": 0, "json_data": { "entity": { "charge-date": "2026-05-30", "amount": 42.50 }, "sub_entities": [] } } ], "followup_suggestions": ["What other options do I have?"] } ] ``` *** ## Welcome message Open your conversation UI with the agent's configured greeting — before any message has been sent: ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/messaging/v1/welcome-message" \ -H "Authorization: Bearer " ``` ### GET `/messaging/v1/welcome-message` Returns the welcome message of your agent's live version: ```json 200 theme={"dark"} { "welcome_message": "Hi! How can I help you today?" } ``` *** ## Follow-up suggestions Generate suggested next prompts from a context you provide — useful for offering the end user quick next questions: ```bash cURL theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/followup-suggestions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "context": { "topic": "order tracking" } }' ``` ### POST `/messaging/v1/followup-suggestions` The context the suggestions are generated from — any JSON object describing where the user is in your product or conversation. ```json 200 theme={"dark"} { "suggestions": [ "Where is my order?", "Can I change the delivery address?" ] } ``` The agent's replies also carry `followup_suggestions` inline — this endpoint is for generating suggestions *outside* a turn, e.g. when the user lands on a page. *** ## Managing threads Listing, filtering, renaming, and auditing threads across your organization live on the management surface — see [Management → Threads](/api/management/threads). # Traces Source: https://docs.aui.io/api/messaging/traces Inspect the agent's reasoning: what it understood, which rules fired, what it decided. Every interaction produces a **trace** — a structured record of the agent's reasoning for that turn: the input it saw, what it understood, the decisions it took, the parameters it computed, the rules it evaluated, and the response it gave. Traces are available for **[runtime v1 agents](/sdk/runtime-v1-agents)** today — trace support for runtime v2 is coming. Two ways to fetch traces on the messaging surface: | Endpoint | Returns | | ------------------------------------------------------ | --------------------------------------- | | `GET /messaging/v1/threads/{threadId}/trace` | Every interaction's trace in the thread | | `GET /messaging/v1/interactions/{interactionId}/trace` | One interaction's trace | The `interactionId` is the `message.id` from a [send-message response](/api/messaging/send-messages). On a runtime v1 [WebSocket session](/api/messaging/websocket), the final `message` envelope embeds the same trace as `data.trace_info` when the session was opened with `include_trace=true`. *** ## Get a thread's traces ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/messaging/v1/threads/{threadId}/trace" \ -H "Authorization: Bearer " ``` Returns an array of trace objects, one per interaction, oldest first. ## Get one interaction's trace ```bash cURL theme={"dark"} curl "https://api-v3.aui.io/apollo-api/messaging/v1/interactions/{interactionId}/trace" \ -H "Authorization: Bearer " ``` *** ## The trace object The end-user message the turn started from (`input.message`). What the agent understood: the detected `intents` and the `guardrails` outcome (`passed`, `reason`). The decisions the agent took while handling the message — one entry per step. The kind of decision (e.g. a tool activation, a computation, a branch). The tool involved, when the decision concerns one. Outcome of the step; `fail_reason` explains failures. Why the step ran: `type`, `reasons`, `matched_objective`. The rule behind the decision, when one applies: `code`, `type`, `action`, `reason`. Parameters the agent computed during the turn (`code`, `title`, `scope`). Every rule evaluated: its `code`, whether it `triggered`, and the per-condition `reason` breakdown. How the agent answered: the response `type` and `message`, follow-up `suggestions`, what it was `asking_for`, any `block_message`, and rendered `jsx_widgets`. ```json 200 theme={"dark"} { "input": { "message": "I want to dispute a charge from May 30th" }, "understanding": { "intents": ["dispute_charge"], "guardrails": { "passed": true } }, "decisions": [ { "type": "rule_check", "rule": { "code": "dispute_window", "type": "policy", "action": "block", "reason": "Charge is older than the 8-day dispute window" }, "status": "blocked" } ], "computed_parameters": [ { "code": "transaction_date", "title": "Transaction date", "scope": "conversation" } ], "rules_evaluations": [ { "code": "dispute_window", "triggered": true, "reason": [ { "passed": false, "trigger_type": "condition", "condition": { "method": "date_diff", "operation": "lte" } } ] } ], "response": { "type": "answer", "message": "Our dispute window is 8 days, so that charge can't be disputed — here are your options.", "suggestions": ["What other options do I have?"] } } ``` Trace fields are additive: new fields may appear as the reasoning engine evolves, so treat unknown fields as informational rather than errors. *** ## Traces on the management surface The same traces are available under `/management/v1` for back-office tooling — `GET /management/v1/threads/{threadId}/trace` and `GET /management/v1/interactions/{interactionId}/trace` — authenticated with a management credential. See [Management → Threads](/api/management/threads). # WebSocket (runtime v1) Source: https://docs.aui.io/api/messaging/websocket A bidirectional session for runtime v1 agents — streaming replies with gap-free recovery. For [runtime v1 agents](/sdk/runtime-v1-agents), the WebSocket session is a bidirectional transport for chat UIs: send message frames, receive the reply as a live token stream, and recover any missed events after a reconnect. It shares the conversation model and threads of the [REST and SSE transports](/api/messaging/send-messages). **Runtime v1 agents only.** WebSocket sessions belong to runtime v1. Runtime v2 agents stream over HTTP instead — the [SSE stream endpoint](/api/messaging/send-messages#stream-the-reply-sse) gives you the same token-by-token reply per turn, resumable with `Last-Event-ID`, with no socket to keep alive; a runtime v2 agent answers a session with an `error` envelope explaining it is SSE-only. [Runtime v1 agents](/sdk/runtime-v1-agents#websocket-sessions) maps each socket frame to its SSE equivalent. *** ## Connect ``` wss://api-v3.aui.io/apollo-api/messaging/v1/session ``` Authenticate on the upgrade request with your Bearer token: ``` Authorization: Bearer ``` Invalid or expired credentials close the connection with code `1008` (policy violation). Internal errors close with `1011`. ### Query parameters When true, the final `message` envelope carries `data.trace_info` — the reasoning [trace](/api/messaging/traces). Omit for the minimal stream (token deltas + completion). `all` or `verbose` opts into the full workflow/lifecycle event set. With `task_id`, replays buffered envelopes with `seq` greater than this value on connect — gap recovery after a reconnect. Thread id to replay from on connect (pairs with `resume_after`). *** ## Frames you send ### `message` — submit a turn ```json theme={"dark"} { "type": "message", "agent_id": "6a344d186d3160971ec62eb6", "thread_id": null, "user_id": "my-user-123", "text": "I am looking for a built-in microwave", "context": { "url": "https://example.com/products" }, "agent_variables": {} } ``` Must be `"message"`. The target agent — use the `agent_id` returned by the [token exchange](/api/authentication). Omit to auto-create a thread; its id is announced back as the first envelope. Pass it to continue an existing thread. End-user reference for an auto-created thread. The message content. ### `resume` — replay after a reconnect ```json theme={"dark"} { "type": "resume", "resume_after": 41 } ``` Replays buffered envelopes with `seq > resume_after` for the active thread. *** ## Envelopes you receive Every server frame is a sequenced envelope: ```json theme={"dark"} { "seq": 42, "type": "event", "data": { ... } } ``` | `type` | Meaning | `data` | | --------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- | | `thread` | The resolved thread id, once per thread per connection | `{ "thread_id": "..." }` | | `event` | A streaming event while the agent works | Token deltas carry `data.text` under event name `thread-message-text-content-updated` | | `message` | Turn complete — the full agent reply | The same `message` object as REST, plus `trace_info` when `include_trace=true` | | `error` | A recoverable error; the socket stays open | `{ "error": "...", "code": 402 }` for session errors, or the turn error's message and status | Track the highest `seq` you've processed — that's your resume cursor. Locally-generated errors use `seq: -1` and are not resumable. *** ## Code example ```javascript Node.js theme={"dark"} const WebSocket = require("ws"); let lastSeq = -1; const ws = new WebSocket( "wss://api-v3.aui.io/apollo-api/messaging/v1/session", { headers: { Authorization: `Bearer ${accessToken}` } } ); ws.on("open", () => { ws.send(JSON.stringify({ type: "message", agent_id: agentId, // from the token exchange user_id: "my-user-123", text: "I am looking for a built-in microwave", })); }); ws.on("message", (raw) => { const envelope = JSON.parse(raw); if (envelope.seq > 0) lastSeq = envelope.seq; switch (envelope.type) { case "thread": console.log("thread:", envelope.data.thread_id); break; case "event": if (envelope.data?.data?.text) { process.stdout.write(envelope.data.data.text); // token delta } break; case "message": console.log("\ncomplete:", envelope.data.text); break; case "error": console.error("error:", envelope.data); break; } }); ws.on("close", (code) => { if (code !== 1000) { // reconnect, then recover the gap: // ?task_id=&resume_after= } }); ``` *** ## Best practices * **Reconnect with resume** — on an abnormal close, reconnect with `task_id` + `resume_after` (or send a `resume` frame) instead of restarting the turn; you get exactly the envelopes you missed. * **Track `seq` continuously** — a gap in `seq` means you missed frames; resume from your last processed value. * **Exponential backoff** — back off between reconnection attempts. * **Keep the thread id** — persist `thread_id` across reconnects to stay in the same conversation. The full machine-readable contract is published as an AsyncAPI document at [`/asyncapi.yaml`](https://api-v3.aui.io/apollo-api/asyncapi.yaml). # API Overview Source: https://docs.aui.io/api/overview Talk to your Apollo-1 agents and manage them programmatically — one API, two surfaces.
Stable REST + SSE
The Apollo API is how your product talks to your agents. It has two surfaces that share one base URL and one authentication flow: Run conversations: send messages (complete, or streamed token-by-token over server-sent events), read transcripts, rerun interactions, inspect reasoning traces, and reach users on WhatsApp or SMS. Operate your workspace: projects, agents, versions and their configuration, conversation threads, and usage metrics. ## Base URL ``` https://api-v3.aui.io/apollo-api ``` Messaging endpoints live under `/messaging/v1`, management endpoints under `/management/v1`. [Runtime v1 agents](/sdk/runtime-v1-agents) can also open a [WebSocket session](/api/messaging/websocket) on the same host: `wss://api-v3.aui.io/apollo-api/messaging/v1/session`. ## Authentication in one minute Exchange your **publishable key** for a short-lived access token, then send it as a Bearer token: ```bash theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/auth/token" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "publishable_key", "publishable_key": "pk_..." }' ``` The token identifies your agent — messaging calls never carry an `agent_id` in the body. Management endpoints also accept an **organization API key** as an alternative. See [Authentication](/api/authentication) for the full flow. ## A conversation in three calls ```bash Get a token theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/auth/token" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "publishable_key", "publishable_key": "pk_..." }' ``` ```bash Send the first message theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "text": "I want to dispute a charge", "user_id": "my-user-123" }' ``` The response includes a `thread_id` — a new thread was created for you. Pass it back to continue the same conversation: ```bash Continue the conversation theme={"dark"} curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "text": "It was from May 30th", "user_id": "my-user-123", "thread_id": "" }' ``` ## Core concepts A thread is one conversation with an agent. Threads are created automatically on the first message — no separate create call. Each user message and its agent reply form an interaction. Interactions can be rerun with edited text, and each one is addressable by its id. The agent's reasoning, step by step: what it understood, which rules fired, and the decisions it took. Available for runtime v1 agents today; runtime v2 support is coming. Agents live in projects. An agent's behavior is defined by its versions; exactly one version is live at a time. ## Explore Publishable keys, access tokens, and organization API keys. REST, server-sent events, and reruns. Every endpoint, schema, and error — generated from the live API. One error envelope with stable, machine-readable codes. # Changelog Source: https://docs.aui.io/changelog Latest updates, new features, and API changes. ## September 2026 **CLI** `stable` · `@aui.io/apollo` The Apollo CLI is the operator console for Apollo agents — a scriptable CLI (`apollo `) and a full-screen terminal TUI (bare `apollo`) in one npm install: * **Local-first authoring** — agents check out as typed YAML programs (`bundle/src/`) with JSON Schemas, coding-agent skill packs, and the runtime's `AGENTS.md`; `validate`, `diff`, `pull`, and `push` complete the loop. * **Runtime testing** — `apollo chat` talks to the live runtime, including unpushed local edits via `--local .`, with the full decision trace via `--trace`; `apollo thread` inspects conversations. * **Server-side authoring** — `apollo advise` answers grounded questions over your checkout, `apollo build` runs gated build turns, and `apollo evaluate` runs the live evaluation suite (simulated callers plus a judge). * **Versions, knowledge, and secrets** — full version lifecycle (`version publish` is also rollback), knowledge hubs (`apollo kb`), vault secrets (`apollo vault`), and a per-agent mock database (`apollo mockdb`). * **Built for coding agents** — every command is non-interactive with a stable `--json` envelope and predictable exit codes. Get started: [Installation](/cli/installation). ## August 2026 **API** · **SDK** Agents can now run on **runtime v2** — the current, streaming-first generation (its threads have UUID ids). Agents created before August 1 2026 stay on runtime v1. Sending is unchanged and routes automatically; the differences surface in a few places: * **Thread reads** (transcripts, get/rename/list) take a `runtime_version` selector — pass the runtime version the agent runs on (e.g. `0.8.0`). See [Threads and runtime versions](/sdk/overview#threads-and-runtime-versions). * **SSE streams** can end with a new `suggestions` event carrying ready-made follow-up prompts (`followup_suggestions`, with the `thread_id` and `interaction_id` they belong to). * **Cards** carry new optional metadata: `title`, `capability` (which agent capability produced the card), and `instance` (the record it's about). * **Streaming is over SSE** — one HTTP call per turn, resumable with `Last-Event-ID`. WebSocket sessions stay with runtime v1; see [Runtime v1 agents](/sdk/runtime-v1-agents) for the frame-by-frame mapping. * Sends accept an optional `runtime_version` build pin (advanced; normally omit). **SDK** `breaking` Management list methods now take their filter fields **directly on the request** — drop the `filters: { … }` wrapper (`listThreads({ project_id })`, `listAgents(projectId, { name })`, `listVersions(agentId, { status })`). The TypeScript compiler points at every call site. This also fixes a bug where list filters were silently not applied on the wire in 3.3.4 and earlier — from 3.3.5 they filter reliably. Requests from older SDK versions keep behaving exactly as they did. **API** · **SDK** `POST /messaging/v1/channels/{channel}/threads` now accepts an optional `sender_id` — the MongoDB ObjectId of a sender you connected in the [Playground](https://apollo.aui.io). Pass it to start the conversation **from that connected number**. Omit it to keep using the platform default sender. `phone_number` remains the **recipient**. When a specific sender is used, the response includes `from` so you can confirm which connected number sent the opener. See [Channels](/api/messaging/channels) and the [SDK messaging client](/sdk/messaging#channels-whatsapp-and-sms). ## July 2026 **API** `breaking` Agent reply `cards` now carry the entity in two self-contained representations: `rendered_jsx` (unchanged — a ready-to-render JSX string for React UIs) and the new `json_data` — the same card as structured JSON, so you can build your own card UI in any framework (Vue, Angular, mobile). `json_data.entity` is a flat key–value map of the card's fields, and `json_data.sub_entities` holds nested groups such as product variants. The card's previous `name`, `category`, and `parameters` fields were removed — everything you need to render is in `json_data`. See [Cards](/api/messaging/send-messages#cards). **API** `GET /management/v1/threads` items now include `version_tag` — the agent version the conversation ran on. Use it to tell which release of your agent handled each thread when filtering or auditing conversations. See [Management → Threads](/api/management/threads). **API** `breaking` The API base URL changed from `https://api-v3.aui.io/apollo-api-v2` to `https://api-v3.aui.io/apollo-api`. All paths under it are unchanged (e.g. `POST /messaging/v1/messages`, `POST /management/v1/auth/token`). The old base URL keeps working for a few more days to give you time to migrate, and will then be removed — switch to the new one now. **API** `breaking` Your agent is now identified by your access token: when you exchange a publishable key at `POST /management/v1/auth/token`, the resulting token already carries the agent. Sending `agent_id` in the body of `POST /messaging/v1/messages`, `POST /messaging/v1/messages/stream`, or `POST /messaging/v1/threads/{threadId}/rerun` is no longer accepted and returns a validation error (`422`). **Migration:** remove `agent_id` from your messaging request bodies — responses keep the same shape. See [Send messages](/api/messaging/send-messages). **API** `breaking` The `POST /messaging/v1/channels/{channel}/threads` request body changed: * `agent_id` was removed — the agent now comes from your access token. * `user_ref_id` was renamed to `user_id`. * `template_id` and `content_variables` (WhatsApp template overrides) were removed — the agent's configured template is always used; `agent_display_name` remains available. See [Channels](/api/messaging/channels). **SDK** `breaking` · v3.2 `@aui.io/aui-client` v3 is a full rewrite against the Apollo API. The single `ApolloClient` is replaced by two clients, one per credential: * **`ApolloMessagingClient`** — publishable key, browser-safe. Token exchange and refresh are handled internally; the agent comes from the key, not request bodies. Messaging, channels, and WebSocket sessions. * **`ApolloManagementClient`** — organization API key, server-side only. Projects, agents, versions, threads, and usage. Tasks are now threads: `createTask` is gone (threads auto-create on the first `sendMessage`), `task_id` became `thread_id`, and the client defaults to the production API with no environment configuration. See the [SDK upgrade guide](/sdk/migration). **API** `new` * **Update a thread** — `PATCH /management/v1/threads/{threadId}` renames a thread (`title`); the full updated thread is returned. * **Agent variables** — `POST /messaging/v1/messages` and `POST /messaging/v1/messages/stream` accept an optional `agent_variables` object (`static` and `dynamic`) with per-message values for the agent's configured context variables. * **Welcome message** — `GET /messaging/v1/welcome-message` returns your agent's configured greeting, for opening a conversation UI before the first message. * **Follow-up suggestions** — `POST /messaging/v1/followup-suggestions` generates suggested next prompts from a context you provide. ## March 2026 **Agent Builder API** `alpha` · Closed to the public * Alpha release of the Agent Builder API — available to select partners only * Identity, organizations, accounts, and networks (agents) endpoints * Agent settings read/write: tools, parameters, entities, integrations, rules * Knowledge base management with file upload and website scraping * API Workflow integration generator # Command Reference Source: https://docs.aui.io/cli/commands Complete reference for every Apollo CLI command. Every command accepts a global `--json` flag that emits a stable envelope to stdout for scripting, piping, and coding agents: `{"success": true, "data": …}` on success, `{"success": false, "error": {"code", "message", "suggestion"}}` on failure. Run `apollo --help` for the live flag list on your installed version. ## Quick Reference | Command | Description | | ----------------------------------------- | --------------------------------------------------------- | | [`apollo login`](#apollo-login) | Authenticate with Apollo | | [`apollo logout`](#apollo-logout) | Remove the active session | | [`apollo status`](#apollo-status) | Show the active context | | [`apollo doctor`](#apollo-doctor) | Diagnose the setup end to end | | [`apollo upgrade`](#apollo-upgrade) | Upgrade the CLI from npm | | [`apollo profile`](#apollo-profile) | Show or select the active credential profile | | [`apollo org`](#apollo-org) | Select an organization | | [`apollo project`](#apollo-project) | Manage projects | | [`apollo agent`](#apollo-agent) | Manage agents and checkouts | | [`apollo runtime`](#apollo-runtime) | Select the runtime (engine build) a checkout runs against | | **Authoring** | | | [`apollo validate`](#apollo-validate) | Validate the local agent program | | [`apollo diff`](#apollo-diff) | Compare local files with the last baseline | | [`apollo pull`](#apollo-pull) | Pull the selected agent version, schemas, and skills | | [`apollo push`](#apollo-push) | Validate and push local changes as a new version | | [`apollo version`](#apollo-version) | Manage agent versions | | **Server-side authoring & evaluation** | | | [`apollo advise`](#apollo-advise) | Ask the authoring advisor a grounded question | | [`apollo build`](#apollo-build) | Run one gated build turn server-side | | [`apollo certify`](#apollo-certify) | Run the bundle's own scenario suite offline | | [`apollo evaluate`](#apollo-evaluate) | Run the live evaluation suite server-side | | **Testing & inspection** | | | [`apollo chat`](#apollo-chat) | Chat with an agent (alias: `apollo send`) | | [`apollo thread`](#apollo-thread) | Inspect threads, messages, and traces | | **Resources** | | | [`apollo kb`](#apollo-kb) | The agent's knowledge hubs | | [`apollo vault`](#apollo-vault) | Manage vault secrets (`{{vault.NAME}}`) | | [`apollo mockdb`](#apollo-mockdb) | Manage the per-agent mock database | | **Shell** | | | [`apollo shell-init`](#apollo-shell-init) | Print the shell wrapper enabling auto-cd | ## Global Flags Place global flags **before** the subcommand; command-specific flags go after it (`apollo --json push -m "…"`). | Flag | Description | | ----------------------- | ---------------------------------------------------------------------- | | `--json` | Emit a stable JSON envelope | | `--quiet` | Suppress progress and warnings | | `--verbose` | Diagnostic details, including one redacted line per API request | | `--no-input` | Disable interactive input | | `--yes` | Confirm prompts | | `--profile ` | Override the active profile for this command | | `--project ` | Override the active project | | `--org ` | Override the active organization | | `--runtime-version ` | Override the checkout's pinned runtime (env: `APOLLO_RUNTIME_VERSION`) | | `--timeout ` | Request timeout in milliseconds (env: `APOLLO_TIMEOUT_MS`) | **Exit codes:** `0` success · `1` runtime/API failure · `2` validation failure · `3` authentication/configuration failure. *** ## Session & Context Authenticate with Apollo. With no options, opens the browser sign-in flow. ```bash theme={"dark"} apollo login # browser flow apollo login --email user@example.com # email OTP (re-run with --otp ) apollo login --token "$ACCESS_TOKEN" # store a pre-issued JWT ``` | Option | Description | | ----------------- | --------------------------------- | | `--sso` | Use the browser login flow | | `--email ` | Send an email OTP | | `--otp ` | OTP code (with `--email`) | | `--token ` | Store an existing access token | | `--url ` | Override the Playground login URL | Invalidate the remote session (best-effort) and remove the local credential. ```bash theme={"dark"} apollo logout ``` Show the active Apollo context: profile, checkout, authentication, organization, project, agent, version, runtime, and token expiry. ```bash theme={"dark"} apollo status apollo status --check # also check service connectivity and authorization ``` Diagnose the setup end to end: runtimes (Node/Bun), install origin, shell integration, `APOLLO_*` overrides, terminal capabilities, credential permissions, token expiry, the current checkout, and service reachability. Read-only, with the fix attached to every finding; exits `2` on problems. Share its output when reporting issues. ```bash theme={"dark"} apollo doctor apollo doctor --offline # skip connectivity checks ``` Upgrade Apollo from npm (`@aui.io/apollo`). ```bash theme={"dark"} apollo upgrade # install @latest apollo upgrade --check # show the available version without installing apollo upgrade 0.2.1 # install a specific version or tag ``` | Option | Description | | --------- | --------------------------------------------- | | `--check` | Show the available version without installing | | `--force` | Reinstall even when already on that version | Show or select the active credential profile. Profiles keep separate credentials for different users or contexts. ```bash theme={"dark"} apollo profile current apollo profile list apollo profile use work ``` `--profile ` before any command is a one-command override; it does not change the saved default. Change the default with `apollo profile use`. List organizations and re-scope the access token to one. `use` without an id opens an interactive picker. ```bash theme={"dark"} apollo --json org list [--name ] apollo org use [org-id] ``` You rarely need this directly — `apollo agent use` switches the organization-scoped token automatically when the selected agent requires another org. Manage projects — the containers agents live in. `use` inside a checkout updates that checkout's `.apollorc`; outside any checkout it sets the account default. ```bash theme={"dark"} apollo --json project list [--name ] apollo project use [project-id] apollo project create "Display name" ``` *** ## Agents & Checkouts Manage agents and their local checkouts. A **checkout** is a directory holding one agent, pinned by its own `.apollorc`. With [shell integration](/cli/installation#post-login-setup), `use`, `import`, and `create` also `cd` into the checkout. ```bash theme={"dark"} apollo --json agent list [--name ] # filter is server-side apollo agent use [agent-id|name] # enter a KNOWN checkout apollo agent path # print the last-used checkout path apollo agent create "Agent name" # provision + create a checkout apollo agent import [agent-id] # pull a cloud agent into a checkout apollo agent unlink # remove the local binding only ``` **`agent create`** options: | Option | Description | | ----------------- | ---------------------------------------------------------------------- | | `--dir ` | Checkout directory (defaults to a managed directory under `~/apollo/`) | | `--template ` | Template agent to go live on immediately (default: empty draft) | **`agent import`** options: | Option | Description | | -------------------- | ---------------------------------------------------------------------- | | `--name ` | Filter the interactive picker by name | | `--tag ` | Version tag (defaults to the live version) | | `--dir ` | Checkout directory (defaults to a managed directory under `~/apollo/`) | | `--skills ` | Comma-separated `cursor,claude,opencode` (default: `cursor,claude`) | | `--force` | Overwrite an existing local program | Import writes `.apollorc`, materializes the program files, registers the checkout, makes the agent active, installs the authoring skill packs, and records a local diff baseline. `agent use` only enters checkouts already known to the registry — import remote-only agents first. There is intentionally no delete command. `agent unlink` removes only the local binding. Select the runtime (engine build) this checkout runs against. ```bash theme={"dark"} apollo runtime list # runtime versions this account can select apollo runtime show # the runtime this checkout is pinned to apollo runtime use [version] # pin a version ('latest' takes the newest) ``` *** ## Authoring Validate the local agent program — the verdict plus the orphan and era censuses. Exits `2` on findings. Everything that must travel with the bundle is authored under `bundle/src/` — `validate` (and `push`) read only that tree. ```bash theme={"dark"} apollo validate [directory] # defaults to the bound checkout ``` Compare local files with the last baseline (the last successful import, pull, or push). ```bash theme={"dark"} apollo diff apollo diff --remote # compare with the selected remote version instead ``` Pull the selected agent version plus the schemas, skills, and `AGENTS.md` its runtime serves. Pull refuses to overwrite local changes unless forced; a successful pull makes `bundle/src` match the remote snapshot exactly, including deleting local files no longer in the remote bundle. ```bash theme={"dark"} apollo pull apollo pull --tag v2.3 apollo pull --force # discard local bundle changes and match remote ``` | Option | Description | | -------------------- | ------------------------------------------------------------------- | | `--tag ` | Version tag | | `--dir ` | Destination directory | | `--force` | Discard local bundle changes and match remote | | `--skills ` | Comma-separated `cursor,claude,opencode` (default: `cursor,claude`) | | `--no-skills` | Skip the skill packs and the checkout's `AGENTS.md` | | `--no-schemas` | Skip the authoring JSON Schemas | Validate and push local changes — a changed program mints a version; a changed `bundle/build` ships a build bundle. Push validates first, creates a draft from a published version when necessary, commits the bundle, updates `.apollorc` to the returned tag, and refreshes the diff baseline. ```bash theme={"dark"} apollo push --dry-run # validate and show the diff without pushing apollo push -m "Tighten refund eligibility" ``` | Option | Description | | ------------------------- | ------------------------------------------ | | `-m, --message ` | Commit message | | `--dry-run` | Validate and show the diff without pushing | Pushing is not publishing — a pushed version goes live only after `apollo version publish `. Manage agent versions. ```bash theme={"dark"} apollo --json version list apollo version get v2.3 apollo version use v2.3 # bind a tag in this checkout apollo version create --from v2.3 --label "Variant" # clone an existing version apollo version create --label "Empty draft" apollo version publish v3.0 apollo version archive v2.3 ``` **`version create`** options: `--from ` (clone an existing version) · `--template ` (create from a template) · `--label *** ## Server-Side Authoring & Evaluation Ask the server-side authoring advisor — ships this checkout's tree and waits for grounded advice. ```bash theme={"dark"} apollo advise "Why doesn't the refund gate fire on turn two?" ``` | Option | Description | | ----------------- | -------------------------------------------------------------- | | `--fresh` | Start a new advisor thread instead of continuing the saved one | | `--model ` | Advisor model override | | `--max-usd ` | Per-question spend cap (server default applies otherwise) | Run one build turn over this checkout's pushed agent, server-side. The turn is gated: green pushes a new version and pulls it into the checkout; red refuses. ```bash theme={"dark"} apollo build "Add a fee waiver for premium members" -m "Fee waiver" ``` | Option | Description | | ---------------------- | ----------------------------------------------------- | | `--version-tag ` | The base version to build from (defaults to the pin) | | `-m, --message ` | The push message | | `--model ` | Build model override | | `--max-usd ` | Per-turn spend cap (server default applies otherwise) | | `--evaluate` | Run the evaluation suite on the result before pushing | | `--publish` | Flip the agent's active pointer to the pushed version | Run the bundle's own scenario suite (`bundle/src/scenarios.yaml`) offline — the evaluation gate. Produces a report; never a push gate. ```bash theme={"dark"} apollo certify [directory] ``` Run the live evaluation suite server-side over this checkout — simulated callers plus a judge. The run is frozen at start, so you can keep editing. ```bash theme={"dark"} apollo evaluate --follow # wait for the verdict apollo evaluate -m "Post-refactor baseline" # note shown on the scoreboard apollo evaluate --status # read the last run's scoreboard apollo evaluate --cancel ``` | Option | Description | | ---------------------- | ----------------------------------------------------------------------------- | | `--follow` | Wait for the verdict instead of returning the receipt | | `--scenarios ` | Comma-separated scenario codes to scope the run (a scoped run never promotes) | | `-m, --message ` | A note on the run — shows up on the scoreboard | | `--version-tag ` | The tag stamped on the minted trial threads (defaults to the pin) | | `--status` | Read a run's scoreboard instead of starting one | | `--cancel` | Cancel a live run | | `--id ` | The run `--status`/`--cancel` mean (defaults to the last started) | *** ## Testing & Inspection Chat with an agent, or start an interactive session. Alias: `apollo send`. Calling `apollo chat` with no text opens a REPL in an interactive terminal; scripts should pass text explicitly with `--no-input`. ```bash theme={"dark"} apollo chat "Where is my order?" --var caller.id=CUST-1 apollo chat "Cancel order #W001" --var caller.id=CUST-1 --trace apollo chat "Cancel order #W001" --local . --trace # test local, unpushed files ``` | Option | Description | | --------------------- | ---------------------------------------------------- | | `--thread ` | Append to an existing thread | | `--local ` | Send a local program bundle inline | | `--var ` | Agent variable (repeatable; JSON values are decoded) | | `--trace` | Include the reply trace | A send without `--thread` creates and persists a thread — capture `data.thread_id` from `--json` output and pass `--thread` on later turns for multi-turn conversations: ```bash theme={"dark"} t=$(apollo --json send "Hi — I need to cancel an order" --local . | jq -r '.data.thread_id') apollo --json send "It's order #W001" --thread "$t" --local . | jq -r '.data.message.text' ``` Turns on one thread are strictly sequential (a racing turn is refused with `API_423`), but separate threads share nothing — run them concurrently (around 20 parallel threads is a sane cap). Inspect Apollo threads. Messages are sent as the signed-in user; `thread list` likewise lists the signed-in user's threads. ```bash theme={"dark"} apollo thread create apollo --json thread list [--size 25] [--after ] apollo --json thread get apollo --json thread messages apollo --json thread trace apollo --json thread trace --interaction ``` `trace` returns the runtime's official turn-trace view, whose vocabulary is the program's capabilities, policies, facts, sources, and records. *** ## Resources The agent's knowledge hubs — the corpora that `hub:` sources search at runtime. A source in `bundle/src/sources.yaml` names its hub (`hub: `), and the match is **by name**. Every `kb` command needs an agent checkout. ```bash theme={"dark"} apollo kb create POLICY_DOCS --description "Cancellation and refund policy" apollo kb add POLICY_DOCS ./policy/*.md # upload documents (indexing is async) apollo kb add-url POLICY_DOCS https://example.com/refunds apollo kb status POLICY_DOCS # wait for completed apollo kb check # local hub: refs vs the agent's remote hubs apollo kb list apollo kb delete POLICY_DOCS --force # irreversible ``` | Command | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------- | | `create ` | Create an empty hub in the agent's scope (`--description `) | | `list` | List hubs owned by the agent | | `add ` | Upload documents into an existing hub; indexing runs async | | `add-url ` | Scrape web pages into an existing hub; indexing runs async | | `status ` | Show a hub's latest indexing jobs | | `check` | Cross-check the program's `hub:` references against the agent's remote hubs — run after editing sources, before push | | `delete --force` | Permanently delete a hub and all its resources | A hub is inert until a pushed bundle declares it: the agent consults a hub only because a `kind: knowledge` source in the *running* program names it. After uploading, declare the hub in `bundle/src/sources.yaml`, then `apollo push` and `apollo version publish`. A reference to a nonexistent hub retrieves nothing, with no validation error. Manage vault secrets, referenced from the program as `{{vault.NAME}}`. ```bash theme={"dark"} printf '%s' "$API_KEY" | apollo vault set STRIPE_KEY --description "Stripe live key" apollo vault list # visible secrets, masked apollo vault get STRIPE_KEY # metadata and masked preview apollo vault rm STRIPE_KEY --force ``` | Command | Description | | ------------------- | -------------------------------------------------------------------- | | `set ` | Create or rotate a secret (`--value `, or prefer piped stdin) | | `list` | List secrets visible from the current scope (masked) | | `get ` | Show one secret's metadata and masked preview | | `rm --force` | Delete a secret (`409` while connections still reference it) | Manage the per-agent mock database — a test database the agent's connections can run against. ```bash theme={"dark"} apollo mockdb provision # create the mock DB and store keys locally apollo mockdb wire # write bundle/src/connections.yaml for mock-db apollo mockdb describe # schema, endpoints, and row counts apollo mockdb seed ``` | Command | Description | | ------------- | ---------------------------------------------------------------------------------------------- | | `provision` | Create the agent's mock DB and store runtime/management keys locally | | `status` | Show mock DB ownership and local key prefixes | | `describe` | Show schema, endpoints, and row counts | | `export` | Export schema, base data, and endpoints | | `keys` | Show locally stored key prefixes (no secrets) | | `wire` | Write `bundle/src/connections.yaml` for mock-db using the stored runtime key (ships with push) | | `collections` | Manage mock DB collections | | `seed` | Seed data | | `endpoint` | Manage named endpoints | | `execute` | Execute against the mock DB | | `session` | Inspect or reset session overlays | | `rotate` | Rotate mock DB keys | | `delete` | Permanently delete the mock DB and clear local keys | *** ## Shell Print the shell wrapper (zsh/bash) enabling auto-cd after `agent use` / `agent import` / `agent create`. Add to your shell profile: ```bash theme={"dark"} eval "$(apollo shell-init)" ``` ## Next Steps Checkout structure, config files, and environment variables. Common development workflows and patterns. # Configuration Source: https://docs.aui.io/cli/configuration Checkout structure, config files, and environment variables for the Apollo CLI. ## Checkout Structure A **checkout** is a local directory holding one agent. After `apollo agent import` or `apollo agent create`, it looks like this: ``` my-agent/ ├── .apollorc # org / project / agent / version_tag binding ├── bundle/ │ ├── src/ # the editable program source — the only tree push reads │ │ ├── program.yaml # the program manifest │ │ ├── vocabulary.yaml # the agent's typed vocabulary │ │ ├── situations.yaml # situations the agent recognizes │ │ ├── sources.yaml # data & knowledge sources (hub: references) │ │ ├── derivations.yaml # derived facts │ │ ├── connections.yaml # external connection wiring │ │ ├── capabilities/ # one directory per capability │ │ └── policies/ # behavioral policies (e.g. global.yaml) │ └── build/ # build & evaluation assets (scenarios, personas, …) ├── schemas/ # authoring JSON Schemas for every program file ├── AGENTS.md # authoring guide served by the agent's runtime ├── .claude/skills/ # authoring skill packs for Claude Code └── .cursor/skills/ # authoring skill packs for Cursor ``` | Path | Purpose | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `.apollorc` | Binds the directory to an organization, project, agent, and version tag | | `bundle/src/` | The program — typed YAML/Markdown files. **Everything that must travel with the bundle is authored here**; `push` reads only this tree | | `bundle/build/` | Build and evaluation assets that ship as a build bundle | | `schemas/` | JSON Schemas the CLI validates against, for editor autocomplete too | | `AGENTS.md` | The authoring guide the agent's runtime serves | | `.claude/` · `.cursor/` | Coding-agent skill packs installed on import/create (skip with `--no-skills` on pull) | Open the agent folder as your coding-tool project root so the skill packs are discovered. The skill packs are a snapshot from import time — `apollo pull` refreshes them along with the schemas and `AGENTS.md` (skip with `--no-skills` / `--no-schemas`). ### The `.apollorc` Binding ```json .apollorc theme={"dark"} { "config_version": 1, "org_id": "…", "project_id": "…", "agent_id": "…", "version_tag": "v7.2", "runtime_version": "0.8.11" } ``` Context is positional: the nearest `.apollorc` walking up from your current directory selects the agent. Commands run inside a checkout use its binding for organization, project, agent, and version. Don't edit `.apollorc` manually — `apollo agent use / import`, `apollo project use`, `apollo version use`, and `apollo runtime use` maintain it, and `apollo push` updates the tag on every push. *** ## User Configuration (`~/.apollo/`) ``` ~/.apollo/ ├── config.json # active profile and default projects ├── credentials.json # tokens (mode 0600 — the only secret-bearing file) ├── checkouts.json # known-checkouts registry └── baselines/ # sync hashes for diff/push ``` Never read, print, copy, or commit `~/.apollo/credentials.json` — it holds your access and refresh tokens. All other commands need no additional secrets. *** ## Environment Variables Environment variables override file-based configuration — useful for CI/CD pipelines. | Variable | Description | | ------------------------ | --------------------------------------------------------------- | | `APOLLO_TOKEN` | Skip login — authenticate with this token directly (CI auth) | | `APOLLO_PROFILE` | Select the credential profile | | `APOLLO_ORG_ID` | Organization ID override | | `APOLLO_PROJECT_ID` | Project ID override | | `APOLLO_RUNTIME_VERSION` | Override the checkout's pinned runtime version | | `APOLLO_TIMEOUT_MS` | Request timeout in milliseconds | | `APOLLO_TUI` | `0` keeps a bare `apollo` on the CLI welcome instead of the TUI | | `APOLLO_*_URL` | Per-service endpoint overrides | ```bash theme={"dark"} # Example: authenticate non-interactively in CI export APOLLO_TOKEN="$CI_APOLLO_TOKEN" apollo --json validate ``` *** ## Output & Exit Codes With the global `--json` flag, every command emits a stable envelope on stdout: ```json theme={"dark"} { "success": true, "data": { } } ``` ```json theme={"dark"} { "success": false, "error": { "code": "VALIDATION_ERROR", "message": "…", "suggestion": "…" } } ``` | Exit code | Meaning | | --------- | ------------------------------------ | | `0` | Success | | `1` | Runtime/API failure | | `2` | Validation failure | | `3` | Authentication/configuration failure | `--verbose` streams one **redacted** line per API request to stderr (auth/token/key/secret values never appear); the TUI shows the same stream live in its activity drawer. ## Next Steps Full reference for all CLI commands. Common development workflows and patterns. # Installation Source: https://docs.aui.io/cli/installation Install the Apollo CLI and authenticate with your account. ## System Requirements | Requirement | Needed for | | ------------------------------------ | ------------------------ | | **Node.js** ≥ 20 (includes npm) | everything | | [**Bun**](https://bun.sh) (optional) | the interactive TUI only | No npm login is required to install. Without Bun, the interactive TUI prints install guidance and the CLI remains fully usable. ## Install ```bash theme={"dark"} npm install -g @aui.io/apollo ``` Verify the installation: ```bash theme={"dark"} apollo --version ``` Upgrade at any time: ```bash theme={"dark"} apollo upgrade # install @latest from npm apollo upgrade --check # show the available version without installing ``` ## Authentication ```bash theme={"dark"} apollo login ``` With no options, `apollo login` opens the browser sign-in flow via the [Playground](https://apollo.aui.io); sign in and you're redirected back automatically. Other login modes: ```bash theme={"dark"} apollo login --email user@example.com # sends an email OTP, then prompts for it apollo login --token "$ACCESS_TOKEN" # store a pre-issued JWT ``` Non-interactive OTP is two steps — the first call sends the code, the second verifies it: ```bash theme={"dark"} apollo --json --no-input login --email user@example.com # → error: "OTP sent. Re-run the same command with --otp ." apollo --json --no-input login --email user@example.com --otp 123456 ``` Login stores access and refresh tokens in `~/.apollo/credentials.json` (mode 600 — the only secret-bearing file). Normal commands need no additional secrets, and credentials persist across terminal sessions. `apollo logout` invalidates the remote session and removes the local credential. ### Profiles Profiles keep separate credentials for different users or contexts: ```bash theme={"dark"} apollo profile current # show the active profile apollo profile list # list credential profiles apollo profile use work # select the default profile ``` The selected profile persists for later commands. `--profile ` before any command is a one-command override for automation — it does not change the saved default. ## Post-Login Setup ```bash theme={"dark"} apollo status ``` You should see your active profile, organization, project, and agent context. Add `--check` to also verify service connectivity and authorization, or run `apollo doctor` for a full end-to-end diagnosis. Add to your shell profile (the postinstall step offers to do this for you): ```bash theme={"dark"} eval "$(apollo shell-init)" ``` This lets `apollo agent use / import / create` drop your shell directly into the agent's checkout directory. ```bash theme={"dark"} # Create a brand-new agent (provisions it and creates a local checkout) apollo agent create "Returns Assistant" # Or pull an existing agent into a local checkout apollo agent import ``` Checkouts default to a managed directory under `~/apollo/`; use `--dir` for an intentional other path. Both commands install authoring skill packs for Cursor and Claude Code into the checkout. ## Uninstall ```bash theme={"dark"} npm uninstall -g @aui.io/apollo ``` To remove stored credentials and configuration: ```bash theme={"dark"} rm -rf ~/.apollo ``` This removes all stored credentials, the known-checkouts registry, and diff baselines. You'll need to run `apollo login` again. ## Next Steps Full reference for all CLI commands. Checkout structure, config files, and environment variables. # Apollo CLI Source: https://docs.aui.io/cli/overview Author, test, and operate Apollo agents from your terminal — a scriptable CLI and a full-screen TUI in one install. ```bash theme={"dark"} npm install -g @aui.io/apollo ``` The Apollo CLI (`@aui.io/apollo`) is the operator console for Apollo agents. One install gives you two surfaces: a scriptable command-line interface (`apollo `) and a full-screen terminal UI (bare `apollo`) for the author/test loop. It enables a **local-first development workflow**: import an agent as a typed YAML program, edit it in your IDE with schema support, validate, test against the live runtime, and push changes back as new versions. The CLI is designed to be driven by **coding agents** as much as by humans. Every command is fully non-interactive with a `--json` output mode, and importing an agent installs authoring skill packs for Cursor and Claude Code — so tools like Cursor, Claude Code, and the Agent Builder can author, run, and iterate on an agent end-to-end. ## Quick Start ```bash theme={"dark"} # 1. Authenticate (opens the browser) apollo login # 2. Create a new agent — or import an existing one with: apollo agent import apollo agent create "Returns Assistant" cd # 3. Edit the program under bundle/src/ (program.yaml, capabilities/, policies/, …) # 4. Validate your changes apollo validate # 5. Test against the live runtime without deploying apollo chat "I want to return an order" --local . --trace # 6. Push changes to the cloud as a new version apollo push -m "Describe the change" ``` ## Mental Model Context is positional: the nearest `.apollorc` walking up from your current directory selects the agent you are working on. `apollo status` always tells you where you stand. | Layer | What it is | Commands | | ---------------- | --------------------------------------------------------------- | ----------------------------- | | **Profile** | Named credential slot (e.g. `default`, `work`) | `profile *` | | **Checkout** | Local directory holding one agent (`.apollorc` + `bundle/src/`) | `agent use / import / create` | | **Organization** | Identity / billing org (on the access token) | `org *` | | **Project** | Container for agents | `project *` | | **Agent** | The program you edit and test | `agent *` | | **Version** | Tagged remote snapshot of the program | `version *`, `pull` / `push` | ## Key Features Import an agent as a typed YAML program under `bundle/src/`. Edit it in VSCode, Cursor, or any editor — the checkout ships JSON Schemas for every file. Version control is git: diffs against `policies/global.yaml` are real diffs, and rollback is `git revert`. `apollo validate` checks the local program deterministically before any push. `apollo diff` compares your working tree with the last baseline (or the selected remote version with `--remote`), and `apollo push --dry-run` previews exactly what would ship. Talk to your agent against the real Apollo-1 runtime with `apollo chat` — including your local, unpushed edits via `--local .` — and read the decision trace with `--trace`. Inspect full conversations with `apollo thread messages` and `apollo thread trace`. Ask the grounded authoring advisor a question with `apollo advise`, run a complete build turn with `apollo build` (gated: green pushes a new version, red refuses), and grade the agent with `apollo evaluate` — simulated callers plus a judge, run server-side over your checkout. Manage the full lifecycle — draft, publish, archive — with `apollo version`. Publishing is also the rollback operation: re-publishing an older version makes it live again. Create knowledge hubs and upload documents with `apollo kb`, spin up a per-agent test database with `apollo mockdb`, and store credentials referenced as `{{vault.NAME}}` with `apollo vault`. Run the bundle's own scenario suite offline with `apollo certify`, and the live evaluation suite — simulated callers and a judge — with `apollo evaluate`. Bare `apollo` on a TTY launches the full-screen terminal UI: an Author mode (validate, diff, push, pull, versions, resources) and a Talk mode (converse with the running agent), with an action palette (`Ctrl+K`) that shows the CLI equivalent of every operation. ## The TUI in One Minute Bare `apollo` on a TTY launches the TUI (`apollo tui` and `--tui` force it; any subcommand, `--json`, `--no-input`, or a non-TTY always runs the CLI). It has two primary modes — **Author** (the program cockpit) and **Talk** (converse with the running agent): | Input | Action | | ------------------------------ | ------------------------------------------------------------------ | | `Ctrl+1` / `Ctrl+2` (or `Tab`) | Author / Talk | | `Ctrl+K` | Action palette — every operation, with CLI equivalents | | `Ctrl+G` | Context switcher — profile / agent / project / org | | `Ctrl+J` | Activity drawer — operations, retry, output, live API-request tail | | `Esc` | Close overlay / cancel | In Author, single letters run quick actions (`v` validate · `d` diff · `p` push · `l` pull · `r` versions · `m` resources · `o` open in IDE), and free text is a copilot instruction to the author backend. In Talk, the composer sends everything to the agent verbatim. The TUI runs on [Bun](https://bun.sh). Without Bun installed, `apollo` prints install guidance and the CLI remains fully usable. ## Get Started Install the CLI and authenticate with your account. Full reference for every CLI command. Checkout structure, config files, and environment variables. Common development workflows and best practices. # Workflows Source: https://docs.aui.io/cli/workflows Common development workflows and best practices for the Apollo CLI. ## Import and Edit an Existing Agent The most common workflow: pull an agent from the cloud, edit it locally, validate, test, and push back. ```bash theme={"dark"} # Login (opens the browser) apollo login # Import the agent into a local checkout — with shell integration, this cds into it apollo agent import # Open the checkout in your IDE cursor . # or: code . # Edit the program under bundle/src/ # Validate your changes apollo validate # See what changed apollo diff # Test against the live runtime (see below), then push to the cloud apollo push -m "Explain the intent of this change" ``` Importing installs authoring skill packs for Cursor and Claude Code (add `opencode` via `--skills`), pulls the authoring JSON Schemas into `schemas/`, and drops the runtime's `AGENTS.md` guide into the checkout — open the agent folder as your coding-tool project root and no additional setup is required. *** ## Create a New Agent from Scratch `apollo agent create` provisions the agent server-side, creates a local checkout, and makes it active. ```bash theme={"dark"} apollo agent create "Returns Assistant" # Start from a template instead of an empty draft apollo agent create "Returns Assistant" --template # Edit bundle/src/, then: apollo validate apollo push -m "First version" ``` *** ## Test Against the Runtime Talk to your agent against the real Apollo-1 runtime. Pass `--local .` to send your working tree inline — replies reflect your latest edits, no push required. ```bash theme={"dark"} # One-shot probe with the decision trace apollo chat "I want to return an order" --local . --var caller.id=CUST-1 --trace # Multi-turn conversation: capture the thread id, then keep appending to it t=$(apollo --json send "Hi — I need to cancel an order" --local . --var caller.id=CUST-1 \ | jq -r '.data.thread_id') for msg in \ "It's order #W001" \ "Will I be charged a fee?" \ "OK, go ahead"; do apollo --json send "$msg" --thread "$t" --local . --var caller.id=CUST-1 \ | jq -r '.data.message.text' done # Inspect the conversation afterwards apollo --json thread messages "$t" apollo --json thread trace "$t" ``` **Test multi-turn threads, not single messages.** Most defects live on turn two and later — a read-back that never executes, a gate that promises and then refuses. Make scripted conversations 5–7 turns. Separate threads are independent: run them as parallel processes (around 20 concurrent threads is a sane cap); turns on one thread are strictly sequential. *** ## Validate, Certify, and Push The authoring arc is **pull → edit → validate → certify → push**: ```bash theme={"dark"} apollo pull # program + schemas + skills + AGENTS.md # edit bundle/src/… apollo validate # structural verdict (exit 2 on findings) apollo certify # run the bundle's own scenario suite offline apollo push --dry-run # preview what will ship apollo push -m "Tighten refund eligibility rule" ``` `validate` checks structure; only `certify` proves conduct — that gates refuse, read-backs execute, and writes land. A bundle with no `bundle/src/scenarios.yaml` has never been graded. *** ## Publish a Version Pushing mints a version; to make it live, publish it. ```bash theme={"dark"} apollo version list apollo version publish v3.0 # Rollback is publishing an older version — it becomes live again apollo version publish v2.3 # Compare before publishing apollo diff --remote ``` *** ## Evaluate the Agent Run the live evaluation suite server-side — simulated callers plus a judge — over your checkout. The run is frozen at start, so you can keep editing while it runs. ```bash theme={"dark"} apollo evaluate --follow # wait for the verdict apollo evaluate -m "Post-refactor baseline" # note shown on the scoreboard apollo evaluate --scenarios REF-01,REF-02 # scope the run (never promotes) apollo evaluate --status # read the scoreboard later ``` *** ## Server-Side Authoring Two commands move authoring itself to the server, grounded in your checkout: ```bash theme={"dark"} # Ask a grounded question — ships the tree, waits for advice apollo advise "Why doesn't the fee waiver fire for premium members?" # Run one gated build turn: green pushes a new version and pulls it back, red refuses apollo build "Waive cancellation fees for premium members" -m "Fee waiver" --evaluate ``` Add `--publish` to `build` to flip the agent's active pointer to the pushed version in the same turn. *** ## Ground the Agent in Knowledge Knowledge hubs are the corpora that `hub:` sources search at runtime. Every step below is load-bearing — the last two are the ones people skip: ```bash theme={"dark"} apollo kb create POLICY_DOCS --description "Cancellation and refund policy" apollo kb add POLICY_DOCS ./policy/*.md # or add-url for pages to scrape apollo kb status POLICY_DOCS # wait for completed — indexing is async # → declare it in bundle/src/sources.yaml: # - id: handbook # kind: knowledge # hub: POLICY_DOCS apollo kb check # hub: refs vs the agent's remote hubs apollo validate apollo push -m "Answer policy questions from POLICY_DOCS" apollo version publish # pushing is not publishing ``` To test retrieval **before** pushing, send the working tree inline with `apollo chat --local .` from inside the checkout. *** ## CI/CD Integration Authenticate with a token, force non-interactive mode, and parse the JSON envelope. ```bash theme={"dark"} export APOLLO_TOKEN="$CI_APOLLO_TOKEN" apollo --json --no-input validate apollo --json --no-input push -m "$COMMIT_MESSAGE" ``` Exit codes are stable: `0` success, `1` runtime/API failure, `2` validation failure, `3` auth/config failure — gate pipeline steps on them. `apollo --verbose ` streams one redacted line per API request to stderr — handy when debugging a pipeline. `apollo doctor` diagnoses the whole setup with the fix attached to every finding. *** ## Driving the CLI with a Coding Agent The CLI is built to be driven by coding agents (Cursor, Claude Code, the Agent Builder). Every command is non-interactive with `--json`, and importing an agent installs skill packs that teach the workflow. * **Author → run → read the trace → revise.** Have the agent edit `bundle/src/`, then `apollo --json chat … --local . --trace`, read the trace, and iterate until behavior holds. * **Put global flags before the command** — `apollo --json --no-input …` — and parse the envelope (`data` on success, `error.code`/`message`/`suggestion` on failure). * **Keep the checkout fresh.** `apollo pull` refreshes the program, schemas, skill packs, and `AGENTS.md` the runtime serves. *** ## Next Steps Full reference for all CLI commands. Checkout structure, config files, and environment variables. # Apollo-1: Reasoning in Code, Not Weights Source: https://docs.aui.io/introduction A new kind of reasoning over language — reasoning you can read, that runs exactly as written. Reasoning in Code, Not Weights: Apollo-1 ## Summary Apollo-1 introduces a new kind of reasoning over language: reasoning you can read, that runs exactly as written. A language model's reasoning lives in weights — you cannot read it, and its behavior is sampled. A prompt is readable but only advisory; the model may follow it or not. Apollo-1's reasoning is neither. It is written down in full, and it runs as written. What you read is what runs. That reasoning is a program — a file the company authors, edits, and owns. Not behavior frozen in a vendor's weights, not logic buried inside a vendor's application, but the company's own artifact: readable end to end, version-controlled, auditable, and changed when the business changes. The principal is the company. An Apollo-1 agent talks to a user but answers to a business — booking, claims, returns, disputes, payments — where generative AI works for the user: the developer, the employee, the individual. The mechanism is three layers, and only three. A typed symbolic language for task-oriented reasoning. Programs written in it — each program an agent. And a runtime that runs them: one frozen model that takes a program, a user's message, and live context, and produces behavior. Language, programs, runtime. Because that one model runs any program written for it — generalizing to programs, situations, and phrasings it was never shown — it is a foundation model in the strict sense, and the agents are what it runs, not what is baked into it. This is possible because the domain is finite: the procedural structure of task-oriented dialogue is a closed, compact grammar, and a single model can cover it. Building and changing an agent is software work, and that is the deeper point. Once reasoning is a program, it is a software artifact — versioned, diffed, reviewed, tested, composed, owned — and it improves the way software improves. Long-horizon coding agents made this practical: the program, the runtime call, and the trace all live in one medium a coding agent reads and writes, so it can author a change, run it, read the trace, and revise until the behavior holds. That loop is what turns a faithful program into a reliable one, and what closed the cost of authoring and maintaining logic this complex — the standing argument against neuro-symbolic AI at scale. Run it enough and the gains compound: a behavior made correct stays correct, so quality becomes a function of compute. A different kind of model, for a different kind of agent. Reasoning in code, not weights — which is to say, reasoning as software. *** ## Neuro-Symbolic AI Neuro-symbolic AI composes neural perception with symbolic logic in a single model. Decisions are computed from typed symbolic state, not sampled from token probabilities. The architecture has been a research direction for decades, and it produced no foundation model. Classical symbolic AI tried to encode meaning into its symbols, which forced ontologies to represent the world. The world did not fit. The structures did not compose across domains, and the maintenance cost crushed every implementation. Apollo-1's symbols are procedural, not semantic. They carry roles, relations, state transitions, and predicates over state — the grammar of task-oriented dialogue, not a description of the world. Content stays in the neural modules. The symbolic layer knows where a value sits in a program and what role it plays; it does not represent what the value means. This is the separation classical AI never made, and it is the line between a foundation model and a graveyard. Semantic common sense — that water is wet, that the dead stay dead — is about the world, and it is infinite; it is what earlier symbolic programs spent decades trying to write down while the world refused to fit. Procedural common sense — confirm before charging, identify before acting, never reverse what never happened — is not about the world at all. It is the shape of finishing a task through conversation, and it is finite. The procedural grammar can be completed; the semantic one cannot. Apollo-1 takes the first and refuses the second. This is the load-bearing claim of the architecture, so it is worth stating precisely: the procedural grammar is finite by construction. We built it, over years, by dissecting task-oriented conversations into their recurring structures. The same structures recur under different content, so generalization works on the structures while the values that fill them change turn to turn. Novel inputs are mapped onto that finite set at inference. When the mapping is imperfect, the result is task failure, not a silently broken rule. Apollo-1 is the first foundation model for neuro-symbolic AI: a single model, frozen, that generalizes one capability — reasoning over typed symbolic state, binding it to natural language — across the whole space of task-oriented reasoning programs written for it. A language model's domain is text; Apollo-1's domain is those programs. Same model, different program, different agent, and an improvement to the model propagates to every program built on it. Three architectural consequences run through the rest of the paper. Language and logic operate in one computation. The agent's cognition is a program, separate from the model that runs it. And because the program is software, the agent improves through the loop that has driven a decade of machine learning — propose a change, validate it, keep what scores — with one addition that matters: because what scores is preserved as a test, the gains compound rather than regress. *** ## Two Kinds of Agents Two different systems are emerging under one word. They are not variations of the same thing. They have different principals, different jobs, and cognition in different places. Open-ended agents work for users: coding assistants, personal AI, employee tools. The user is the principal, and flexibility is the point. Cognition lives at the model provider, in weights, and the user adapts to whatever the latest model does. The LLM is the right substrate for this. Task-oriented agents work on behalf of companies: the agent that handles a claim, schedules a procedure, processes a return, files a dispute, authorizes a transfer, books a seat. These agents serve users, but they represent the company — the entity whose policies must be enforced, whose lawyers must approve, whose compliance team must audit, whose product team must change behavior when the business changes. For these, cognition cannot stay implicit at a provider. It has to be an artifact the company holds. Task-oriented agents require three properties, and an architecture has to provide all three at once: **Reasoning over both language and state, in one model.** Users do not follow scripts. They ask unexpected questions, change their minds, wander. The agent has to reason over what they say. At the same time it must evaluate conditions against state and enforce its rules without exception: the ticket cancels only when the passenger is Business Class and Platinum Elite; the payment processes only on explicit confirmation; the refund issues only on documented eligibility. Open-ended language and formal reasoning over state have to meet inside one model, not across two systems trading messages. **Mutable cognition.** An airline's cancellation policy changes; a bank's dispute window changes; a hospital's scheduling rules change. The agent has to move with the business, without retraining and without a model release. **Intelligence in a program, not in weights.** The agent has to be an artifact compliance can read, legal can sign, engineering can version. Cognition in weights cannot be read, audited, or attributed to a decision. Without an artifact to point at, behavior is no one's responsibility — and no enterprise deploys responsibility it cannot assign. *** ## Why Current Approaches Struggle Two architectures dominate task-oriented AI today: orchestration frameworks and function-calling LLM agents. Both wrap an LLM in different scaffolding, and both fail the three-property test for the same structural reason: rules are not a first-class object in either. **Orchestration frameworks** wrap an LLM in a workflow system — state machines, routing, branching. The state machine reasons; the LLM converses; the two do not share understanding. A user is mid-payment and asks, "wait — what's the cancellation policy before I pay?" No transition was coded for the digression, so the system breaks, gives a canned reply, or forces the user back on script. You add a branch. Then users ask about refunds mid-payment, or shipping. Real deployments accumulate hundreds of branches and still miss edge cases. Hand off to the LLM instead, and it has no model of the flow, the rules, or the accumulated state, so it may process the payment without confirmation because it is predicting a token, not reasoning from state. Conversation and reasoning end up inversely correlated: the tighter the state machine, the worse the experience; the more the LLM is trusted, the less the behavior holds. **Function-calling agents** take the opposite approach: give the LLM tools and let it decide when to call them. Conversation works. But the decisions are sampled from a probability distribution, not computed from state. Prompting, fine-tuning, and output filtering reduce unwanted tool calls; they do not eliminate them. The model might call the refund function without verifying documentation, skip a confirmation, or invoke a tool with the wrong parameters. Validation layers that gate a call before it executes help, but they are reactive — the agent has already decided to act — and each one is written per tool, not derived from a shared model of the domain. Both treat rules as add-ons. In orchestration, a rule is a branch — part of a flow, not part of a model. In function-calling, a rule is a sentence in a system prompt — advisory, soft, forgettable. Neither captures what rules actually do in task-oriented cognition. Rules are not only enforcement; they are the structural layer that lets symbolic logic and neural reasoning coexist. When a rule is a symbolic predicate the runtime evaluates, the symbolic side holds the logic absolutely while the neural side handles whatever language arrives. The rule does not constrain the conversation. It stabilizes the cognition. Without rules as structure, the model has nothing to hold, and conversation and reasoning revert to the inverse correlation neither approach can escape. Until neuro-symbolic AI, no architecture combined open-ended conversation with reliable enforcement in one model. *** ## Origins In 2017 we began encoding millions of real task-oriented conversations into structured data, with a workforce of 60,000 human agents. The insight was not data scale; it was what must be represented — and why it could only be learned, not written down. Task-oriented conversational AI requires two kinds of knowledge in tandem. Descriptive knowledge — entities, attributes, domain content. Procedural knowledge — roles, logic, flows, policies. Datasets are stateless; logic requires explicit state. We began building a typed symbolic language to capture these recurring structures. Why 60,000 agents and not an ontology team? Because the knowledge we needed is tacit. The procedural sense of how a skilled person actually closes a claim, or defuses a dispute, or knows when to confirm and when to refuse, is written in no rulebook; it lives only in the doing. Tacit knowledge cannot be stated, only distilled from behavior. That is the deeper reason classical AI was doomed in principle and not merely in practice — it set out to write down what can only be extracted — and it is why our path ran through millions of real conversations ranked by reputation rather than through a specification. Around 2021 the leap in language models arrived. Modern LLMs replaced the pre-transformer foundations our neural stack had been built on. Language stopped being the bottleneck, and only then did the rest of the architecture become reachable. Across every domain we tested — booking, scheduling, claims, disputes, renewals, authorizations — task-oriented dialogue followed the same procedural patterns: parameter extraction, intent identification, logic evaluation, policy enforcement, state-dependent branching. We built the typed symbolic language out across these structures, and the neuro-symbolic reasoner that computes next actions from encoded state. The procedural logic inside the engine was not trained into weights; it was taught — distilled over years from those conversations into symbolic structure, by dissecting them into their elements and ranking contributions through a peer-review reputation system. Augmented Intelligence — our name — is the term for the loop that produced it. The second outside contribution arrived in mid-2025: coding agents able to read, modify, and verify structured codebases end-to-end. Cognition expressed as code is the end-state of a typed symbolic language — what the language was designed to enable. The maintenance cost of authoring and evolving programs in our language had been the standing argument against neuro-symbolic AI at scale. With coding agents at production capability, that argument closed. Two things had to be true at once, and only recently were. On our side, a coding agent had to be able to drive the runtime directly: the language complete enough to express an agent in full, the CLI and API working under real version control, and the path from the files to the runtime cleared down to the program, the prompt, and the context — removing the last code between them is what brought it within reach. On the other side, coding agents had to become long-horizon, able to work a problem for hours rather than answer in one pass. The first half was ours to build; the second was not. They arrived together, which is why this is only now possible. And the fit is not luck: the reasoning is code because it was taught, not learned into weights, so advances in coding agents accrue to this architecture rather than threaten it. *** ## Apollo-1 Apollo-1 is built on neuro-symbolic architecture. Its inputs are typed symbolic programs and natural-language messages. Its outputs are typed symbolic states and natural-language responses. Reasoning happens in one pass over a single representation: neural modules handle language and perception, symbolic modules handle state and logic, both operating together inside the same computational loop. The same computation that writes the sentence checks the rule. There is no moment at which the model could choose to break a rule, because rules are part of the computation that produces the response, not a check wrapped around it. What makes Apollo-1 distinct from any other foundation model is where its cognition lives. A language model's cognition lives in weights — a parameter tensor whose behavior is implicit, produced by training, modifiable only by more training. Apollo-1's cognition lives in code — a symbolic program whose behavior is explicit, produced by writing, modifiable by editing. The runtime compiles the program and executes it. The program is the agent. Two agents on Apollo-1 run the same runtime and different programs; what makes one a refund agent and another a claims agent is a file. This is what makes Apollo-1 a foundation model, and the claim rests on what it generalizes over. A language model generalizes over text; Apollo-1 generalizes over the space of task-oriented reasoning programs — one frozen model that runs any program written in its symbolic language and produces correct behavior for it, with an improvement to the model reaching every program at once. ### The Symbolic Language Apollo-1's symbolic substrate is a typed programming language for task-oriented reasoning: a grammar that covers a finite domain — task-oriented dialogue — and a runtime that compiles programs written in it. The language has three properties: 1. **It is typed** — Every entity, parameter, rule, and tool has a type the runtime checks, and a program that does not type-check does not run. 2. **It is finite in its procedural states** — Across every domain we have studied, the same procedural structures appear under different content, and the grammar covers their full set. 3. **It is expressive over content** — Any value can occupy any field, because the symbols describe procedural roles, not world meaning. An Apollo-1 agent's program is a typed YAML codebase under `bundle/src/`: a program manifest (`program.yaml`), the agent's vocabulary, situations, sources, and derivations, a `capabilities/` directory with one capability per folder, and a `policies/` directory holding the rules the agent enforces. Together they describe what the agent does, what it knows, what it must enforce, and what it can call. The program is text, readable end-to-end by anyone who can open a file. ### The Runtime Apollo-1's runtime is a neuro-symbolic reasoner. At inference it takes a typed symbolic program and a natural-language message and produces behavior, turn by turn, against live state. A domain-agnostic encoder parses the message into typed symbolic objects, forming the initial state. A stateful loop then iterates until the turn completes: a neuro-symbolic state machine maintains symbolic state, a symbolic reasoning engine computes the next action from that state, and a neuro-symbolic planner compiles executable plans. A domain-agnostic decoder generates language from the final state. Perception is probabilistic; action selection is not. Given the same state, the runtime makes the same decision, and every decision in a trace is reproducible from the state that produced it. End-to-end outputs are not deterministic, because perception runs through the neural modules and two phrasings of one request can form different initial states. Once a state is formed, the logic over it is fixed. Failures in perception surface as task failure, not as policy violation. ### Authoring Apollo-1 does not write agents. It runs them. Programs are written by coding agents — in the CLI, by the developer's coding agent; in the Playground, by the Agent Builder, a coding agent embedded in an authoring harness. Apollo-1 provides the language, the runtime, the schema, the templates, and the documentation. The coding agent does the writing. When the coding agent can run programs on the runtime, the target of authoring changes. It is no longer a well-typed program that expresses the policy; it is a program whose runtime behavior matches the intent. The agent writes a candidate, compiles it, runs it against scenarios, reads the trace, and revises until the observed behavior and the intended behavior agree. The scenarios need not be supplied by hand — the coding agent derives them from the program and the change it just made. The unit of work shifts from producing an artifact to converging on a behavior, and most of the work is no longer generation; it is running programs and reading traces. *** ## Properties Many properties follow from cognition being code rather than weights. Four are central: rules become a first-class object; every decision is recorded in a trace anyone can read; a faithful program can be made reliable by running it; and reasoning, being a program, becomes software in the full sense. ### Rules as a First-Class Object In Apollo-1, rules are typed symbolic predicates the runtime evaluates against state. The agent does not break a rule for the same reason a compiler does not ignore a type: the rule is part of the computation that produces the response, not a check around it. When you define your capabilities, Apollo-1 generates an ontology — a typed representation of your entities, parameters, and relationships, shared across all your capabilities — and from it you author the rules the agent must enforce. Apollo-1 expresses them as predicates that live in the program's `policies/` files as code: policy rules (unconditional enforcement), confirmation rules (explicit consent before execution), authentication rules (identity before execution), conditional rules (applied only when conditions hold), and sequencing rules (enforced ordering). Evaluation is deterministic. If the predicate is `today − txn.date ≤ 8 days` and the transaction is nine days old, the action is blocked, every time. Perception remains probabilistic; the system can misunderstand a request, but it cannot decide to skip a required step or forget a policy mid-conversation. The payoff is wider than enforcement. When rules are part of the computation, they do not constrain the conversation; they stabilize the cognition. The symbolic side holds the logic absolutely — the predicate fires or it does not — and the neural side stays free to handle whatever language arrives. ### White-Box Traceability Every turn produces a trace that records the symbolic computation in full: the intent as parsed, the entities resolved, the tools considered, the rules evaluated, the predicates that fired, the parameters extracted, the decisions made and the reasons attached. Each is a real object in the trace, addressable in code, comparable across turns. Apollo-1's trace is the computation, recorded as it happens. The runtime cannot decide one thing and trace another, because the decision and the trace are the same object. "Why did you block that?" has a literal answer: this rule, this predicate, this state. ### From Faithful to Reliable Authoring produces a faithful program: it runs exactly as written. Faithful is not reliable. Reliable is faithful and written right, and for logic this complex, whether it is written right is an empirical fact, not one you can read off the page. The run-read-revise loop: runtime access, so the agent can see what the program actually does, and a long horizon, so it can keep going until the behavior holds. Because the program, the scenario, the runtime call, and the trace are all in one medium the agent reads and writes, the loop runs without a human in it. Quality becomes something you buy with compute. ### Reasoning as Software Once cognition is a program rather than a parameter tensor, it is not only readable and ownable — it is software, and it inherits the entire software lifecycle. An Apollo-1 agent is versioned and diffed; forked into a variant for a new market or a new regulator; reviewed line by line before it ships; tested against scenarios; composed from shared modules; packaged; and, in principle, open-sourced. Apollo-1 does not just let a company own its reasoning. It makes a company's reasoning software. The breakthrough is not that reasoning is written in code. It is that reasoning becomes software. *** ## The CLI and the Playground Apollo-1 agents are authored in two places. The CLI is the developer surface; the Playground is the working surface for everyone touching an agent — engineers, compliance officers, operations leads, product managers, customer-experience owners. Both edit the same typed JSON codebase. Both run against the same runtime. ### The CLI An Apollo-1 agent is a typed YAML codebase, edited in Cursor, VS Code, or any editor with full schema support. Version control is git. Diffs against `policies/global.yaml` are real diffs. Programs are written by coding agents in the developer's terminal or IDE: the developer says what the agent should do, the coding agent generates the program against the language specification, the developer iterates. Pull requests work as pull requests work; tests against fixtures work as tests against fixtures work. An agent's history is a commit log, its review is a code review, its rollback is a git revert. ### The Playground The Playground is the agent's working surface. Engineers use it to inspect reasoning, watch rules fire, and iterate on a policy without leaving the browser. Stakeholders without code in their workflow use it to author and edit the program in natural language. Two surfaces sit side by side. On the right, the agent — the code at runtime; it talks and acts, and clicking any turn opens the white-box trace: initial state, execution, rule evaluation, generation. On the left, the program, addressable in three modes: Build (English, where the Agent Builder authors and modifies the program), View (the structured UI), and Code (the raw program files). In Build mode you work with the Agent Builder — a coding agent in a context-rich harness with read-write access to the program, live access to every turn's trace, schema validation that ensures changes type-check, scenario evaluation, and a quality gate that decides what commits. *** ## What Apollo-1 Isn't For Apollo-1's architecture makes deliberate trade-offs. By optimizing for task-oriented agents, it does not compete in other domains, by design. **Open-ended creative work** — writing, brainstorming, exploratory dialogue, where variation creates value — is better served by transformers; Apollo-1's structures enforce consistency, and creativity often requires the opposite. **Code generation** — Apollo-1 can integrate with code-execution tools, but its language is purpose-built for task execution, not software development. **Low-stakes, high-variation settings** — engagement campaigns, tutoring, entertainment chatbots — are better served by probabilistic variety than by formal enforcement. *** ## General Availability Apollo-1 is generally available, deployed at scale across dozens of enterprises in regulated and unregulated industries, including Fortune 500 companies, with a strategic go-to-market partnership with Google. The [Playground](https://apollo.aui.io) is open, featuring Apollo-1 agents across HR, IT, regulated industries, retail, automotive warranties, and more. Each agent runs from its program alone, viewable as code or in a UI view for non-technical stakeholders, and the Agent Builder is available on every one of them. Apollo-1 integrates with existing generative-AI workflows and adapts to any API or external system — no endpoint changes, no data preprocessing — with native connectivity to Salesforce, HubSpot, Zendesk, and others, and full MCP support. Available today: * The Conversational API — for task-oriented dialogue * The [Playground](https://apollo.aui.io) and the [`apollo` CLI](/cli/overview) * Full documentation and toolkits Coming next: the Workflow Automation API and voice support. Apollo-1 improves on three axes. Its neural modules improve with every advance in low-latency LLMs. Its symbolic language evolves as we extend its coverage of task-oriented reasoning. And as coding-agent capability advances, so does the ease of building on and evolving Apollo-1. *** ## Conclusion Open-ended agents work for users; their cognition lives at the model provider, and that suits the user. Apollo-1 is the foundation model the other kind of agent runs on — agents that act on behalf of the entity the user is talking to, whose cognition the company writes, owns, and changes. Because the agent is a program rather than a behavior trapped in weights, the company can read it, sign it, version it, and improve it, and Apollo-1 compiles it into reliable execution. Filing claims, opening disputes, processing returns, authorizing payments, completing bookings: these are the conversations that run the economy, and they are too consequential to leave to behavior no one can read. The deeper shift is what becomes possible once reasoning is a program. It stops being a model you rent and becomes software you own — versioned, tested, reviewed, and improved on a loop that turns compute into reliability, and surrounded, in time, by the same ecosystem every other kind of software has. A different kind of model, for a different kind of agent, on a different reasoning framework: the first an organization can not only call its own, but build on as software. Reasoning in code, not weights — reasoning, at last, as software. Augmented Intelligence (AUI) Inc. Patents pending. *** ## Get Started Build your first agent and send messages. Send messages to agents and manage them programmatically. Integrate agents with the TypeScript/JavaScript client. Build and manage agents from your terminal. Latest updates and API changes. # Agent Hierarchy Source: https://docs.aui.io/overview/agent-hierarchy Understanding Apollo-1's three-level organizational structure Apollo-1 organizes agents across three levels, providing a clear separation of scope and configuration. ## Organization The top-level entity representing your company. An organization contains multiple projects and provides the overarching identity for all agents within it. ## Project A workspace for a team or product. Each project lives within an organization and houses multiple agents. Projects allow teams to manage their own set of agents independently. ## Agent An individual conversational AI with its own configuration, settings, entities, and tools. Each agent operates within a project and represents a specific use case or domain. ### Agent Versions Every agent has multiple versions that track changes to its configuration over time. Versions enable safe iteration, testing, and deployment of agent behavior. **Version States:** * **Draft** — Editable working version. You can modify configuration, test changes, and iterate freely. Not live. * **Published** — Frozen snapshot. Exactly one published version is the agent's **live** version — the one conversations run against. * **Archived** — Preserved but retired. Archived versions remain in history; the live version can't be archived. **Version Lifecycle:** 1. Create a **draft** version and edit the agent's program — every `apollo push` mints a new draft version, addressed by its tag (e.g. `v3.2`) 2. **Publish** the draft to freeze it and make it live — pushing is not publishing 3. Publish another version to switch or roll back — re-publishing a previous version re-activates it 4. **Archive** old versions to clean up while preserving history ``` Organization ├── Project A │ ├── Agent 1 │ │ ├── Version v1.0 (Published — live) │ │ ├── Version v1.1 (Published) │ │ ├── Version v2.0 (Draft) │ │ └── Version v0.9 (Archived) │ └── Agent 2 │ ├── Version v1.0 (Published — live) │ └── Version v1.1 (Draft) └── Project B ├── Agent 3 └── Agent 4 ``` ### Version Pinning When creating conversation threads or sending messages, you can pin to specific versions: * **By version ID** — Pin to a specific draft or published version * **By version tag** — Use semantic labels like `v8.14` to reference revisions * **Default behavior** — If no version is specified, the agent's live version is used This enables testing changes before promotion: ```bash theme={"dark"} # Bind the checkout to a draft tag, then chat against it apollo version use v2.0 apollo chat "Where is my order?" # Without a bound tag, sends target the agent's live version ``` The whole [program](/overview/agent-program) — vocabulary, capabilities, policies, sources — is versioned together. When you edit an agent, you're editing a specific version. Changes to a draft don't affect the live version until you publish it. # The Agent Program Source: https://docs.aui.io/overview/agent-program An agent is a folder of typed YAML that states a business — what it can know, what it may do, what it must never do, and what it says. An Apollo-1 agent **is its program**: a folder of typed YAML files that states a business, not a runtime. You never describe stages, prompts, or models. You describe what the agent can **know**, what it may **do**, what it must **never** do, and what it **says** — and the runtime turns that into behavior, turn by turn. Each concern lives in its own file, so the program stays readable end to end: | File | What it states | | ------------------ | -------------------------------------------------------------------------------------------------------------- | | `program.yaml` | Who the agent **is** — identity, voice, welcome, caller context (the only required file) | | `vocabulary.yaml` | The **nouns** — the [facts and records](/overview/vocabulary) the agent can know and hold | | `sources.yaml` | Where facts come from and what changes the world — every read and write | | `derivations.yaml` | [Facts worked out from other facts](/overview/derivations) — business arithmetic, thresholds, lookups | | `situations.yaml` | Standing truths about the world, projected as `state.*` for gates to read | | `capabilities/` | One folder per thing the agent can **do** — see [Capabilities & Policies](/overview/capabilities-and-policies) | | `policies/` | The gates and guardrails — what must hold before an action proceeds | | `connections.yaml` | Live wiring for sources (endpoints, auth, knowledge hubs); credential values stay in the vault | A handful of more specialized files cover advanced ground — conversation places, procedures, plans, approval chains, org roles — but every program starts with the table above, and only `program.yaml` is required. ## Identity: `program.yaml` `program.yaml` holds what the agent *is*, not what it can do: ```yaml theme={"dark"} id: returns-desk caller: fields: [customer.id] context: given: [customer.id] # supplied by the host at session open voice: tone: > Warm and direct. Lead with the answer, then the reason. never_say: [ "as an AI", "unfortunately" ] welcome: say: "Hi! I can help with returns, refunds, and order status." ``` * **`voice`** — the agent's register: `tone`, `brevity`, and phrases it must `never_say`. * **`welcome`** — the opening line, before the caller says anything. * **`caller` and `context`** — who is on the line and which facts the host supplies at session open (`given`), refreshes, or loads. * **`clock`** — what "today" means when rules are read against dates. ## Where the goal and guardrails live There is no goal prompt. What the agent does is stated by its **capabilities**, each with its own intent; what it must never do is stated by its **policies and obligations** — enforced predicates, not advisory text. That separation is the point: identity is declared once, behavior is declared per capability, and constraints are rules the runtime evaluates rather than suggestions a model may forget. ## How the parts meet at runtime A caller's message is routed to a capability by its `use_when`. The capability names the facts it `needs`; the runtime establishes them — from context, from sources, from derivations, or by asking the caller in the fact's own words. Policies gate the action before it proceeds, obligations check the reply before it ships, and every decision lands in the trace with the rule that produced it. Facts and records — the nouns everything else is built on. What the agent can do, and the gates that govern it. # Capabilities & Policies Source: https://docs.aui.io/overview/capabilities-and-policies What the agent can do, and the gates that govern every action and every reply. Two declarations carry an agent's behavior. **Capabilities** state what it can do; **policies** state what must hold before it does. Both are business statements the runtime enforces — never prompts, never function schemas. ## Capabilities A capability is one thing the agent can do, in one folder: `capabilities//capability.yaml`. ```yaml theme={"dark"} id: cancel-order intent: Cancel an order the caller placed. use_when: > The caller wants to cancel, stop, or undo an order. needs: [order.id, order.status] reads: [orders] changes: [orders] # naming a source here makes this an ACTION confirm: show: [order.id, order.total] warn: "This can't be undone once the order has shipped." report: outcomes: - when: order.status is "shipped" say: > This order has already shipped, so it can't be cancelled — but I can start a return instead. - otherwise: say: "Done — {order.id} is cancelled and the refund is on its way." ``` The key difference from tool-calling: **the author never writes the orchestration.** The runtime routes to the capability on `use_when`, works out each fact in `needs` — from context, sources, derivations, or by asking the caller in that fact's own words — and only then proposes the action. `reads` names the sources consulted; `changes` names what the action writes, and a `changes` capability takes a `confirm` read-back — an irreversible write without one refuses to build. ## Policies: the gates A policy is a gate: a condition that must hold before an action proceeds, and what to do when it doesn't. Agent-wide policies live in `policies/global.yaml`; capability-scoped ones sit next to the capability. ```yaml theme={"dark"} policies: - id: refund-needs-a-delivered-order intent: Refunds are only issued for orders that actually arrived. applies_to: [refund-order] requires: order.status is "delivered" otherwise: refuse: > I can only refund an order once it's been delivered. offer: "I can check the delivery status for you." ``` * **`requires`** fails closed — if the condition is unknown, the action does not proceed. **`forbids`** fails open. Choosing between them is choosing what happens when the world is uncertain. * **`applies_to`** names capabilities, or targets every write (`{effect: writes}`). * **`otherwise`** says what happens at the gate: `refuse`, `must_say`, `offer`, `ask`. * **`intent`** is the reason — it appears in the trace as the *because* of every enforcement. Because a gate is a predicate over established facts, it cannot be talked around: gating on what a caller merely *claims* (a `conversation` fact) is itself a build error — ownership and identity gate on verified facts. ## Obligations: the reply-level guardrails Alongside gates, the policies files carry **obligations** — constraints on the reply itself: things the agent must `never` say or must always include (`must`, `must_say`), optionally scoped by `when` or by standing state. This is where the old notion of "guardrails" lives — as checked rules, not advisory prose. ## Four surfaces, one question each The engine separates refusal into four surfaces — most authoring confusion is putting a rule on the wrong one: | Surface | Owns the question | Fires | | --------------------------- | ---------------------------------- | ------------------------------------ | | **Intake** (the door) | Is this conversation mine at all? | Before any capability runs | | **Policies** (the gates) | May this *action* proceed? | When a capability's work is proposed | | **Obligations** (the reply) | May this *reply* ship? | After the reply is composed | | **Decline** (the words) | How do I say "not something I do"? | When routing finds nothing | Together with [confirmation](/overview/agent-program) (explicit consent before an irreversible write, with an expiry — a question left hanging is not consent), these give every rule one home and every enforcement a readable trace. # Derivations Source: https://docs.aui.io/overview/derivations Facts worked out from other facts — where your business arithmetic, thresholds, and rules-of-thumb live, deterministically. Some facts nobody supplies — they're **worked out**: an order's total, a claim's size band, whether a customer qualifies. `derivations.yaml` is where those live. Each derivation names one fact, the facts it's computed from, and exactly one way to compute it — and the runtime recomputes it **every turn from live inputs**, so a derived value can never go stale mid-conversation. ```yaml theme={"dark"} derivations: - id: order.total means: What the order comes to. given: [order.subtotal, order.tax] value: order.subtotal + order.tax ``` * **`id`** — the fact this row produces (declared in the [vocabulary](/overview/vocabulary) with `known_from: derived`). * **`means`** — what the value is, in words a caller could hear. * **`given`** — the input facts; until they're established, the derived fact is simply unknown. * **One body** — how it's computed. There are four kinds: ## The four kinds **A formula (`value:`)** — arithmetic and logic over other facts. Computed exactly (decimal, never float): ```yaml theme={"dark"} - id: order.total given: [order.subtotal, order.tax] value: order.subtotal + order.tax ``` **A ladder (`decide:`)** — ordered conditions where the *first* match wins, so the order is the meaning. `otherwise:` is mandatory — every ladder says what happens when nothing matches: ```yaml theme={"dark"} - id: claim.band means: How large the claim is for routing purposes. given: [claim.amount] decide: - when: claim.amount >= 10000 then: large - when: claim.amount >= 1000 then: medium otherwise: small ``` **A table (`table:`)** — a lookup over one or two enum axes. Because the axes are enums, a missing cell is a build error, not a runtime surprise: ```yaml theme={"dark"} - id: rate.pct means: The commission rate for this rep. table: rows: rep.region cols: rep.segment values: emea: { enterprise: 12, smb: 7 } amer: { enterprise: 11, smb: 6 } ``` **A model instruction (`model:`)** — for the rare value no formula can produce, like a search query built from a caller's request. This is the one kind computed once and kept, rather than recomputed every turn. ## Why derivations matter Derivations are where policy text becomes computation. A threshold from your methodology, a fee schedule, an eligibility rule — each becomes a row that always computes the same answer from the same inputs, shows its work in the trace, and can name the exact provision it implements with `encodes:`: ```yaml theme={"dark"} - id: refund.fee_waived encodes: "Returns policy §4.2" means: Whether the return fee is waived. given: [customer.tier, order.total] value: customer.tier == "premium" or order.total > 200 ``` [Capabilities](/overview/capabilities-and-policies) list derived facts in their `needs`, gates test them in `requires`, and replies read them back — so the arithmetic your business runs on is written once, in one file, and enforced everywhere. # Vocabulary: Facts & Records Source: https://docs.aui.io/overview/vocabulary The nouns of your agent's world — every value it can know and every thing it can hold. `vocabulary.yaml` declares the agent's typed vocabulary: **facts** (values the agent can know) and **records** (things it can hold instances of). Everything the agent decides is downstream of these — capabilities name the facts they need, policies gate on them, derivations compute them, and replies read them back. ## Facts A fact is one typed value with a meaning: ```yaml theme={"dark"} facts: - id: order.id type: text known_from: conversation ask_as: "Which order is this about — for example W-1042?" means: The order the caller wants help with. - id: order.total type: money known_from: source means: What the caller paid, including shipping. ``` | Key | Purpose | | ------------ | ---------------------------------------------------------------------------- | | `id` | The fact's name — `order.total` scopes it to the `order` record | | `type` | `text`, `enum`, `money`, `number`, `date`, `datetime`, `duration`, `boolean` | | `means` | What the value *is*, in words the caller could hear read back | | `known_from` | Where the value comes from — the most important line in the file | | `ask_as` | The exact question to ask when the fact must come from the caller | **`known_from` is the seam between language and state.** It declares whether a value arrives from the host at session open (`given`), is said by the caller (`conversation`), is fetched from a system (`source`), or is computed from other facts (`derived`). Once a value is established, everything downstream of it — gates, arithmetic, branching — is deterministic. Facts can also carry a shape check (`looks_like`), sensitivity flags (`sensitive`, `public: false`), and asking behavior (`never_ask`, `once`). ## Records A record is a thing the agent holds instances of — an order, a claim, a booking — with a key, fields, and optionally a lifecycle: ```yaml theme={"dark"} records: - id: order means: A purchase the caller made. key: order.id known_as: "the order, my purchase" states: [placed, shipped, delivered, returned] transitions: - { do: return-order, from: delivered, to: returned } ``` The record's fields are simply the facts scoped to it (`order.id`, `order.total`, …). The state machine — `states` and `transitions` — makes lifecycle rules enforceable: a transition that isn't declared can't happen. A record holding several rows at once (search results, a cart) is a **collection**. ## How the vocabulary is used * **Capabilities** list the facts they `need` — the runtime establishes each one from its `known_from`, asking the caller only with that fact's `ask_as`. * **Policies** gate on facts and record state: `requires: order.status is "delivered"`. * **[Derivations](/overview/derivations)** compute new facts from existing ones — thresholds, eligibility, totals. * **Replies** read facts back to the caller using their `means`, so the agent describes values the way the business does. Declare a noun once, and every capability, gate, and reply speaks the same language. # Quickstart Source: https://docs.aui.io/quickstart Understand AUI's products and start building in minutes. ## AUI Products AUI provides a family of products for building and integrating AI agents: Integrate AI agents into your product and manage them programmatically. Send messages via REST or stream replies token-by-token over server-sent events, receive structured responses, and operate projects, agents, and versions — ready for web, mobile, or backend use. The official TypeScript/JavaScript client (`@aui.io/aui-client`) — type-safe methods, automatic retries, and built-in streaming. Develop agents locally with the `apollo` CLI. Edit typed YAML programs with schema support, validate instantly, test against the live runtime, and push versions to the cloud. Programmatically create and manage agents. Define knowledge, tools, behavior, and guardrails through a simple API — fully customizable and scalable. | | API | SDK | CLI | Agent Builder API | | ------------- | ---------------------------- | ------------------------- | ----------------------- | ------------------------- | | **Purpose** | Message & manage agents | Type-safe client for apps | Develop & deploy agents | Create & configure agents | | **Interface** | REST + SSE | TypeScript / JavaScript | Terminal | REST | | **Status** | Stable | Stable | Stable | Available | | **Best for** | App integration & operations | Web & Node apps | Day-to-day development | Automation & CI/CD | *** ## Start from the Playground The fastest way to experience AUI is through the [Playground](https://apollo.aui.io) — no code required. Go to [apollo.aui.io](https://apollo.aui.io) and log in with your AUI account. Choose an existing agent or create a new one to start a conversation. Send messages and see the agent respond in real time. Test how it handles different intents, activates tools, and follows rules. When you're ready to integrate, exchange your publishable key for an access token and send the same messages programmatically: ```bash cURL theme={"dark"} # 1. Exchange your publishable key for an access token curl -X POST "https://api-v3.aui.io/apollo-api/management/v1/auth/token" \ -H "Content-Type: application/json" \ -d '{ "grant_type": "publishable_key", "publishable_key": "pk_..." }' # 2. Send a message — a conversation thread is created automatically curl -X POST "https://api-v3.aui.io/apollo-api/messaging/v1/messages" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{ "text": "I need help finding a product", "user_id": "my-user-123" }' ``` See the [API overview](/api/overview) for the full tour. *** ## Next Steps Start integrating agents into your app. Install the CLI and start building agents locally. # Best Practices Source: https://docs.aui.io/sdk/best-practices Error handling, timeouts, key hygiene, and patterns that hold up in production. ## Pick the right client for where your code runs Publishable key. Safe in browsers and mobile webviews — that's what makes the key "publishable". Use it for anything an end user drives. Organization API key. **Server-side only** — backend services, CI, cron. Never ship this key in client code. ```ts theme={"dark"} // Backend: both clients are fine const management = new ApolloManagementClient({ organizationApiKey: process.env.AUI_ORG_API_KEY! }); // Browser: messaging only const messaging = new ApolloMessagingClient({ publishableKey: 'pk_network_...' }); ``` If your publishable key is used from a browser, restrict it to your web origins and IP ranges in the [Playground](https://apollo.aui.io) so it can't be replayed elsewhere. ## Reuse one client instance The messaging client caches its access token and refreshes it before expiry. Constructing a new client per request throws that away and forces a token exchange every time: ```ts theme={"dark"} // Good — module-level singleton export const apollo = new ApolloMessagingClient({ publishableKey: PK }); // Bad — new client (and token exchange) per call async function send(text: string) { const client = new ApolloMessagingClient({ publishableKey: PK }); return client.messaging.sendMessage({ user_id, text }); } ``` ## Error handling All failures throw typed errors. `ApolloError` and `ApolloTimeoutError` are exported at the top level; per-status classes live under the `Apollo` namespace (`BadRequestError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`, `ConflictError`, `UnprocessableEntityError`, `InternalServerError`, `BadGatewayError`). ```ts theme={"dark"} import { Apollo, ApolloError, ApolloTimeoutError } from '@aui.io/aui-client'; try { await client.agents.getAgent(agentId); } catch (error) { if (error instanceof Apollo.NotFoundError) { // handle the specific case } else if (error instanceof ApolloTimeoutError) { // the request timed out — consider a longer timeoutInSeconds } else if (error instanceof ApolloError) { // any other API error: statusCode + the error envelope in body const body = error.body as { error?: { code?: string; request_id?: string } }; console.error(error.statusCode, body?.error?.code, body?.error?.request_id); } else { throw error; // not an API error — don't swallow it } } ``` `error.body` carries the API's [error envelope](/api/errors) — branch on the stable `error.code`, and log `error.request_id` for support. ## Timeouts and retries There is no client-wide timeout — set one per call where it matters, and tune retries (default: 2) for the operation's cost: ```ts theme={"dark"} // Long-running list over a large window await client.threads.listThreads({ project_id }, { timeoutInSeconds: 120 }); // Don't retry non-idempotent sends more than you must await client.messaging.sendMessage(request, { maxRetries: 0 }); // Cancelable from the UI const controller = new AbortController(); await client.messaging.sendMessage(request, { abortSignal: controller.signal }); ``` `sendMessage` triggers a billable agent turn. Automatic retries re-send the request — for user-driven sends prefer `maxRetries: 0` and surface the error, rather than risking duplicate turns. ## Filter your thread lists An unfiltered list sorts every thread in the organization and can be slow. Scope by `project_id`, `agent_id`, or a `created` range — filter fields go directly on the request — and paginate with the returned cursors: ```ts theme={"dark"} let page = await client.threads.listThreads({ project_id, created: ['2026-07-01T00:00:00Z'], 'page[size]': 50, }); while (page.meta?.has_more) { page = await client.threads.listThreads({ project_id, 'page[after]': page.meta.after_cursor!, }); } ``` Upgrading from 3.3.4 or earlier? List methods used to take a `filters: { … }` wrapper — drop it and pass the fields directly (the compiler points at every call site). Older versions also didn't apply list filters on the wire; from 3.3.5 they filter reliably. ## Streaming resilience * **Track `seq` and resume** — every `event` and `message` frame carries a monotonic `seq`; keep the highest one you've processed and, if the connection drops, call `streamMessage` again with `'Last-Event-ID': String(lastSeq)` instead of re-submitting the turn — the missed frames replay without a second billable turn (see [Stream a message](/sdk/messaging#stream-a-message)). * **Branch on `error`** — a turn that fails mid-stream arrives as an `error` event; handle it in the same loop as the other frames. * **Persist `thread_id`** — keep it across reconnects (and page reloads, if the conversation should survive them) to stay in the same conversation. * **Prefer REST for one-shot turns** — if you don't need live tokens, `sendMessage` is simpler to operate. * **Runtime v1 WebSocket sessions** — for [runtime v1 agents](/sdk/runtime-v1-agents), the same rules apply to the socket: track `seq`, send `{ type: 'resume', resume_after: lastSeq }` on reconnect, and register one `on()` handler per event (a second call replaces the first). See [WebSocket](/sdk/websocket). ## Troubleshooting The credential doesn't match the client: messaging needs a publishable key (`pk_network_...`), management needs the organization API key. If the key has origin/IP restrictions, confirm the calling origin is allowlisted. `1008` is an authentication failure on the upgrade — the key exchange failed or the token was rejected. Verify the publishable key; `1011` signals a server-side error instead. If the socket opens but answers with an `error` envelope saying the session is SSE-only, the agent is on runtime v2 — use `streamMessage` (see [Runtime v1 agents](/sdk/runtime-v1-agents)). Unlike `sendMessage`, the stream request wraps the payload: `streamMessage({ body: { user_id, text } })` — the top level also accepts the `Last-Event-ID` resume header. Add filters (`project_id`, `created` range) and raise the per-call timeout: `listThreads({ project_id }, { timeoutInSeconds: 120 })`. The read went to the wrong runtime. Thread reads take the `runtime_version` selector — the runtime version your agent runs on, e.g. `listMessages(threadId, { runtime_version: '0.8.0' })` (same for `threads.getThread` / `getThreadMessages` / `updateThread`); omitting it asks runtime v1. UUID thread ids are the tell. See [Threads and runtime versions](/sdk/overview#threads-and-runtime-versions). From 3.3.5, list methods take filter fields directly on the request — replace `listThreads({ filters: { project_id } })` with `listThreads({ project_id })`. The old wrapper was also silently not applied on the wire, so this upgrade is when your filters start working. ## Runtime v1 agents Agents created before August 1 2026 run on runtime v1: no `runtime_version` on thread reads, WebSocket sessions and traces available, 24-character hex thread ids. Everything that differs — and the checklist for moving an integration to runtime v2 — lives on one page: Tell the runtimes apart, read runtime v1 threads, and swap a WebSocket session for `streamMessage`. # Management Client Source: https://docs.aui.io/sdk/management Operate projects, agents, versions, threads, and usage — server-side, with your organization API key. `ApolloManagementClient` is the server-side client for operating your workspace. It authenticates with your organization API key, sent as the `x-organization-api-key` header on every request — never expose the key in the browser. ```ts theme={"dark"} import { ApolloManagementClient } from '@aui.io/aui-client'; const client = new ApolloManagementClient({ organizationApiKey: process.env.AUI_ORG_API_KEY!, }); ``` All methods are `async`, fully typed, and throw [typed errors](/sdk/best-practices#error-handling) on failure. Request and response models are exported under the `Apollo` namespace. ## Projects | Method | Description | | ----------------------------------------------- | --------------------------------------------- | | `projects.listProjects(request?)` | List the organization's projects (paginated). | | `projects.createProject(request)` | Create a project. | | `projects.getProject(projectId)` | Fetch one project. | | `projects.deleteProject(projectId)` | Delete a project. | | `projects.getProjectUsage(projectId, request?)` | Usage aggregated across the project. | ```ts theme={"dark"} const project = await client.projects.createProject({ name: 'Support agents' }); const usage = await client.projects.getProjectUsage(project.id, { created_from: '2026-07-01T00:00:00Z', }); ``` ## Agents | Method | Description | | ----------------------------------------- | ---------------------------------------------------------------------- | | `agents.listAgents(projectId, request?)` | List a project's agents (paginated; optional `name` substring filter). | | `agents.createAgent(projectId, request)` | Create an agent (optional `from_template`). | | `agents.getAgent(agentId)` | Fetch one agent (includes `live_version_id`). | | `agents.updateAgent(agentId, request)` | Rename an agent. | | `agents.deleteAgent(agentId)` | Delete an agent **and all its versions**. | | `agents.getAgentUsage(agentId, request?)` | Usage metrics for one agent. | ```ts theme={"dark"} const agent = await client.agents.createAgent(project.id, { name: 'Order support' }); await client.agents.updateAgent(agent.id, { name: 'Order & shipping support' }); ``` ## Agent versions | Method | Description | | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `agentVersions.listVersions(agentId, request?)` | List versions (paginated, filterable by `status`/`tag`/`label`/`version_number`; `exclude_revisions` collapses revision history). | | `agentVersions.createVersion(agentId, request)` | Create a draft (`source`: `agent-scope`, `template`, `version`, or `empty`). | | `agentVersions.updateVersion(agentId, versionId, request)` | Update metadata (label, tags, notes). | | `agentVersions.pushVersion(agentId, versionId, request)` | Push a configuration bundle (optional `commit_message`; `caller`: `agent_builder`, `ui`, or `cli`). | | `agentVersions.pullVersion(agentId, versionId, request?)` | Download a configuration bundle (optional `version_tag`). | | `agentVersions.publishVersion(agentId, versionId)` | Make the version live (ship or roll back). | | `agentVersions.archiveVersion(agentId, versionId)` | Retire a version. | ```ts theme={"dark"} const draft = await client.agentVersions.createVersion(agent.id, { source: 'version', from_version: liveId }); await client.agentVersions.pushVersion(agent.id, draft.id, { caller: 'cli', bundle }); await client.agentVersions.publishVersion(agent.id, draft.id); ``` ## Threads | Method | Description | | ----------------------------------------------- | ------------------------------------------------------------- | | `threads.listThreads(request?)` | List the organization's threads, newest first (paginated). | | `threads.getThread(threadId, request?)` | Fetch one thread. | | `threads.updateThread(threadId, request)` | Update a thread (currently `title`). | | `threads.getThreadMessages(threadId, request?)` | The thread's transcript. | | `threads.getThreadTrace(threadId)` | Every interaction's reasoning trace (runtime v1 agents only). | | `threads.getInteractionTrace(interactionId)` | One interaction's reasoning trace (runtime v1 agents only). | Filter fields go directly on the request: `project_id`, `agent_id`, `user_id`, `external_id`, `created` (one value, or two for a range), `tool`, `rule`, and `param`. List endpoints also take `sort_by` / `sort_order` and `page[size]` / `page[after]` / `page[before]` for keyset pagination in either direction. ```ts theme={"dark"} const page = await client.threads.listThreads({ project_id: project.id, created: ['2026-07-01T00:00:00Z'], 'page[size]': 20, }); await client.threads.updateThread(threadId, { title: 'Renamed conversation' }); ``` Prefer a filter such as `project_id` over an empty request — the unfiltered list sorts every thread in the organization and can be slow. For large windows, raise the per-call timeout: `client.threads.listThreads({ project_id }, { timeoutInSeconds: 120 })`. ### Threads and runtime versions A thread lives on the runtime that created it — reads and renames name that runtime with the `runtime_version` selector (e.g. `'0.8.0'`, the runtime version the agent runs on; any value outside the `1.x` family selects runtime v2, and it doesn't have to match the thread's exact build): ```ts theme={"dark"} const thread = await client.threads.getThread(threadId, { runtime_version: '0.8.0' }); const transcript = await client.threads.getThreadMessages(threadId, { runtime_version: '0.8.0' }); await client.threads.updateThread(threadId, { runtime_version: '0.8.0', title: 'Renamed' }); ``` Listing runtime v2 threads is always scoped to an agent or an end user, and only `agent_id`, `user_id`, and `created` (with `agent_id`) apply — the runtime v1–only filters return a clear 400 on the runtime v2 path: ```ts theme={"dark"} const page = await client.threads.listThreads({ runtime_version: '0.8.0', agent_id: [agentId], }); ``` Threads of [runtime v1 agents](/sdk/runtime-v1-agents) (24-character hex ids) live on runtime v1, which is the default — the calls earlier on this page, without `runtime_version`, read those. ## Pagination List methods return a page: `results` plus `meta` cursors. ```ts theme={"dark"} let page = await client.threads.listThreads({ project_id }); while (true) { for (const thread of page.results) console.log(thread.id, thread.title, thread.version_tag); // version_tag: the agent version the thread ran on if (!page.meta?.has_more) break; page = await client.threads.listThreads({ project_id, 'page[after]': page.meta.after_cursor!, }); } ``` Every method also accepts a final `requestOptions` argument (`timeoutInSeconds`, `maxRetries`, `abortSignal`, extra `headers`, extra `queryParams`, and a per-call `organizationApiKey` override) — see [Best practices](/sdk/best-practices#timeouts-and-retries). # Messaging Client Source: https://docs.aui.io/sdk/messaging Send messages, stream replies, read conversations, and reach users on channels — with a publishable key. `ApolloMessagingClient` is the browser-safe client for end-user messaging. It authenticates with your publishable key, exchanging it for a short-lived access token internally — the agent comes from the key, never from request bodies. ```ts theme={"dark"} import { ApolloMessagingClient } from '@aui.io/aui-client'; const client = new ApolloMessagingClient({ publishableKey: 'pk_network_...' }); ``` All methods are `async`, fully typed, and throw [typed errors](/sdk/best-practices#error-handling) on failure. Request and response models are exported under the `Apollo` namespace. ## Send a message Omit `thread_id` to start a new conversation, or pass it to continue one. ```ts theme={"dark"} const response = await client.messaging.sendMessage({ user_id: 'end-user-123', text: 'I want to dispute a charge', // thread_id: existingThreadId, }); console.log(response.thread_id); // the (possibly new) thread console.log(response.message.text); // the agent's reply console.log(response.message.id); // interaction id — for reruns and traces ``` When the agent surfaces entities — products, bookings, search results — the reply carries them as `cards`. Each card holds the same entity in two self-contained representations, so you pick what fits your stack: a ready-to-render JSX string for React, or structured JSON for any other framework — see [Cards](/api/messaging/send-messages#cards) for the full shape. ```ts theme={"dark"} for (const card of response.message.cards ?? []) { card.rendered_jsx; // ready-to-render JSX (React) card.json_data?.entity; // flat key–value fields (any framework) card.json_data?.sub_entities; // nested groups, e.g. product variants card.is_recommended; // the agent's top pick card.title; // the card's authored title card.capability; // which agent capability produced it — pick a widget template by it card.instance; // the specific record the card is about, if any } ``` `title`, `capability`, and `instance` are optional and may be `null` — read them defensively. Pass per-message values for the agent's configured context variables with `agent_variables`, and attach an image for the agent to reason over with `image_url`: ```ts theme={"dark"} await client.messaging.sendMessage({ user_id: 'end-user-123', text: 'Where is my order?', agent_variables: { static: { customer_name: 'Ada' }, dynamic: { order_id: 'ORD-1042' }, }, // image_url: 'https://…/receipt.jpg', // vision input, optional }); ``` Send requests also accept an optional `runtime_version` — an advanced build pin for the turn; normally omit it. ## Stream a message `streamMessage` is the streaming transport: one HTTP call per turn that returns the reply as an async-iterable stream of Server-Sent Events — token deltas while the agent works, then the complete message, then ready-made follow-up suggestions. It takes the same request body and auth as `sendMessage`, leaves you no connection to keep alive, and resumes after a drop without re-running the turn: ```ts theme={"dark"} const stream = await client.messaging.streamMessage({ body: { user_id: 'end-user-123', text: 'Tell me about my account' }, }); for await (const event of stream) { switch (event.type) { case 'thread': // first event: the resolved thread id console.log(event.data.thread_id); break; case 'event': // token deltas carry data.text // data is typed Record — cast before use process.stdout.write(String(event.data.text ?? '')); break; case 'message': // terminal event: the full reply console.log('\n', event.data.text); break; case 'suggestions': // optional final event: follow-up prompts console.log(event.data.followup_suggestions); break; case 'error': // the turn failed mid-stream console.error(event.data); break; } } ``` Ready-made follow-up prompts arrive automatically as the `suggestions` frame after the terminal `message` — render them as tappable chips, no extra call needed. Its data carries `followup_suggestions` alongside the `thread_id` and `interaction_id` it belongs to. ([Runtime v1 agents](/sdk/runtime-v1-agents) don't emit this frame; their `followup_suggestions` ride on the message itself.) To resume a dropped stream without re-running the turn, reconnect with the last `seq` you saw: ```ts theme={"dark"} await client.messaging.streamMessage({ 'Last-Event-ID': String(lastSeq), body: { user_id: 'end-user-123', text: '...', thread_id }, }); ``` ## Rerun an interaction Regenerate a previous interaction — with the original or edited text — on a new thread branched from the original. Both `interaction_id` and `text` are required: ```ts theme={"dark"} const rerun = await client.messaging.rerun(threadId, { interaction_id: message.id, text: 'Actually, the charge was from June 1st', // version_id / version_tag: pin to a specific agent version // user_id, image_url: optional, as on sendMessage }); console.log(rerun.thread_id); // the NEW thread the rerun created ``` ## Read a conversation A thread lives on the runtime that created it, so the transcript read names that runtime — pass the runtime version your agent runs on as `runtime_version` (sending resolves it automatically; see [Threads and runtime versions](/sdk/overview#threads-and-runtime-versions)): ```ts theme={"dark"} const messages = await client.messaging.listMessages(threadId, { runtime_version: '0.8.0', }); ``` Any value outside the `1.x` family selects runtime v2 — it doesn't have to match the thread's exact build. Threads of [runtime v1 agents](/sdk/runtime-v1-agents) take no selector: `listMessages(threadId)` reads them as is. ### Traces (runtime v1 agents) Runtime v1 agents expose the reasoning trace behind each interaction; trace support for runtime v2 is coming. ```ts theme={"dark"} const traces = await client.messaging.threadTrace(threadId); // one trace per interaction const trace = await client.messaging.interactionTrace(interactionId); ``` `threadTrace` is paginated — pass `page[size]`, `page[after]` / `page[before]`, and `sort_by` / `sort_order` in an optional second argument. ## Welcome message and follow-up suggestions ```ts theme={"dark"} const { welcome_message } = await client.messaging.getWelcomeMessage(); // Agents with a dynamic welcome message accept placeholder values: await client.messaging.getWelcomeMessage({ placeholders: { customer_name: 'Ada' }, }); const { suggestions } = await client.messaging.generateFollowupSuggestions({ context: { topic: 'order tracking' }, }); ``` Called without a `context`, `generateFollowupSuggestions` returns the agent's configured static suggestions. ## Client context After the first exchange (or an explicit `getContext()` call), the client knows which agent and organization its key resolves to: ```ts theme={"dark"} const { agentId, organizationId, keyType } = await client.getContext(); // Also available as getters once resolved: client.keyType; // 'agent' | 'unknown' client.agentId; client.organizationId; ``` ## Channels (WhatsApp and SMS) Start an outbound conversation on a channel with `channels.initiateThread`. Pass `'sms'` or `'whatsapp'` as the channel. `phone_number` is the **recipient** (who receives the opener). `sender_id` is **not** that phone number. It is the MongoDB ObjectId of a sender record you already connected in the [Playground](https://apollo.aui.io). If you connected a specific phone number through the Playground (SMS or WhatsApp), copy that sender's ObjectId and pass it as `sender_id`. The SDK then starts the conversation **from that connected number**. Without `sender_id`, the platform default sender is used instead. The response includes `from` when a specific sender was used, so you can confirm which connected number the opener was sent from. ```ts theme={"dark"} const thread = await client.channels.initiateThread('sms', { phone_number: '+14155551234', // recipient user_id: 'end-user-123', text: 'Hi! Your order has shipped.', // thread_id: existingThreadId, // MongoDB ObjectId of the specific number you connected in the Playground. // Pass it here to start the conversation from that number. sender_id: '67c1a2b3d4e5f67890123456', }); console.log(thread.thread_id, thread.from); ``` WhatsApp is the same call. Template fields apply to WhatsApp only; `text` is used for SMS and ignored by WhatsApp. ```ts theme={"dark"} const thread = await client.channels.initiateThread('whatsapp', { phone_number: '+14155551234', // recipient user_id: 'end-user-123', // Same Playground-connected sender ObjectId — starts WhatsApp from that number sender_id: '67c1a2b3d4e5f67890123456', agent_display_name: 'Support', // bound to template variable {{1}} }); ``` ## Method reference | Method | Description | | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | `messaging.sendMessage(request)` | Send a message and return the reply. | | `messaging.streamMessage(request)` | Send a message and stream the reply (SSE). | | `messaging.rerun(threadId, request)` | Regenerate an interaction on a new thread. | | `messaging.listMessages(threadId, request?)` | The thread's full transcript — pass `runtime_version` (runtime v1 threads: none). | | `messaging.threadTrace(threadId, request?)` | Every interaction's reasoning trace (paginated; runtime v1 agents only). | | `messaging.interactionTrace(interactionId)` | One interaction's reasoning trace (runtime v1 agents only). | | `messaging.getWelcomeMessage(request?)` | The agent's configured greeting; accepts `placeholders`. | | `messaging.generateFollowupSuggestions(request)` | Suggested next prompts from a context (static suggestions without one). | | `channels.initiateThread(channel, request)` | Start a WhatsApp/SMS conversation. Pass `sender_id` to send from a Playground-connected number. | | `connect(args?)` | Open a [WebSocket session](/sdk/websocket) (runtime v1 agents only). | | `getContext()` | Resolve the key's agent and organization (also on the `keyType`, `agentId`, `organizationId` getters). | Every method also accepts a final `requestOptions` argument (`timeoutInSeconds`, `maxRetries`, `abortSignal`, extra `headers`) — see [Best practices](/sdk/best-practices#timeouts-and-retries). # SDK Source: https://docs.aui.io/sdk/overview The official TypeScript/JavaScript SDK for the Apollo API — two type-safe clients, one per credential.
TypeScript @aui.io/aui-client v3
The official TypeScript/JavaScript SDK wraps the [Apollo API](/api/overview) with full type definitions, automatic authentication, retries, and built-in streaming. It exposes **two clients, one per credential** — import the one that matches where your code runs: | Client | Credential | Browser-safe | Surface | | ------------------------ | ---------------------------------- | -------------------- | ------------------------------------------ | | `ApolloMessagingClient` | Publishable key (`pk_network_...`) | Yes | End-user messaging, streaming, channels | | `ApolloManagementClient` | Organization API key | **No — server only** | Projects, agents, versions, threads, usage | ## Installation ```bash npm theme={"dark"} npm install @aui.io/aui-client ``` ```bash yarn theme={"dark"} yarn add @aui.io/aui-client ``` ```bash pnpm theme={"dark"} pnpm add @aui.io/aui-client ``` Requires Node.js ≥ 18 (or any modern browser for the messaging client). Type definitions ship with the package. ## Quick start — messaging The messaging client authenticates with your publishable key. It exchanges the key for a short-lived access token and refreshes it as needed — you never handle tokens directly, and the agent is derived from the key rather than passed in request bodies. ```ts theme={"dark"} import { ApolloMessagingClient } from '@aui.io/aui-client'; const client = new ApolloMessagingClient({ publishableKey: 'pk_network_xxxxxxxxxxxxxxxxxxxxxxxx', }); // Send a message — a conversation thread is created automatically const response = await client.messaging.sendMessage({ user_id: 'end-user-123', text: 'What can you help me with?', }); console.log(response.thread_id); // pass this back to continue the thread console.log(response.message.text); ``` ## Quick start — management The management client authenticates with your organization API key, sent as the `x-organization-api-key` header on every request. It's meant for backend services and CI — never expose the key in the browser. ```ts theme={"dark"} import { ApolloManagementClient } from '@aui.io/aui-client'; const client = new ApolloManagementClient({ organizationApiKey: process.env.AUI_ORG_API_KEY!, }); const projects = await client.projects.listProjects(); const agents = await client.agents.listAgents(projects.results[0].id, {}); ``` ## Threads and runtime versions **A thread lives on the runtime that created it**, and thread reads name that runtime. Sending never does — `sendMessage`, `streamMessage`, `rerun`, and the channel openers resolve the runtime from the agent behind your key. When you read a thread back (`listMessages`, and the management `getThread`, `getThreadMessages`, `updateThread`, `listThreads`), pass the runtime version your agent runs on as `runtime_version`: ```ts theme={"dark"} const messages = await client.messaging.listMessages(threadId, { runtime_version: '0.8.0', }); ``` The rule is on the major version: any value outside the `1.x` family selects runtime v2, so it doesn't have to match the thread's exact build. Omitting it asks runtime v1 instead — a runtime v2 thread read that way returns 404. **Runtime v1 agents** — as a rule of thumb, agents created before August 1 2026 — take no `runtime_version` on reads, keep [WebSocket sessions](/sdk/websocket) and traces, and produce 24-character hex thread ids (runtime v2 ids are UUIDs). Everything runtime-v1-specific is collected in [Runtime v1 agents](/sdk/runtime-v1-agents). ## Why use the SDK? Token exchange, refresh, and header injection are automatic — construct the client with a key and start calling. Full TypeScript definitions with IntelliSense across every method and payload, exported under the `Apollo` namespace. `streamMessage` returns the reply as an async-iterable SSE stream — token deltas, the final message, follow-up suggestions — resumable with `Last-Event-ID`. `ApolloError`, `ApolloTimeoutError`, and per-status classes like `Apollo.NotFoundError` for clean handling. Transient failures retry automatically (2 attempts by default, tunable per call). Generated from the live API specification, so the client tracks the API as it evolves. ## Environment Both clients default to production — no configuration needed: ``` https://api-v3.aui.io/apollo-api ``` To point at a different host (e.g. a regional deployment), pass `baseUrl`: ```ts theme={"dark"} const client = new ApolloMessagingClient({ publishableKey: 'pk_network_...', baseUrl: 'https://api-eu-v3.aui.io/apollo-api', }); ``` The known base URLs are exported as `ApolloEnvironment`, and both clients accept a `logging` option (the `logging` namespace is exported too) for request-level logs. ## Resolved key context After the first request (or an explicit `getContext()` call), the messaging client exposes the scope resolved from your publishable key: ```ts theme={"dark"} const context = await client.getContext(); context.agentId; // the agent your calls run against context.organizationId; // the organization the key belongs to context.keyType; // 'agent' | 'unknown' ``` ## Explore the SDK Send and stream messages, read conversations, and reach users on WhatsApp/SMS. Operate projects, agents, versions, threads, and usage from your backend. Error handling, timeouts, retries, streaming resilience, and key hygiene. On an agent created before August 1 2026? What differs, and the WebSocket session those agents still support. The underlying REST + SSE API the SDK wraps. # Runtime v1 agents Source: https://docs.aui.io/sdk/runtime-v1-agents How agents on runtime v1 differ from runtime v2, how to tell which one you have, and what to change when you move. Apollo agents run on one of two runtime generations. **Runtime v2** is the current one — every other page in these docs describes it. Agents created before the runtime v2 rollout run on **runtime v1**, and a handful of things work differently for them. This page collects all of those differences so the main pages don't have to. ## Which runtime is my agent on? As a rule of thumb, agents created before **August 1 2026** run on runtime v1, and agents created on or after that date run on runtime v2. When you'd rather not rely on a date, two things tell you for sure: | Tell | Runtime v1 | Runtime v2 | | ------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------- | | Thread ids the agent produces | 24-character hex — `68e78d0dc5a4b19a030d03d6` | UUID — `3f9c2a1e-7b0d-4c52-9e1a-6d2f8b4c0a17` | | `runtime_version` on the agent's live [version](/api/management/versions) | A `1.x` value, or none | A runtime build such as `0.8.0` | The thread id is the quickest check — send one message and look at the `thread_id` that comes back. ## What's different Sending is identical on both runtimes: `sendMessage`, `streamMessage`, `rerun`, and the channel openers route automatically based on the agent behind your key, and you never pick a runtime when sending. The differences surface in a few places only: | Capability | Runtime v1 | Runtime v2 | | ------------------------------------------ | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Send / stream / rerun / channels | ✅ Same calls | ✅ Same calls | | Reading threads back | No selector needed | Pass `runtime_version` (e.g. `'0.8.0'`) — see [below](#reading-runtime-v1-threads) | | Live streaming | [`streamMessage`](/sdk/messaging#stream-a-message) (SSE) or a [WebSocket session](/sdk/websocket) | `streamMessage` (SSE) | | `suggestions` stream frame | Not emitted — `followup_suggestions` ride on the message | Emitted after the terminal `message` | | Traces (`threadTrace`, `interactionTrace`) | ✅ | Not yet available | | `runtime_version` build pin on sends | Ignored | Honored (advanced; normally omit) | | Thread id format | 24-character hex | UUID | ## Reading runtime v1 threads A thread lives on the runtime that created it, and thread reads name that runtime with `runtime_version`. Runtime v1 is the **default** — so reads of runtime v1 threads take no selector at all, while reads of runtime v2 threads must carry one: ```ts theme={"dark"} // Runtime v1 thread — the default target, nothing to pass const v1Messages = await client.messaging.listMessages(v1ThreadId); // Runtime v2 thread — name the runtime the agent runs on const v2Messages = await client.messaging.listMessages(threadId, { runtime_version: '0.8.0', }); ``` The rule is on the major version: a `1.x` value (or none) selects runtime v1; **any value outside the `1.x` family selects runtime v2**, and it doesn't have to match the thread's exact build. Asking the wrong runtime returns 404 — the most common symptom is a UUID thread that "disappears" when read without `runtime_version`. The same applies on the management client and the REST API: | Read | Runtime v1 thread | Runtime v2 thread | | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `messaging.listMessages(threadId)` · `GET /messaging/v1/threads/{threadId}/messages` | As is | `runtime_version` | | `threads.getThread` · `getThreadMessages` · `updateThread` · `GET`/`PATCH /management/v1/threads/{threadId}` | As is | `runtime_version` | | `threads.listThreads` · `GET /management/v1/threads` | All filters (`project_id`, `tool`, `rule`, `param`, …) | `runtime_version` plus `agent_id` or `user_id` — only `agent_id`, `user_id`, and `created` (with `agent_id`) apply; runtime v1–only filters return a clear 400 | ## Traces Runtime v1 agents expose the reasoning [trace](/api/messaging/traces) behind every interaction — what the agent understood, which rules fired, what it decided: ```ts theme={"dark"} // Messaging client (publishable key) const traces = await client.messaging.threadTrace(threadId); // one per interaction, paginated const trace = await client.messaging.interactionTrace(interactionId); // Management client (organization API key) await management.threads.getThreadTrace(threadId); await management.threads.getInteractionTrace(interactionId); ``` `threadTrace` is paginated — pass `page[size]`, `page[after]` / `page[before]`, and `sort_by` / `sort_order` in an optional second argument. On a runtime v1 [WebSocket session](/sdk/websocket) opened with `include_trace=true`, the final `message` envelope also embeds the trace as `data.trace_info`. Trace support for runtime v2 is coming; until then these calls apply to runtime v1 agents only. ## WebSocket sessions Runtime v1 agents accept a bidirectional WebSocket session through `client.connect()` — typed envelopes, automatic reconnection, and `seq`-based resume. The full protocol is on the [WebSocket](/sdk/websocket) page (SDK) and [API → WebSocket](/api/messaging/websocket) (wire format). Runtime v2 agents don't open sessions: `connect()` answers with an `error` envelope explaining the session is SSE-only. They stream over HTTP with `streamMessage` instead — one call per turn, the same token-by-token reply, resumable with `Last-Event-ID`, and nothing to keep alive. The frames map almost one to one: | WebSocket session (runtime v1) | `streamMessage` (runtime v2) | | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | `const socket = await client.connect(); socket.sendMessage({ type: 'message', agent_id, user_id, text })` | `const stream = await client.messaging.streamMessage({ body: { user_id, text } })` | | `socket.on('message', (envelope) => …)` and switch on `envelope.type` | `for await (const event of stream)` and switch on `event.type` | | `thread` · `event` · `message` · `error` envelopes | The same four types, plus `suggestions` after the terminal `message` | | Token text at `envelope.data.data.text` (event `thread-message-text-content-updated`) | Token text at `event.data.text` | | `agent_id` required on every frame | Not needed — the agent comes from your key | | Reconnect, then `socket.sendResume({ type: 'resume', resume_after: lastSeq })` | Call again with `'Last-Event-ID': String(lastSeq)` in the request | | `socket.close()` | Nothing to close — the stream ends with the turn | ## Moving an integration to runtime v2 When the agent behind your key moves to runtime v2, sends keep working untouched. Work through this list for everything else: * [ ] **Thread reads** — add `runtime_version` (the build your agent runs on, e.g. `'0.8.0'`) to `listMessages`, `threads.getThread`, `getThreadMessages`, and `updateThread`; scope `listThreads` with `runtime_version` plus `agent_id` or `user_id`. * [ ] **Thread ids** — stop validating them as 24-character hex ids; runtime v2 ids are UUIDs. * [ ] **Streaming** — replace `connect()` + `sendMessage` frames with `streamMessage`, drop `agent_id` from the request, and resume with `Last-Event-ID` instead of a `resume` frame. * [ ] **Follow-up suggestions** — handle the new `suggestions` frame (`data.followup_suggestions`, with the `thread_id` and `interaction_id` it belongs to) instead of reading them off the message. * [ ] **Traces** — guard `threadTrace` / `interactionTrace` / `getThreadTrace` / `getInteractionTrace` calls until runtime v2 trace support lands. Still importing `ApolloClient` from `@aui.io/aui-client` 1.x (the `ia-controller` client)? Upgrade to v3 first — the [SDK upgrade guide](/sdk/migration) maps every call — then come back to this page for the runtime differences. # WebSocket (runtime v1) Source: https://docs.aui.io/sdk/websocket Bidirectional messaging sessions for runtime v1 agents — typed envelopes, reconnection, and resume. For [runtime v1 agents](/sdk/runtime-v1-agents), the messaging client can open a bidirectional WebSocket session: send message frames, receive the reply as a token stream, and recover missed events after a reconnect. Authentication is handled for you, and it works in both Node and the browser. **Runtime v1 agents only.** WebSocket sessions belong to runtime v1. Runtime v2 agents stream over HTTP instead — one [`streamMessage`](/sdk/messaging#stream-a-message) call per turn gives you the same token-by-token reply, resumable with `Last-Event-ID`, with no socket to keep alive; a runtime v2 agent answers `connect()` with an `error` envelope explaining the session is SSE-only. [Runtime v1 agents](/sdk/runtime-v1-agents#websocket-sessions) maps each socket frame to its `streamMessage` equivalent. (Tip: UUID thread ids mean runtime v2.) ## Open a session ```ts theme={"dark"} import { ApolloMessagingClient } from '@aui.io/aui-client'; const client = new ApolloMessagingClient({ publishableKey: 'pk_network_...' }); const socket = await client.connect(); await socket.waitForOpen(); ``` `connect()` accepts optional arguments: ```ts theme={"dark"} const socket = await client.connect({ headers: { 'x-request-tag': 'support-widget' }, // extra connect headers debug: false, // log socket activity reconnectAttempts: 30, // default: 30 }); ``` ## Send a message The socket sends typed frames. A `message` frame submits a turn — note that on the WebSocket the target agent is explicit; use the id resolved from your key: ```ts theme={"dark"} const { agentId } = await client.getContext(); socket.sendMessage({ type: 'message', agent_id: agentId!, user_id: 'end-user-123', text: 'I am looking for a built-in microwave', // thread_id: existingThreadId, // omit to auto-create a thread // context: { url: location.href }, // originating page URL // context: { welcome_message: '...' }, // seed the opening message // agent_variables: { ... }, }); ``` ## Receive envelopes Every server frame is a sequenced envelope. Register handlers with `on(event, handler)` — events are `open`, `message`, `error`, and `close`: ```ts theme={"dark"} let lastSeq = -1; let threadId: string | undefined; socket.on('message', (envelope) => { if (envelope.seq > 0) lastSeq = envelope.seq; switch (envelope.type) { case 'thread': // announced once per thread: the resolved id threadId = envelope.data?.thread_id; break; case 'event': { // streaming events while the agent works const name = envelope.data?.channel?.event_name; if (name === 'thread-message-text-content-updated') { process.stdout.write((envelope.data?.data as any)?.text ?? ''); } break; } case 'message': // the completed reply (plus trace_info when enabled) console.log('\ncomplete:', envelope.data?.text); break; case 'error': // session- or turn-level error; the socket stays open console.error(envelope.data); break; } }); socket.on('close', (event) => console.log('closed', event.code)); socket.on('error', (error) => console.error(error)); ``` | Envelope `type` | Meaning | `data` | | --------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | | `thread` | The resolved thread id (once per thread) | `{ thread_id }` | | `event` | Streaming event; token deltas arrive as `thread-message-text-content-updated` with the text under `data.data.text` | `Apollo.StreamEvent` | | `message` | Turn complete — the full reply | `Apollo.Message` + optional `trace_info` | | `error` | Session error (`{ error, code }`) or turn error (`{ message, status_code, error_code? }`) | varies | `on()` registers a **single handler per event** — calling it again for the same event replaces the previous handler. ## Resume after a reconnect Envelopes carry a monotonic `seq` (locally generated errors use `-1`). Track the highest value you've processed; after a reconnect, ask the server to replay what you missed instead of re-running the turn: ```ts theme={"dark"} socket.sendResume({ type: 'resume', resume_after: lastSeq }); ``` The socket reconnects automatically (up to `reconnectAttempts`), so a typical pattern is to send a `resume` frame from the `open` handler when `lastSeq > -1`. ## Close the session ```ts theme={"dark"} socket.close(); ``` The socket also exposes `readyState` for connection-state checks. ## Full example ```ts theme={"dark"} import { ApolloMessagingClient } from '@aui.io/aui-client'; const client = new ApolloMessagingClient({ publishableKey: process.env.AUI_PK! }); const { agentId } = await client.getContext(); const socket = await client.connect(); let lastSeq = -1; socket.on('open', () => { if (lastSeq > -1) socket.sendResume({ type: 'resume', resume_after: lastSeq }); }); socket.on('message', (envelope) => { if (envelope.seq > 0) lastSeq = envelope.seq; if (envelope.type === 'message') { console.log('agent:', envelope.data?.text); socket.close(); } }); await socket.waitForOpen(); socket.sendMessage({ type: 'message', agent_id: agentId!, user_id: 'end-user-123', text: 'Hello over WebSocket', }); ``` The wire protocol underneath (URL, query parameters like `include_trace` and `events`, close codes) is documented in [API → WebSocket](/api/messaging/websocket), with the machine-readable contract published as an AsyncAPI document.