> ## Documentation Index
> Fetch the complete documentation index at: https://docs.praxa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# API keys

> Two API-key credential families exist for callers outside Praxa's own

Two API-key credential families exist for callers outside Praxa's own
first-party apps. `aura_agent_*` keys are live today, each scoped to one
published custom-agent deployment. `praxa_sk_` keys are the tenant-wide
credential for the Praxa Execution Fabric (`/v1/execute` and the rest of the
`/v1/*` gateway) and are a developer preview.

> **Status:** `aura_agent_*` deployment keys and embed origin-allowlists —
> **Available today.** `praxa_sk_` tenant keys, scopes, and the key console —
> **Developer preview.**

`aura_agent_*` keys are not being retired. Per the fabric design's resolved
decisions, `aura_agent_*` "stays valid for custom-agent deployments and
migrates onto the tenant model later" — you do not need to pre-emptively
rotate onto `praxa_sk_` when it ships (design spec §7.2).

For what a key's scope actually constrains, see `scopes-and-tenancy.md`. For
throttling behavior, see `rate-limits.md`.

## Key formats

| Prefix        | Status            | Grants access to                                                           | Principal kind               |
| ------------- | ----------------- | -------------------------------------------------------------------------- | ---------------------------- |
| `aura_agent_` | Available today   | One published custom-agent deployment's External API channel               | API key, deployment-scoped   |
| `praxa_sk_`   | Developer preview | A tenant's Execution Fabric endpoints, within the key's scopes             | API key, tenant-scoped       |
| Supabase JWT  | Available today   | First-party apps (Expo, web, desktop/Control) acting as the signed-in user | User session, not an API key |

These three are the only principal kinds the platform recognizes on
authenticated surfaces. All three resolve through one server-side principal
resolver to the same fail-closed, server-derived authority model — the
credential changes, the authorization logic doesn't (design spec §3.2).

### `aura_agent_*` — deployment credential

* **Shape:** `aura_agent_` followed by 64 lowercase hex characters (32 random
  bytes from `crypto.getRandomValues`).
* **What it can do:** authenticates one deployment's External API channel
  only. It has no separate scope list — the key *is* the deployment's
  identity, so its authority is exactly that deployment's bound connectors,
  knowledge, and memory (`docs/custom-agents.md`).
* **Companion control:** a deployment on the website-embed channel is
  additionally bound to an explicit origin allowlist, checked at both token
  exchange and run time — see `scopes-and-tenancy.md` for the full mechanism.
* **Recognition on the wire:** `Authorization: Bearer aura_agent_<64 hex>`.
  The server distinguishes "not an Aura key" from "an Aura key that's
  malformed" — a value that starts with `aura_agent_` but doesn't match the
  full 64-hex-character shape is still flagged, so the route fails closed
  instead of silently treating it as anonymous
  (`src/lib/custom-agents/api-auth.server.ts`).

### `praxa_sk_` — tenant credential (developer preview)

* **Shape:** `praxa_sk_` followed by a hex string. The prefix itself is a
  locked owner decision (2026-07-30); the exact byte length isn't finalized
  in the design spec yet.
* **What it can do:** scoped by the key, not implicit like `aura_agent_*`.
  Each key carries scopes, an expiry, a per-key rate-limit tier, and a
  last-used timestamp, managed through a key CRUD console (design spec
  §3.2).
* **Endpoint family:** the public gateway's `/v1/*` routes
  (`execute`, `runs`, `runs/:id/events`, `runs/:id/approve`,
  `runs/:id/cancel`, `usage`, `webhooks`), served at `api.praxa.io` — already
  a live custom domain on the router worker, but the `/v1/*` dispatch to the
  gateway is flag-gated and not turned on yet (design spec §3.1, §4 F1).
  This is a **different path** from the existing `/api/v1/*` desktop
  gateway, which is Supabase-JWT-only and unrelated to API keys.
* As of this writing, no endpoint serves this contract in production yet —
  the request/event/error schemas exist in `packages/ai-platform` but are
  not wired to a live route (`docs/ai-platform/README.md` §7).

## Lifecycle

### Create

* **`aura_agent_*`:** minted server-side when a deployment's External API
  channel is created. 32 random bytes become `aura_agent_<hex>`
  (`createAgentPublishableKey()`,
  `src/lib/custom-agents/embed-token.server.ts`). The plaintext is returned
  exactly once, in that creation response.
* **`praxa_sk_` (preview):** issued from the tenant's key console (design
  spec §3.2, §4 F2). Not built yet.

### Rotate

* **`aura_agent_*`:** the `rotate_deployment_secret` (External API channel)
  and `rotate_embed_secret` (website channel) mutations call Postgres RPCs
  that generate a fresh secret server-side, store its hash, and return the
  new plaintext exactly once in that mutation's response
  (`src/lib/custom-agents/repository.server.ts`). The old key stops
  verifying immediately: "Secret rotation invalidates the old credential for
  future exchanges" (`docs/custom-agents.md`, "Lifecycle and versions").
  Plan for a hard cutover, not a shared grace window.
* **`praxa_sk_` (preview):** the design spec names key CRUD and revocation
  explicitly but does not name a dedicated rotate operation the way
  `aura_agent_*` has one. Until/unless one ships, treat rotation as
  create-new-then-revoke-old through the console — that's an operational
  reading of the CRUD surface, not a documented endpoint.

### Revoke

* **`aura_agent_*`:** revocation is deployment-level, not a standalone
  key-revoke call, because the key *is* the deployment credential.
  Deployments have their own `active`, `paused`, and `revoked` states;
  pausing blocks new execution without destroying audit history, and
  revoking is the permanent form (`docs/custom-agents.md`).
* **`praxa_sk_` (preview):** revocation is a named, per-key field/operation —
  independent keys on the same tenant can be revoked individually (design
  spec §3.2).

### Expire

* **`aura_agent_*`:** no built-in expiry on the deployment credential itself.
  It's valid until rotated or the deployment is paused/revoked. (Don't
  confuse this with the *website-embed visitor token* exchanged for it —
  that token is short-lived and carries its own `token_ttl_seconds`, but
  it's a different, downstream credential.)
* **`praxa_sk_` (preview):** an explicit `expiry` field, with `last-used`
  tracked separately (design spec §3.2).

## Storage promises

* **Hashed at rest, never stored or logged in plaintext.**
  For `aura_agent_*`: a SHA-256 digest, hex-encoded, verified with a
  constant-time comparison (`hashAgentPublishableKey`,
  `verifyAgentPublishableKey`,
  `src/lib/custom-agents/embed-token.server.ts`) — the same digest Postgres
  computes via `pgcrypto`, so there's one credential authority, not two
  drifting implementations.
  For `praxa_sk_`: the design spec commits to "hash-at-rest" without naming
  an algorithm. The `aura_agent_*` SHA-256-plus-constant-time-compare
  implementation is the likely template but isn't confirmed for
  `platform_api_keys` yet.
* **Shown once.** The plaintext key comes back in the create/rotate response
  body and never again — not on a later read, not in the console, not in
  logs. Losing it means rotating or issuing a new one, not recovering it.
* **Never in logs or error responses.** Server logs and errors carry stable
  codes and correlation IDs, not provider payloads or secrets
  (`docs/decisions/2026-07-24-ai-platform-extraction.md`, "Security model").

## Best practices

* **Environment variables or a secrets manager — never source control, a
  mobile/web app bundle, or client-side JavaScript.** Both key families are
  bearer secrets: whoever holds one can act as that deployment
  (`aura_agent_*`) or that tenant (`praxa_sk_`). Anything that ships to a
  browser or device is readable by whoever has the browser or device.
* **Never hand a key to a browser or native client directly.** Exchange it
  for a short-lived, origin-bound token server-side instead. The website
  embed channel already works this way: the long-lived credential is
  exchanged for a short-lived visitor token bound to deployment, origin, and
  session (`docs/custom-agents.md`, "Runtime channels") — mirror that
  pattern for anything you build on top of these keys.
* **Rotate on a schedule, and immediately after any suspected exposure** — a
  key committed to a repo, a leaked log line, an offboarded integration.
  Rotation invalidates the old key immediately, so coordinate the cutover
  rather than assuming overlap.
* **Least privilege.** For `praxa_sk_`, request only the scopes an
  integration needs (see `scopes-and-tenancy.md`). For `aura_agent_*`,
  which has no scope list to narrow, the equivalent discipline is one key
  per deployment per integration — don't share a single deployment's key
  across unrelated systems just because it's convenient.
* **One key per system.** Distinct keys per integration let you revoke one
  caller without rotating every other one that happens to share a key.

## Examples

### `aura_agent_*` — calling a deployment's External API today

```bash theme={null}
curl https://api.praxa.io/api/custom-agent-run \
  -H "Authorization: Bearer aura_agent_0000000000000000000000000000000000000000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "deploymentPublicId": "dep_your_deployment_id",
    "message": "Summarize this week'\''s open tickets."
  }'
```

```ts theme={null}
const response = await fetch(
  "https://api.praxa.io/api/custom-agent-run",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AURA_AGENT_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      deploymentPublicId: "dep_your_deployment_id",
      message: "Summarize this week's open tickets.",
    }),
  },
);
```

### `praxa_sk_` — Execution Fabric (developer preview, illustrative)

Not live yet. Field names below follow the design spec's `/v1/execute`
parameter list (`task, context, tools, policy, delivery`, plus an
`Idempotency-Key` header) — the JSON Schema itself is not yet published, so
treat the nesting as illustrative rather than a contract.

```bash theme={null}
curl https://api.praxa.io/v1/execute \
  -H "Authorization: Bearer praxa_sk_00000000000000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2026-07-30-example-001" \
  -d '{
    "task": "Draft a release summary from the last 20 commits.",
    "context": {},
    "tools": { "allow": ["web_search"] },
    "policy": { "approvalMode": "human_approval" },
    "delivery": { "mode": "task" }
  }'
```

## See also

* `scopes-and-tenancy.md` — the tenant model and what a scope constrains.
* `rate-limits.md` — per-key throttling, 429/503 handling, retry guidance.
