---
title: "API Connection Reference"
description: "Choose an API entry point, call External Session Run, and understand authentication, retries, errors, and implementation boundaries."
---

# API Connection Reference

Use this page when connecting CI or another application to aachat, or tracing how the CLI and WebUI talk to the server. For starting new work from an external system, use **External Session Run**. For normal human and agent operations, start with the [CLI reference](/en/docs/cli). A route being present in source, or having a `/v1` prefix, does not promise a stable third-party API, a compatibility period, or a version migration policy.

## Choose the entry point and identity

| Your goal | Recommended entry point | Identity and authority |
|---|---|---|
| Start a new Session from CI or a webhook adapter | [External Session Run](/en/docs/external-session-runs); HTTP example below | A dedicated opaque bearer key fixes the project, agent, launch settings and workspace repo. It is not a human login token. |
| Manage teams, projects, members and agents; send messages; use documents, media, HTML, Project Database, Company knowledge or meetings | Human `aachat` CLI and WebUI; existing shared Client for repository development | Logged-in human identity, with team/project roles and operation-specific checks. Authentication alone does not grant write or administration rights. |
| Work inside a running Session, including messages, Asks, documents, Workflow/Trigger operations and delivery receipts | Session-provided `chat` CLI and projected contracts | Session agent identity, project coverage, current membership and lifecycle checks. A Session token is not a general-purpose service credential. |
| Start, continue or inspect interactive Sessions, Workflows and Triggers | CLI/WebUI operations and their existing Client methods | Human or Session identity according to the route; launch, ownership and project scope are checked separately. External run keys do not authorize these APIs. |
| Browse Discover agents, skills and templates, or read intentionally shared content | Public WebUI and issued share URLs | Some reads are anonymous; installing, publishing, voting or managing shares has separate authentication and ownership checks. Share tokens authorize their particular content scope. |
| Implement the WebUI, runtime or aachat infrastructure | Existing WebUI API layer, shared Client, sync and realtime implementation | Internal client/runtime protocols, one-time realtime tickets, or dedicated server/worker authority. Control/admin routes and provider webhooks are not general integration entry points. |

External Session Run starts new work; it does not expose a general API key for browsing project data, continuing any Session, approving permissions, or administering an agent. If a third-party webhook should start work, receive and validate that provider's event in your adapter, then call External Session Run. Do not send arbitrary events to aachat's provider-specific webhook endpoints.

## Prepare an external credential

Use **Start from external app** in the project's WebUI, as described in [External Session Run](/en/docs/external-session-runs). The manager must be the agent's current human owner and an active project Admin or Collaborator; the agent must also be an active project Admin or Collaborator. The project must be active and the owner's runtime must be able to start the chosen agent with the fixed launch settings and workspace repository.

The creation response contains `key`, `token`, and `run_url`. The screen presents the latter two as `AACHAT_API_KEY` and `AACHAT_API_URL`. Here, `AACHAT_API_URL` means the **complete POST URL**, ending in `/v1/external/session-runs`, not just the server origin. Use the returned URL rather than guessing a deployment hostname. Creation/rotation reveals the raw key once; listing credentials does not return it. Store it in the caller's secret store. Do not copy a human login token, Session token, GitHub token or internal worker secret into this field.

The run endpoint reads `Authorization: Bearer <key>` itself; it does not authenticate this request as a human JWT. The server stores a hash of the external key. The prompt and metadata are invocation data, so keep secrets out of both and out of logs. See [Trust Boundary](/en/docs/trust-boundary) for storage and execution boundaries.

## Send a complete request

The following shell example requires curl with `--fail-with-body`, a trusted credential URL and the key loaded into environment variables. Sending it starts real work in that credential's project. The file contains an illustrative CI event, not a live token; replace its task and event identifiers before use, then retain the exact request for retries.

```bash
# AACHAT_API_URL and AACHAT_API_KEY come from the credential creation screen.
# AACHAT_API_URL is the complete run_url, without a query string.
: "${AACHAT_API_URL:?Set the credential run_url}"
: "${AACHAT_API_KEY:?Load the key from your secret store}"

cat > external-run.json <<'JSON'
{
  "text": "Inspect the CI failure for commit abc123 and report findings in the project.",
  "idempotency_key": "ci:example/repo:12345:attempt-1",
  "metadata": {
    "source": "ci",
    "repository": "example/repo",
    "run_id": "12345"
  }
}
JSON

curl --silent --show-error --fail-with-body \
  --request POST "${AACHAT_API_URL}?wait=started" \
  --header "Authorization: Bearer ${AACHAT_API_KEY}" \
  --header 'Content-Type: application/json' \
  --data-binary @external-run.json
```

Only `text`, `idempotency_key` and optional `metadata` are accepted in the JSON body; unknown fields are rejected. Do not add `agent_id`, `project`, `mode`, `launch` or `workspace_repo` to override the credential.

| Input | Current validation |
|---|---|
| `text` | Trimmed, nonempty, at most 100,000 UTF-8 bytes. Runtime `/loop` shortcut syntax is rejected. |
| `idempotency_key` | Trimmed, nonempty, at most 200 UTF-8 bytes; scoped to the credential ID. Use a durable external event identifier. |
| `metadata` | Optional JSON object, at most 8,192 bytes after canonical JSON serialization. Object key order is normalized; an array is not accepted. |
| `wait` query | Omitted or `accepted` returns after acceptance; `started` waits up to the current 10-second start acknowledgement timeout. No completion wait mode. |

New invocations are limited per credential to 30 in a 60-second window and 5 pending (`accepted`) invocations. These are implementation limits, not throughput guarantees. An identical retry returns the existing invocation before these new-invocation checks.

## Interpret the response and read it again

Successful POST and GET return HTTP **200** with an invocation body. The following bodies are illustrative complete shapes; IDs, timestamps and the app hostname are examples. Optional absent fields are omitted rather than necessarily emitted as `null`.

An accepted invocation can look like this, including after `wait=started` reaches its timeout:

```json
{
  "invocation_id": "11111111-1111-4111-8111-111111111111",
  "project": { "team": "example", "name": "ci-checks" },
  "agent_id": "22222222-2222-4222-8222-222222222222",
  "accepted_at": "2026-09-06T09:00:00Z",
  "status": "accepted"
}
```

Once the runtime acknowledges startup, it can look like this:

```json
{
  "invocation_id": "11111111-1111-4111-8111-111111111111",
  "project": { "team": "example", "name": "ci-checks" },
  "agent_id": "22222222-2222-4222-8222-222222222222",
  "session_id": "33333333-3333-4333-8333-333333333333",
  "accepted_at": "2026-09-06T09:00:00Z",
  "status": "started",
  "web_url": "https://app.example.test/t/example/projects/ci-checks?session=33333333-3333-4333-8333-333333333333"
}
```

`started` confirms Session startup, not task completion, CI success, code delivery or a merge. Open the returned `web_url` to follow the work. While still `accepted`, a Session may already be bound, so `session_id` and `web_url` can also be present before `started`.

Save `invocation_id`. Read that invocation without starting another Session:

```bash
INVOCATION_ID='11111111-1111-4111-8111-111111111111' # Replace with the returned ID.
curl --silent --show-error --fail-with-body \
  --header "Authorization: Bearer ${AACHAT_API_KEY}" \
  "${AACHAT_API_URL}/${INVOCATION_ID}"
```

GET returns the same response type. It is scoped to the **token used when this invocation was accepted**, not merely the credential's current token. A missing invocation or a token that does not match that acceptance returns 404. An absent or malformed bearer token returns 401.

## Retry, rotate and revoke deliberately

When a POST response is lost, resend the original request with the same `idempotency_key` and credential. If you already have `invocation_id`, GET it first. Do not generate a new event key just because of a network timeout or an `accepted` response: that can create separate work.

The request comparison includes normalized text, metadata, fixed launch and workspace context. Reusing a key with different input returns 409. JSON object key order is normalized, but changed values are different input. A terminal `failed` invocation is also returned by an identical retry; resending does not restart it. Inspect the failure and any bound Session, fix the cause, then use a new idempotency key only for an intentional new attempt.

- **Revoke or expiry:** blocks new invocations. An already accepted invocation can still be read with its acceptance token, and an identical POST retry can return it while that token still identifies the credential. Revocation is not cancellation of already accepted work or erasure of read access to it.
- **Rotate:** changes an active credential’s token. Revoked or expired credentials cannot be rotated; create a new credential. The old token no longer identifies the credential for POST, including retries. The old acceptance token can still GET invocations accepted with it; the new token cannot GET those old invocations. A same-input POST with the new token and the same idempotency key can return an existing invocation under the same credential ID.
- **Reissue:** creates a different credential identity. Idempotency does not deduplicate across credentials. Reconcile outstanding work before resubmitting through a replacement credential.

New work also rechecks the active project, the creator's current agent ownership and project membership, and the agent's active project membership. A key does not bypass changes to these relationships. Reconfigure through the authorized human owner if they no longer match. Keep acceptance tokens protected while you need them for recovery; do not publish them as diagnostic evidence.

## Errors and recovery

HTTP request rejection uses an `error` envelope, distinct from an invocation's `failure`. For example, changing `metadata` in the request to `[]` produces HTTP 400:

```json
{
  "error": {
    "code": "validation_error",
    "message": "metadata must be an object when provided."
  }
}
```

An invocation accepted earlier can later fail to start and still return HTTP 200, for example if the target project becomes inactive before startup:

```json
{
  "invocation_id": "11111111-1111-4111-8111-111111111111",
  "project": { "team": "example", "name": "ci-checks" },
  "agent_id": "22222222-2222-4222-8222-222222222222",
  "accepted_at": "2026-09-06T09:00:00Z",
  "status": "failed",
  "failure": {
    "code": "conflict",
    "message": "Target project is no longer active. Reissue the external session run key for an active project."
  }
}
```

A failed response may also contain a bound `session_id` and `web_url`. Read them before deciding to start again. Failure codes and messages depend on the actual launch failure; do not treat the example as the only failure mode.

| Observation | Next action |
|---|---|
| 400 `validation_error` | Read the message. Correct request fields, metadata, limits or `wait`; if the target agent is no longer an active Admin or Collaborator, have the human owner restore its project membership before retrying. |
| 401 `unauthorized` | Check the bearer header and intended external key, including rotation; do not replace it with a human or Session token. |
| 403 | Have the human owner check current ownership and project roles. |
| GET 404 `not_found` | Check the invocation ID and its acceptance token. Changing to the newest token does not recover an older invocation. |
| 409 `conflict` | Read the message: reconcile reused-key input, project state, launch or workspace context. A new key is appropriate only for intentional new work. |
| 410 `gone` | The credential cannot accept new work after revoke/expiry; manage it through the owner and reconcile existing invocations first. |
| 429 `rate_limited` | Back off and inspect pending work. Do not churn event keys; these limits do not promise a `Retry-After` header. |
| Network error, timeout or server error | Outcome may be unknown. GET the known invocation or resend the identical POST; retain enough event correlation to reconcile it. |
| 200 with `accepted` or `failed` | Handle `status` and `failure`, not only curl's exit status. `accepted` remains pending; `failed` requires diagnosis before a new attempt. |

## HTTP correlation IDs and redirect boundaries

On supported servers, the `X-Aachat-Request-Id` response header helps correlate an HTTP attempt. With curl, `--dump-header <file>` saves response headers separately. Obtain it through an authorized read, such as a GET for a known invocation; do not repeat a POST to obtain an ID. Replace the header destination with an actual local file and check it for secrets before sharing.

The server adopts a valid single UUID in the request header of the same name; a missing, invalid, or duplicate header produces a new UUID in the response. This is not a required authentication header and grants neither permission nor duplicate-execution protection. Use the `idempotency_key` and invocation checks above for retries. If the header or diagnostic fields cannot be obtained, leave server correlation unverified ([reading diagnostics](/en/docs/troubleshooting)).

The built-in aachat Rust API client does not follow redirects to another origin; same-origin redirects retain the existing bounded following policy. Web fetches to the aachat API reject all redirects, including same-origin ones. This restriction does not describe ordinary page navigation, external storage-grant retrieval, or users' own curl requests in general. If an API endpoint or proxy redirects, confirm the final canonical API endpoint and response with an administrator/support. Do not disable header protection as a workaround or unconditionally resend a write with an unknown outcome.

## Read the existing implementation

For repository development, trace an operation from its existing client method through the mounted route and request/response types to authorization and persistence. Use the source revision corresponding to the server/client you are investigating. The links below open the repository's main branch; they are reading entry points, not a separately versioned SDK specification.

| Question | Source entry point |
|---|---|
| Where are HTTP families mounted and protected? | [Server root](https://github.com/kensaku63/aachat/blob/main/crates/server/src/main.rs), [route composition](https://github.com/kensaku63/aachat/blob/main/crates/server/src/routes.rs), [authentication](https://github.com/kensaku63/aachat/blob/main/crates/server/src/auth.rs) |
| How do existing Rust callers form requests and decode errors? | [Shared Client](https://github.com/kensaku63/aachat/blob/main/crates/shared/src/client.rs) and its [operation modules](https://github.com/kensaku63/aachat/tree/main/crates/shared/src/client) |
| Which JSON fields and enum values are accepted? | [API type registry](https://github.com/kensaku63/aachat/blob/main/crates/shared/src/api_types.rs), [Session and external run types](https://github.com/kensaku63/aachat/blob/main/crates/shared/src/api_types/sessions.rs), [common error types](https://github.com/kensaku63/aachat/blob/main/crates/shared/src/api_types/common.rs) |
| Where do external run validation, retry and credential checks live? | [External run handler](https://github.com/kensaku63/aachat/blob/main/crates/server/src/routes/external_session_runs.rs), [invocation persistence](https://github.com/kensaku63/aachat/blob/main/crates/server/src/db/external_session_runs.rs), [error mapping](https://github.com/kensaku63/aachat/blob/main/crates/server/src/error.rs) |
| Where are stored constraints and changes defined? | [Database migrations](https://github.com/kensaku63/aachat/tree/main/db/migrations); read later migrations as well as the initial table definition. |

Teams/projects/membership, messages/search/timeline, shared documents/artifacts, Company/registry/meetings, Sessions/permissions/waits, Workflows/Triggers, and Discover/publication each have corresponding route and type modules. Follow the family for your use case instead of copying an unrelated HTTP literal. WebUI-specific reads and runtime writes are not interchangeable merely because they share a server.

Control/admin endpoints, development login, sync/runtime delivery, worker/gateway authorization, provider webhook payloads and realtime protocols are intentionally not presented here as callable third-party examples. Use their implementation and deployment contracts when contributing to those components. This reference adds no separate SDK, OpenAPI generator or stability guarantee.
