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

# Gate API

> Integrate Foil Gate agentic signup over REST: browse the service registry, create signup sessions, verify agent tokens, and consume login codes.

The Gate API powers agentic signup flows. It manages a public service registry, organization-owned Gate services, signup sessions, agent tokens, and fresh dashboard login sessions that require a new browser approval.

All Gate endpoints are mounted at `/v1/gate/*`.
Workflow endpoints use Gate-native credentials such as `gtpoll_*`, `agt_*`, one-time approval tokens, and one-time dashboard login codes.
Business backend endpoints use the standard `Authorization: Bearer sk_*` header plus Gate-specific scopes.
Service metadata such as `website`, `docs_url`, and `dashboard_login_url` is authoritative in the Gate registry and is resolved from the session's `service_id`.

## Endpoints

### Service registry

<ParamField path="GET /v1/gate/registry" type="endpoint">
  List all services available for signup.
</ParamField>

```bash theme={"dark"}
curl https://api.usefoil.com/v1/gate/registry
```

```json theme={"dark"}
{
  "data": [
    {
      "id": "foil",
      "status": "active",
      "discoverable": true,
      "name": "Foil",
      "description": "Detect AI agents and malicious users",
      "website": "https://usefoil.com",
      "dashboard_login_url": "https://dashboard.usefoil.com/auth/gate",
      "docs_url": "https://usefoil.com/docs",
      "env_vars": [
        {
          "name": "Publishable key",
          "key": "FOIL_PUBLISHABLE_KEY",
          "secret": false
        },
        {
          "name": "Secret key",
          "key": "FOIL_SECRET_KEY",
          "secret": true
        }
      ],
      "branding": { "logo_url": "...", "primary_color": "#117BE7", "verified": true },
      "consent": { "terms_url": "...", "privacy_url": "..." }
    }
  ]
}
```

<ParamField path="GET /v1/gate/registry/:serviceId" type="endpoint">
  Look up a specific service by ID.
</ParamField>

Only services that are both `active` and `discoverable` appear in the public registry.
The `env_vars` array lists the customer-owned keys the CLI may write locally.
Gate-owned login credentials are derived internally and are not exposed in the registry. When dashboard login is enabled, the CLI writes the Gate login token to `.env` under `<SERVICE_ID>_GATE_AGENT_TOKEN` (for example `FOIL_GATE_AGENT_TOKEN`).

***

### Organization-owned Gate services

Businesses manage their own Gate service definitions under `/v1/gate/services`.

<ParamField path="GET /v1/gate/services" type="endpoint">
  List the Gate services owned by the authenticated organization. Requires `Authorization: Bearer sk_...` plus the `gate:services:read` scope.
</ParamField>

<ParamField path="POST /v1/gate/services" type="endpoint">
  Create a Gate service for the authenticated organization. Requires `Authorization: Bearer sk_...` plus the `gate:services:manage` scope.
</ParamField>

<ParamField path="GET /v1/gate/services/:serviceId" type="endpoint">
  Load one organization-owned Gate service. Requires `Authorization: Bearer sk_...` plus the `gate:services:read` scope.
</ParamField>

<ParamField path="PATCH /v1/gate/services/:serviceId" type="endpoint">
  Partially update an organization-owned Gate service. Requires `Authorization: Bearer sk_...` plus the `gate:services:manage` scope.
</ParamField>

<ParamField path="DELETE /v1/gate/services/:serviceId" type="endpoint">
  Soft-disable an organization-owned Gate service by setting `status = "disabled"`. Requires `Authorization: Bearer sk_...` plus the `gate:services:manage` scope.
</ParamField>

Service IDs are the canonical public slugs used by `npx signup <service>`. They must be `3-32` characters, lowercase, and use only letters, numbers, dashes, or underscores. Each organization may have at most `5` Gate services total.

***

### Signup sessions

<ParamField path="POST /v1/gate/sessions" type="endpoint">
  Create a new signup session. Called by the CLI.
</ParamField>

**Request body:**

| Field                 | Type   | Required | Description                                            |
| --------------------- | ------ | -------- | ------------------------------------------------------ |
| `service_id`          | string | Yes      | Service ID from `GET /v1/gate/registry`                |
| `account_name`        | string | Yes      | Account name (1-100 characters)                        |
| `metadata`            | object | No       | Custom key-value pairs (max 5 keys, 4KB)               |
| `delivery.version`    | number | Yes      | Must be `1`                                            |
| `delivery.algorithm`  | string | Yes      | Must be `x25519-hkdf-sha256/aes-256-gcm`               |
| `delivery.key_id`     | string | Yes      | Base64url SHA-256 fingerprint of `delivery.public_key` |
| `delivery.public_key` | string | Yes      | Base64url raw X25519 public key generated by the CLI   |

**Response (201):**

```json theme={"dark"}
{
  "data": {
    "object": "gate_session",
    "id": "gate_abc123...",
    "status": "pending",
    "poll_token": "gtpoll_...",
    "consent_url": "https://usefoil.com/gate?session=gate_abc123...",
    "expires_at": "2026-03-31T12:15:00.000Z"
  }
}
```

The `poll_token` is returned once and used to poll for the session result.

***

<ParamField path="GET /v1/gate/sessions/:gateSessionId" type="endpoint">
  Poll a session's status. Requires `Authorization: Bearer gtpoll_...`.
</ParamField>

**Response when pending:**

```json theme={"dark"}
{
  "data": {
    "object": "gate_session",
    "id": "gate_abc123...",
    "status": "pending",
    "expires_at": "2026-03-31T12:15:00.000Z"
  }
}
```

**Response when approved:**

```json theme={"dark"}
{
  "data": {
    "object": "gate_session",
    "id": "gate_abc123...",
    "status": "approved",
    "gate_account_id": "gacct_...",
    "account_name": "my-project",
    "delivery_bundle": {
      "integrator": {
        "version": 1,
        "algorithm": "x25519-hkdf-sha256/aes-256-gcm",
        "key_id": "<client key id>",
        "ephemeral_public_key": "<base64url>",
        "salt": "<base64url>",
        "iv": "<base64url>",
        "ciphertext": "<base64url>",
        "tag": "<base64url>"
      },
      "gate": {
        "version": 1,
        "algorithm": "x25519-hkdf-sha256/aes-256-gcm",
        "key_id": "<client key id>",
        "ephemeral_public_key": "<base64url>",
        "salt": "<base64url>",
        "iv": "<base64url>",
        "ciphertext": "<base64url>",
        "tag": "<base64url>"
      }
    },
    "docs_url": "https://..."
  }
}
```

`docs_url` comes from the registry entry for the session's `service_id`, not from the provisioning webhook response.

<Note>
  Approved sessions return encrypted delivery envelopes only. The same immutable ciphertext bundle can be re-polled until the CLI acknowledges receipt. After a successful ack, or after the 24-hour delivery TTL expires, Gate purges the stored envelopes and refuses redelivery.
</Note>

***

<ParamField path="POST /v1/gate/sessions/:gateSessionId/ack" type="endpoint">
  Acknowledge successful local decryption and receipt of the encrypted delivery bundle. Requires `Authorization: Bearer gtpoll_...`.
</ParamField>

**Request body:**

| Field       | Type   | Required | Description                                                  |
| ----------- | ------ | -------- | ------------------------------------------------------------ |
| `ack_token` | string | Yes      | Receipt token found inside the decrypted Gate-owned envelope |

**Response (200):**

```json theme={"dark"}
{
  "data": {
    "object": "gate_session_delivery",
    "gate_session_id": "gate_abc123...",
    "status": "acknowledged"
  }
}
```

***

<ParamField path="GET /v1/gate/sessions/:gateSessionId/consent" type="endpoint">
  Get consent page data for rendering. Called by the hosted consent page.
</ParamField>

***

<ParamField path="POST /v1/gate/sessions/:gateSessionId/approve" type="endpoint">
  Submit consent approval. Called by the consent page after the user clicks Approve.
</ParamField>

**Request body:**

| Field           | Type   | Description                                   |
| --------------- | ------ | --------------------------------------------- |
| `approve_token` | string | One-time token from the consent data endpoint |
| `sealed_token`  | string | Foil sealed scoring token (required)          |
| `session_id`    | string | Foil session ID (optional)                    |

***

<ParamField path="POST /v1/gate/sessions/:gateSessionId/cancel" type="endpoint">
  Cancel a pending session. Called via `sendBeacon` when the consent page is closed.
</ParamField>

***

### Agent tokens

Agent tokens (`agt_` prefix) are Gate-owned login credentials issued only when a service enables `dashboard_login_url`. The CLI stores them locally under the derived env key `<SERVICE_ID>_GATE_AGENT_TOKEN`, and businesses can verify or revoke them with their own `sk_*` secret key.

<ParamField path="POST /v1/gate/agent-tokens/verify" type="endpoint">
  Verify an agent token. Requires `Authorization: Bearer sk_...` plus the `gate:agent_tokens:verify` scope.
</ParamField>

**Request body:**

```json theme={"dark"}
{
  "agent_token": "agt_..."
}
```

**Response (valid):**

```json theme={"dark"}
{
  "data": {
    "valid": true,
    "gate_account_id": "gacct_...",
    "status": "active",
    "created_at": "2026-03-31T...",
    "expires_at": "2026-06-29T..."
  }
}
```

**Response (invalid/revoked):**

```json theme={"dark"}
{
  "data": {
    "valid": false
  }
}
```

Returns `Cache-Control: no-store`.

***

<ParamField path="POST /v1/gate/agent-tokens/revoke" type="endpoint">
  Revoke an agent token. Requires `Authorization: Bearer sk_...` plus the `gate:agent_tokens:revoke` scope.
</ParamField>

**Request body:**

```json theme={"dark"}
{
  "agent_token": "agt_..."
}
```

Returns `204 No Content` on success.

***

### Dashboard login sessions

Dashboard login always requires a fresh Gate approval flow and is available only for services that define `dashboard_login_url`.

<ParamField path="POST /v1/gate/login-sessions" type="endpoint">
  Create a new dashboard login session. Requires `Authorization: Bearer agt_...`.
</ParamField>

**Request body:**

```json theme={"dark"}
{
  "service_id": "foil"
}
```

**Response (201):**

```json theme={"dark"}
{
  "data": {
    "object": "gate_login_session",
    "id": "gate_abc123...",
    "status": "pending",
    "consent_url": "https://usefoil.com/gate?session=gate_abc123...",
    "expires_at": "2026-03-31T12:20:00.000Z"
  }
}
```

After the user approves the hosted Gate page, `POST /v1/gate/sessions/:gateSessionId/approve` returns both:

* `code`: a one-time dashboard login token for CLI callback handoff
* `redirect_url`: the browser redirect to your dashboard's `/auth/gate?code=...` route

***

<ParamField path="POST /v1/gate/login-sessions/consume" type="endpoint">
  Verify and consume a short-lived dashboard login token. Called by the company's dashboard. One-time use. Requires `Authorization: Bearer sk_...` plus the `gate:login_sessions:consume` scope.
</ParamField>

**Request body:**

```json theme={"dark"}
{
  "code": "..."
}
```

**Response:**

```json theme={"dark"}
{
  "data": {
    "object": "gate_dashboard_login",
    "gate_account_id": "gacct_...",
    "account_name": "my-project"
  }
}
```

## Rate limits

| Endpoint                               | Limit   | Key                       |
| -------------------------------------- | ------- | ------------------------- |
| `POST /v1/gate/sessions`               | 5/min   | IP address                |
| `GET /v1/gate/sessions/:id`            | 30/min  | Poll token                |
| `GET /v1/gate/sessions/:id/consent`    | 30/min  | IP address                |
| `POST /v1/gate/sessions/:id/approve`   | 3 total | Trusted IP + session ID   |
| `POST /v1/gate/sessions/:id/ack`       | 20/min  | Poll token                |
| `POST /v1/gate/login-sessions`         | 10/min  | Agent token               |
| `POST /v1/gate/agent-tokens/verify`    | 60/min  | Organization + token      |
| `POST /v1/gate/agent-tokens/revoke`    | 20/min  | Organization + token      |
| `POST /v1/gate/login-sessions/consume` | 30/min  | Organization + login code |

## Error codes

| Code                            | Description                                                         |
| ------------------------------- | ------------------------------------------------------------------- |
| `gate.session_not_found`        | No gate session found for the given ID                              |
| `gate.session_expired`          | The session has expired (15-minute TTL)                             |
| `gate.session_already_resolved` | The session was already approved, rejected, or expired              |
| `gate.approval_in_progress`     | The signup is still being finalized; retry shortly                  |
| `gate.bot_detected`             | Foil flagged the session as automated                               |
| `gate.approve_token_invalid`    | The approval token is missing or incorrect                          |
| `gate.foil_proof_required`      | The approval request omitted the required Foil proof                |
| `gate.foil_proof_invalid`       | The Foil proof was invalid or expired                               |
| `gate.delivery_consumed`        | The approved credentials were already delivered once                |
| `gate.delivery_ack_invalid`     | The encrypted delivery acknowledgement token was missing or invalid |
| `gate.rate_limit`               | Too many requests                                                   |
| `gate.validation_failed`        | Invalid request body                                                |
