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

# REST Surface

> The HTTP routes the self-hosted v2-agent-state edge function actually exposes.

The self-hosted deployment exposes the same HTTP surface as the managed Platform — it is the identical `v2-agent-state` function, just running on your Supabase project. Point a client at your own base URL and everything below behaves identically.

<Note>
  This is a **replication** API — it stores and serves encrypted snapshots and replication blocks. It is **not** a memory-extraction API. There are no `/memories` routes, no message-array ingestion, and no server-side search: embedding and semantic recall happen entirely on-device. See [store\_memory](/platform/core-concepts/store-memory) for where that actually lives.
</Note>

## Base URL

```text theme={null}
https://<project-ref>.supabase.co/functions/v1/v2-agent-state
```

The function strips both the `v2-agent-state` function-name prefix and an optional `/v2` prefix, so `/snapshot` and `/v2/snapshot` are equivalent. Older clients that send the `/v2` form continue to work.

## Authentication

Every route below requires a bearer token. There is no `AUTH_DISABLED` mode.

```http theme={null}
Authorization: Bearer sov_live_your-key
```

Routes fall into three auth classes:

| Class             | Used by                                                | Token                                            |
| ----------------- | ------------------------------------------------------ | ------------------------------------------------ |
| **API key**       | Replication routes (below)                             | `sov_proj_` or `sov_live_`                       |
| **Session JWT**   | Account, billing, teams, settings — dashboard surfaces | WorkOS session JWT, verified against remote JWKS |
| **Scoped bearer** | SCIM provisioning, retention/lifecycle crons           | `scim_` token or cron secret                     |

***

## Replication routes

These are the routes the SDKs use. All are API-key authenticated and subject to the trial gate.

### Write a snapshot

* **`POST /snapshot`**

Body is the sealed envelope — see [POST /snapshot](/api-reference/snapshot) for the full field reference.

```json theme={null}
{
  "agent_id": "sha256-hex",
  "sequence_number": 0,
  "parent_snapshot": null,
  "policy_hash": "0000…",
  "client_payload_hash": "sha256-hex",
  "ciphertext_b64": "…",
  "byte_size": 1234,
  "timestamp": "2026-08-03T09:00:00Z"
}
```

### Restore

* **`GET /snapshot/:agent_id/latest`** — most recent snapshot for an agent
* **`GET /snapshot/:agent_id/:sequence`** — a specific point in history
* **`GET /snapshot/:agent_id/lineage?limit=100`** — the parent chain (default limit `100`)

### Differential replication

* **`POST /replicate`** — push encrypted replication blocks
* **`GET /head`** — latest confirmed sequence number and Merkle root
* **`GET /replay?since=N`** — stream replication log from a sequence number

### Layer-A reads (server-blind metadata)

* **`GET /metadata`** — usage aggregates: envelope counts, byte totals, device count
* **`GET /envelopes`** — paginated ciphertext envelope list

***

## Dashboard and account routes

These exist on the same function but are authenticated by **session JWT**, not API key. They are used by the dashboard rather than the SDKs.

| Route                            | Method(s)                                   | Purpose                                                      |
| -------------------------------- | ------------------------------------------- | ------------------------------------------------------------ |
| `/api-keys`                      | `POST` `GET` `DELETE /:id`                  | Issue, list, revoke API keys                                 |
| `/webhooks`                      | `POST` `GET` `DELETE /:id` `POST /:id/test` | Webhook registration and testing                             |
| `/billing/create-checkout`       | `POST`                                      | Start a Polar checkout                                       |
| `/billing/customer-portal`       | `POST`                                      | Open the Polar customer portal                               |
| `/billing/subscription`          | `GET`                                       | Current subscription state                                   |
| `/billing/update-seats`          | `POST`                                      | Scale team seats                                             |
| `/billing/usage`                 | `GET`                                       | Layer-A usage aggregates                                     |
| `/billing/webhook`               | `POST`                                      | Polar webhook receiver (signature-verified, unauthenticated) |
| `/settings`                      | `GET` `PUT`                                 | Dashboard preference persistence                             |
| `/telemetry`                     | `POST`                                      | Opt-in aggregate-only telemetry                              |
| `/account/kdf-salt`, `/kdf-salt` | `GET`                                       | Mint or fetch the per-user KDF salt                          |

## Team and enterprise routes

| Route                         | Method(s)                  | Purpose                                            |
| ----------------------------- | -------------------------- | -------------------------------------------------- |
| `/teams`                      | `GET` `POST`               | List and create teams                              |
| `/teams/:id/invitations`      | `POST`                     | Invite a member                                    |
| `/teams/:id/grants`           | `POST`                     | Issue a wrapped DEK grant                          |
| `/teams/:id/grant`            | `GET`                      | Fetch your wrapped grant                           |
| `/teams/:id/rotate`           | `POST`                     | Rotate the team DEK                                |
| `/teams/:id/memories`         | `POST` `GET`               | Shared encrypted memory                            |
| `/teams/:id/subkeys`          | `POST`                     | Issue a sub-key                                    |
| `/teams/:id/members`          | `GET`                      | List members                                       |
| `/teams/:id/sync-log`         | `GET`                      | Team replication log                               |
| `/teams/:id/audit`            | `GET`                      | Tamper-evident audit entries                       |
| `/teams/:id/audit/verify`     | `GET`                      | Verify the audit hash chain                        |
| `/teams/:id/audit/export`     | `GET`                      | Export the audit log                               |
| `/teams/:id/guardians/invite` | `POST`                     | Invite a Shamir recovery guardian                  |
| `/sso/:teamId/config`         | `GET` `PUT`                | SAML IdP descriptor (Enterprise)                   |
| `/sso/:teamId/scim-tokens`    | `POST` `GET` `DELETE /:id` | SCIM token lifecycle (Enterprise)                  |
| `/scim/v2/*`                  | various                    | SCIM 2.0 provisioning (Enterprise, `scim_` bearer) |

## Cron and lifecycle routes

Authenticated by a cron secret, not a user token:

* **`POST /retention/purge`** — daily retention sweep
* **`POST /lifecycle/dispatch`** — lifecycle email sweep
* **`POST /invitations/accept`** — accept an invitation (unauthenticated or session-authed)
* **`POST /marketing/capture`**, **`POST /marketing/unsubscribe`** — marketing capture and unsubscribe

***

## Errors

Unmatched paths return:

```json theme={null}
{ "error": "route_not_found" }
```

Errors use a consistent envelope: `{ "error": "<code>", ...details }` with the appropriate HTTP status. See [Errors](/api-reference/errors) for the full code reference.
