Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.archal.ai/llms.txt

Use this file to discover all available pages before exploring further.

Twin endpoints sit behind a control-plane proxy. From outside the CLI, you typically need two auth headers — one for the control plane, one the twin sees. Use this guide when you can’t pull in @archal/runtime (Lambda, Cloudflare Worker, raw curl, polyglot CI). Normal Node.js scripts should use @archal/runtime — it handles both headers.

The two-header pattern

Authorization: Bearer $ARCHAL_TOKEN
x-archal-upstream-authorization: Bearer <non-empty-upstream-token>
  • Authorization — authenticates to the control plane. Token comes from archal login (~/.archal/credentials.json) or a long-lived dashboard token. The control plane verifies the sessionId in the URL belongs to you and the session is live.
  • x-archal-upstream-authorization — what the twin sees as Authorization after the proxy forwards. The proxy strips the original Authorization, rewrites x-archal-upstream-authorization into Authorization, and passes the rest through. Twins that enforce bearer-token auth (e.g. github’s requireGitHubBearerToken) check this header.
The upstream token is any non-empty string — twins simulate the real service’s behavior without a token database. The github twin tests use ghp_test_bootstrap_token. Omit x-archal-upstream-authorization and the proxy forwards your outer Authorization through to the twin. Current bearer-enforcing twins (github, slack, etc.) accept any non-empty bearer token, so the outer Archal token will usually pass their check — but a future twin could enforce a stricter shape and 401. Always send both headers so behavior stays predictable.

Example (curl)

Given a running twin session:
archal twin start github
# → Session: env_12345
# → github twin ready: https://api.archal.ai/runtime/env_12345/github/api
You can hit the twin’s REST surface directly:
export ARCHAL_TOKEN=$(cat ~/.archal/credentials.json | jq -r .token)

# Create a repo on the twin — works exactly like real github.
curl -X POST https://api.archal.ai/runtime/env_12345/github/api/user/repos \
  -H "Authorization: Bearer $ARCHAL_TOKEN" \
  -H "x-archal-upstream-authorization: Bearer ghp_test_bootstrap_token" \
  -H "Content-Type: application/json" \
  -d '{"name": "test-repo", "private": false}'
Omit the outer Authorization and the control plane 401s before the request ever reaches the twin. Omit only x-archal-upstream-authorization and the proxy forwards your outer token through — current bearer-enforcing twins accept any non-empty bearer, so you’ll usually get a 200, but a future stricter twin could return a real service-shaped 401 like:
{
  "message": "Requires authentication",
  "documentation_url": "https://docs.github.com/rest/overview/resources-in-the-rest-api#authentication",
  "status": "401"
}

Example (Python urllib)

import json
import os
import urllib.request

session_id = "env_12345"
base_url = f"https://api.archal.ai/runtime/{session_id}/github/api"

req = urllib.request.Request(
    f"{base_url}/user/repos",
    method="POST",
    data=json.dumps({"name": "test-repo", "private": False}).encode(),
    headers={
        "Authorization": f"Bearer {os.environ['ARCHAL_TOKEN']}",
        "x-archal-upstream-authorization": "Bearer ghp_test_bootstrap_token",
        "Content-Type": "application/json",
    },
)
response = urllib.request.urlopen(req)
print(json.loads(response.read()))

Example (AWS Lambda / Cloudflare Worker)

export async function handler(event: { body: string }) {
  const sessionId = process.env.ARCHAL_SESSION_ID!;
  const baseUrl = `https://api.archal.ai/runtime/${sessionId}/github/api`;

  const response = await fetch(`${baseUrl}/user/repos`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.ARCHAL_TOKEN}`,
      'x-archal-upstream-authorization': 'Bearer ghp_test_bootstrap_token',
      'Content-Type': 'application/json',
    },
    body: event.body,
    // Always cap twin requests with a timeout. Without it, a hung twin
    // worker keeps the Lambda invocation alive until the platform kills
    // it — burning budget and producing no useful failure signal.
    signal: AbortSignal.timeout(15000),
  });
  return { statusCode: response.status, body: await response.text() };
}

Header semantics

Proxy order of operations (see infra/api/session/session-worker-proxy.ts — the capture/strip/re-emit lives there; infra/api/routes/session-events.ts just wires the route):
  1. Validate the outer Authorization: Bearer <archal-token> against your session. Bad/expired/wrong-user tokens get a 401 or 403 here — the twin never sees them.
  2. Capture x-archal-upstream-authorization if present.
  3. Strip sensitive request headers (including x-archal-upstream-authorization).
  4. Set forwarded Authorization to the captured upstream value, or pass through the original outer Authorization if no upstream header was supplied.
  5. Forward to the twin.
Your real Archal token only authenticates the outer hop; the twin sees whatever you placed in x-archal-upstream-authorization. Smuggling a real third-party token through the outer Authorization fails at step 1.

See also