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

# Admin API documentation

> Guide to API key, billing, and container routes available to organization admin API tokens

## Introduction

The Admin API lets you manage regular organization API keys, inspect organization billing data, and access container-related APIs with an admin API key. Admin API keys are for organizations, not personal accounts.

### Common use cases

* **Automating key management**: Create, rotate, or delete API keys programmatically, and set expiration dates or token limits based on your business logic.
* **Building custom dashboards**: Display usage metrics, cost breakdowns by model, and historical trends using the billing and time-series endpoints.
* **Monitoring usage**: Query aggregated or per-key usage statistics to track costs and token consumption.
* **Managing containers programmatically**: Create, validate, update, stop, start, and delete containers. Inspect and update deployments, and manage org secrets, SSH keys, custom domains, and registry credentials.

<Tip>
  If you need per-user usage metrics for billing, the recommended approach is to run a [proxy server](/guides/proxy-server#usage-metrics-for-billing) that tracks token counts via response headers — rather than creating a separate API key per user in your system.
</Tip>

### Authentication

Admin API keys provide programmatic access to your account resources. Admin keys are prefixed with `admin_` and must be included in the Authorization header as a Bearer token.

<Info>
  Need to create an admin API key? Follow our step-by-step guide: [Getting a Tinfoil Admin Key](/admin/get-admin-key)
</Info>

<Warning>
  Admin keys provide programmatic access to organization settings, including managing API keys, reading billing data, and administering container resources.
  Do not share admin keys or expose them in browsers, client-side code, or public repositories.
  Revoke admin keys when team members with admin access leave your organization.
</Warning>

```bash theme={"dark"}
Authorization: Bearer YOUR_ADMIN_KEY
```

### Available endpoints

This page covers the following endpoints:

#### API key management

* `GET /api/keys` - List API keys
* `POST /api/keys` - Create a new API key
* `POST /api/keys/update` - Update an API key name or token cap
* `DELETE /api/keys/:key` - Delete an API key

#### Billing & usage

* `GET /api/billing/usage` - Get aggregated usage statistics for all keys
* `POST /api/billing/usage/key` - Get usage statistics for a specific key
* `GET /api/billing/time-series` - Get time series data
* `GET /api/billing/transactions` - Get transaction history

#### Container endpoints

* **Lifecycle & deployment**
  * `POST /api/containers/validate-name` - Validate a container name or custom domain before deploy
  * `POST /api/containers/validate` - Validate `tinfoil-config.yml` before deploy
  * `GET /api/containers/hosts` - List hosts available to the organization
  * `GET /api/containers` - List containers
  * `GET /api/containers/:id` - Get a specific container
  * `POST /api/containers` - Create a container
  * `POST /api/containers/:id/relaunch` - Relaunch a running or failed container
  * `POST /api/containers/:id/stop` - Stop a running container
  * `POST /api/containers/:id/start` - Start a stopped container
  * `DELETE /api/containers/:id` - Delete a container
  * `GET /api/containers/:id/update` - Get in-progress update status
  * `POST /api/containers/:id/update/accept` - Promote a ready staged update
  * `POST /api/containers/:id/update/cancel` - Cancel an in-progress update
  * `POST /api/containers/:id/github-connection` - Toggle GitHub App connection
  * `GET /api/containers/:id/metrics` - Get resource metrics
* **Deployments**
  * `GET /api/deployments` - List deployments with instance counts
  * `PATCH /api/deployments/:id` - Update deployment settings
  * `POST /api/deployments/:id/update` - Update all or selected instances
* **Related resources**
  * `GET /api/secrets` - List org secrets
  * `POST /api/secrets` - Create a secret
  * `GET /api/secrets/:name` - Get a secret's metadata
  * `PUT /api/secrets/:name` - Update a secret
  * `DELETE /api/secrets/:name` - Delete a secret
  * `GET /api/ssh-keys` - List org SSH keys
  * `POST /api/ssh-keys` - Create an SSH key
  * `DELETE /api/ssh-keys/:name` - Delete an SSH key
  * `GET /api/domains` - List custom domains
  * `POST /api/domains` - Add a domain
  * `POST /api/domains/:domain/verify` - Verify a domain
  * `DELETE /api/domains/:domain` - Delete a domain
  * `GET /api/registry-credentials` - List private registry credential status
  * `PUT /api/registry-credentials/:registry` - Create or update private registry credentials
  * `DELETE /api/registry-credentials/:registry` - Delete private registry credentials

***

## API Key Management

### List API Keys

<Card>
  <ParamField header="GET /api/keys" type="endpoint">
    Returns all regular (non-admin) API keys in your organization. Keys you created are returned in full. Keys created by other members are masked (for example, `tk_12345***`).
  </ParamField>
</Card>

#### Example Request

```bash theme={"dark"}
curl "https://api.tinfoil.sh/api/keys"   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Response

```json theme={"dark"}
[
  {
    "key": "tk_your_full_key_value_here",
    "name": "Production Key",
    "disabled": false,
    "expires_at": "2026-12-31T23:59:59Z",
    "max_tokens": 1000000,
    "metadata": {
      "environment": "production"
    },
    "tokens_used": 450000,
    "input_tokens_used": 250000,
    "output_tokens_used": 200000,
    "request_count": 1200,
    "is_owner": true,
    "created_at": "2026-04-01T00:00:00Z",
    "last_used_at": "2026-04-09T12:34:56Z"
  },
  {
    "key": "tk_abcde**************************",
    "name": "Staging Key",
    "disabled": false,
    "expires_at": null,
    "max_tokens": null,
    "metadata": {},
    "tokens_used": 0,
    "input_tokens_used": 0,
    "output_tokens_used": 0,
    "request_count": 0,
    "is_owner": false,
    "created_at": "2026-04-02T00:00:00Z",
    "last_used_at": "2026-04-08T09:00:00Z"
  }
]
```

### Create API Key

<Card>
  <ParamField header="POST /api/keys" type="endpoint">
    Creates a new regular API key for your organization. Requires active token billing for the organization.
  </ParamField>
</Card>

#### Request Body

<ParamField body="name" type="string" required>
  Name for the API key. Must contain only alphanumeric characters, hyphens, underscores, spaces, and periods.
</ParamField>

<ParamField body="expires_at" type="datetime" optional>
  ISO 8601 timestamp when the key should expire. If not provided, the key doesn't expire.
</ParamField>

<ParamField body="max_tokens" type="integer" optional>
  Maximum number of tokens this key can use. If not provided, no limit is enforced.
</ParamField>

<ParamField body="metadata" type="object" optional>
  Custom metadata to attach to the key. Maximum size: 5KB.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X POST https://api.tinfoil.sh/api/keys   -H "Authorization: Bearer YOUR_ADMIN_KEY"   -H "Content-Type: application/json"   -d '{
    "name": "Production API Key",
    "expires_at": "2026-12-31T23:59:59Z",
    "max_tokens": 1000000,
    "metadata": {
      "environment": "production",
      "team": "backend"
    }
  }'
```

#### Response

```json theme={"dark"}
{
  "key": "tk_your_new_key_value_here",
  "name": "Production API Key",
  "disabled": false,
  "expires_at": "2026-12-31T23:59:59Z",
  "max_tokens": 1000000,
  "metadata": {
    "environment": "production",
    "team": "backend"
  },
  "tokens_used": 0,
  "input_tokens_used": 0,
  "output_tokens_used": 0,
  "request_count": 0,
  "is_owner": true,
  "created_at": "2026-04-09T00:00:00Z",
  "last_used_at": null
}
```

### Update API Key

<Card>
  <ParamField header="POST /api/keys/update" type="endpoint">
    Updates an existing regular API key in your organization. You can update any non-admin key in the organization, provided you have the full key value.
  </ParamField>
</Card>

#### Request Body

<ParamField body="key" type="string" required>
  The API key value to update, as returned by `GET /api/keys`.
</ParamField>

<ParamField body="name" type="string" optional>
  New display name for the API key.
</ParamField>

<ParamField body="max_tokens" type="integer" optional>
  New token cap for the key. Set this to `0` to clear the existing cap.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X POST https://api.tinfoil.sh/api/keys/update   -H "Authorization: Bearer YOUR_ADMIN_KEY"   -H "Content-Type: application/json"   -d '{
    "key": "tk_your_full_key_value_here",
    "name": "Staging API Key",
    "max_tokens": 0
  }'
```

#### Response

```json theme={"dark"}
{
  "message": "API key updated"
}
```

### Delete API Key

<Card>
  <ParamField header="DELETE /api/keys/:key" type="endpoint">
    Deletes a regular API key in your organization. You can delete any non-admin key, provided you have the full key value.
  </ParamField>
</Card>

#### Path Parameters

<ParamField path="key" type="string" required>
  The API key value to delete (for example, `tk_your_full_key_value_here`).
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X DELETE https://api.tinfoil.sh/api/keys/tk_your_full_key_value_here   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Response

```json theme={"dark"}
{
  "message": "API key deleted"
}
```

***

## Billing & Usage

### Get Usage Statistics

<Card>
  <ParamField header="GET /api/billing/usage" type="endpoint">
    Retrieves aggregated usage statistics for your organization for the specified time period.
  </ParamField>
</Card>

#### Query Parameters

<ParamField query="time" type="string" optional>
  Time period for usage statistics. If omitted, returns all-time usage. Valid values:

  * `5m` - Last 5 minutes
  * `15m` - Last 15 minutes
  * `30m` - Last 30 minutes
  * `1h` - Last hour
  * `24h` - Last 24 hours
  * `7d` - Last 7 days
  * `30d` - Last 30 days
  * `60d` - Last 60 days
  * `90d` - Last 90 days
</ParamField>

#### Example Request

```bash theme={"dark"}
curl "https://api.tinfoil.sh/api/billing/usage?time=7d"   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Response

```json theme={"dark"}
{
  "tokens": 1500000,
  "input_tokens": 800000,
  "output_tokens": 700000,
  "requests": 3200,
  "cost": 15.00,
  "keys": {
    "Production Key": {
      "total_tokens": 1000000,
      "total_input_tokens": 600000,
      "total_output_tokens": 400000,
      "total_requests": 2000,
      "cost": 10.00,
      "models": {
        "kimi-k2-5": {
          "tokens": 600000,
          "input_tokens": 350000,
          "output_tokens": 250000,
          "requests": 1200,
          "cost": 6.00
        },
        "gpt-oss-120b": {
          "tokens": 400000,
          "input_tokens": 250000,
          "output_tokens": 150000,
          "requests": 800,
          "cost": 4.00
        }
      }
    },
    "Development Key": {
      "total_tokens": 500000,
      "total_input_tokens": 200000,
      "total_output_tokens": 300000,
      "total_requests": 1200,
      "cost": 5.00,
      "models": {
        "llama3-3-70b": {
          "tokens": 500000,
          "input_tokens": 200000,
          "output_tokens": 300000,
          "requests": 1200,
          "cost": 5.00
        }
      }
    }
  }
}
```

The `keys` object is grouped by API key name.

### Get Usage by Key

<Card>
  <ParamField header="POST /api/billing/usage/key" type="endpoint">
    Retrieves usage statistics for a specific regular API key in your organization.
  </ParamField>
</Card>

#### Query Parameters

<ParamField query="time" type="string" optional>
  Time period for usage statistics. If omitted, returns all-time usage. Valid values:

  * `5m` - Last 5 minutes
  * `15m` - Last 15 minutes
  * `30m` - Last 30 minutes
  * `1h` - Last hour
  * `24h` - Last 24 hours
  * `7d` - Last 7 days
  * `30d` - Last 30 days
  * `60d` - Last 60 days
  * `90d` - Last 90 days
</ParamField>

#### Request Body

<ParamField body="key" type="string" required>
  The API key value to query (for example, `tk_your_full_key_value_here`), as returned by `GET /api/keys`.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X POST "https://api.tinfoil.sh/api/billing/usage/key?time=7d"   -H "Authorization: Bearer YOUR_ADMIN_KEY"   -H "Content-Type: application/json"   -d '{"key": "tk_your_full_key_value_here"}'
```

#### Response

```json theme={"dark"}
{
  "prompt_tokens": 800000,
  "completion_tokens": 700000,
  "requests": 3200,
  "cost": 15.00
}
```

### Get Time Series Data

<Card>
  <ParamField header="GET /api/billing/time-series" type="endpoint">
    Retrieves time-series usage data for your organization over the specified period.
  </ParamField>
</Card>

#### Query Parameters

<ParamField query="time" type="string" default="24h">
  Time period for the time series. Valid values:

  * `5m` - Last 5 minutes
  * `15m` - Last 15 minutes
  * `30m` - Last 30 minutes
  * `1h` - Last hour
  * `24h` - Last 24 hours
  * `7d` - Last 7 days
  * `30d` - Last 30 days
  * `60d` - Last 60 days
  * `90d` - Last 90 days
</ParamField>

The response interval is fixed per window: `5m` → `5s`, `15m` → `15s`, `30m` → `30s`, `1h` → `1m`, `24h` → `15m`, `7d` → `2h`, `30d` → `8h`, and `60d`/`90d` → `24h`. Empty buckets are included as zero-value data points.

#### Example Request

```bash theme={"dark"}
curl "https://api.tinfoil.sh/api/billing/time-series?time=24h"   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Response

```json theme={"dark"}
{
  "data_points": [
    {
      "time": "2026-04-08T12:00:00Z",
      "tokens": 50000,
      "input_tokens": 30000,
      "output_tokens": 20000,
      "requests": 100,
      "models": {
        "kimi-k2-5": {
          "tokens": 30000,
          "input_tokens": 18000,
          "output_tokens": 12000,
          "requests": 60
        },
        "llama3-3-70b": {
          "tokens": 20000,
          "input_tokens": 12000,
          "output_tokens": 8000,
          "requests": 40
        }
      }
    },
    {
      "time": "2026-04-08T12:15:00Z",
      "tokens": 75000,
      "input_tokens": 45000,
      "output_tokens": 30000,
      "requests": 150,
      "models": {
        "kimi-k2-5": {
          "tokens": 75000,
          "input_tokens": 45000,
          "output_tokens": 30000,
          "requests": 150
        }
      }
    }
  ],
  "interval": "15m0s"
}
```

### Get Transaction History

<Card>
  <ParamField header="GET /api/billing/transactions" type="endpoint">
    Retrieves invoice and standalone charge history for your organization.
  </ParamField>
</Card>

Viewing transaction history requires organization admin access. If the organization does not have a Stripe customer yet, the response is:

```json theme={"dark"}
{
  "transactions": []
}
```

#### Example Request

```bash theme={"dark"}
curl "https://api.tinfoil.sh/api/billing/transactions"   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Response

```json theme={"dark"}
{
  "transactions": [
    {
      "id": "in_1234567890",
      "date": "2024-01-01T00:00:00Z",
      "type": "Invoice",
      "description": "Monthly subscription",
      "amount": 99.00,
      "status": "completed",
      "invoice_url": "https://invoice.stripe.com/i/..."
    },
    {
      "id": "ch_0987654321",
      "date": "2024-01-15T12:30:00Z",
      "type": "Charge",
      "description": "API Usage",
      "amount": 25.50,
      "status": "completed"
    }
  ]
}
```

***

## Containers

Admin API keys can access the same container APIs as a browser session, as long as the key belongs to the target organization.

<Info>
  Create, start, and relaunch operations require an active container subscription. Read-only endpoints and cleanup operations such as list, get, stop, and delete do not require an active subscription.
</Info>

<Info>
  Private registry endpoints require private registry access to be enabled for the organization.
</Info>

### Lifecycle & Deployment

#### Validate Container Name

<Card>
  <ParamField header="POST /api/containers/validate-name" type="endpoint">
    Checks whether a container name is valid and available for the current organization. You can also validate a custom domain before creating or relaunching a container.
  </ParamField>
</Card>

#### Request Body

<ParamField body="name" type="string" required>
  Container name. Must be lowercase alphanumeric with hyphens, max 64 characters.
</ParamField>

<ParamField body="debug" type="boolean" optional>
  Whether to validate the name for debug mode.
</ParamField>

<ParamField body="custom_domain" type="string" optional>
  Custom domain to validate.
</ParamField>

<ParamField body="relaunch_container_id" type="string" optional>
  Existing container UUID when validating a relaunch that keeps the same custom domain.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X POST "https://api.tinfoil.sh/api/containers/validate-name"   -H "Authorization: Bearer YOUR_ADMIN_KEY"   -H "Content-Type: application/json"   -d '{
    "name": "my-app",
    "debug": false,
    "custom_domain": "api.example.com"
  }'
```

#### Response

```json theme={"dark"}
{
  "available": true,
  "name": "my-app"
}
```

#### Validate Container Config

<Card>
  <ParamField header="POST /api/containers/validate" type="endpoint">
    Validates the `tinfoil-config.yml` in a repository tag before create, replace, or relaunch.
  </ParamField>
</Card>

#### Request Body

<ParamField body="repo" type="string" required>
  GitHub repository in `owner/repo` format.
</ParamField>

<ParamField body="tag" type="string" required>
  Git tag to validate.
</ParamField>

<ParamField body="relaunch_container_id" type="string" optional>
  Existing container UUID. When present, instance-limit checks are skipped for relaunch validation.
</ParamField>

<ParamField body="replace_container_id" type="string" optional>
  Existing container UUID. When present, instance-limit checks are skipped for replace validation.
</ParamField>

#### List Hosts

<Card>
  <ParamField header="GET /api/containers/hosts" type="endpoint">
    Returns the container hosts available to the organization, including the default host and the GPU values available on each host.
  </ParamField>
</Card>

#### Response

```json theme={"dark"}
[
  {
    "name": "default-host",
    "is_default": true,
    "available_gpu_values": [1, 2, 4]
  }
]
```

#### List Containers

<Card>
  <ParamField header="GET /api/containers" type="endpoint">
    Returns all containers in your organization. Responses may also include `ssh_port`, `host_name`, `host_gpu_type`, and `host_cpu_type` when available.
  </ParamField>
</Card>

#### Example Request

```bash theme={"dark"}
curl "https://api.tinfoil.sh/api/containers"   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Response

```json theme={"dark"}
[
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "deployment_id": "7f8a4b3e-63c1-4bde-899b-36a35dfb1fc2",
    "name": "my-app",
    "repo": "myorg/my-app",
    "status": "ready",
    "domain": "my-app.myorg.tinfoil.dev",
    "current_tag": "v1.2.0",
    "cpus": 2,
    "gpus": 0,
    "memory_mb": 4096,
    "debug": false,
    "created_at": "2026-04-01T00:00:00Z"
  }
]
```

#### Get Container

<Card>
  <ParamField header="GET /api/containers/:id" type="endpoint">
    Returns details for a specific container.
  </ParamField>
</Card>

#### Path Parameters

<ParamField path="id" type="string" required>
  The container UUID.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl "https://api.tinfoil.sh/api/containers/550e8400-e29b-41d4-a716-446655440000"   -H "Authorization: Bearer YOUR_ADMIN_KEY"
```

#### Create Container

<Card>
  <ParamField header="POST /api/containers" type="endpoint">
    Creates and deploys a new container. The repository must contain a `tinfoil-config.yml` at the specified tag.
  </ParamField>
</Card>

#### Request Body

<ParamField body="name" type="string" required>
  Container name. Must be lowercase alphanumeric with hyphens.
</ParamField>

<ParamField body="repo" type="string" required>
  GitHub repository in `owner/repo` format.
</ParamField>

<ParamField body="tag" type="string" required>
  Git tag to deploy. The tag must have a published GitHub release.
</ParamField>

<ParamField body="variables" type="object" optional>
  Environment variables as key-value pairs.
</ParamField>

<ParamField body="secrets" type="string[]" optional>
  Names of existing org secrets to inject.
</ParamField>

<ParamField body="ssh_keys" type="string[]" optional>
  Names of existing org SSH keys to inject.
</ParamField>

<ParamField body="debug" type="boolean" optional>
  Enable debug mode.
</ParamField>

<ParamField body="gpus" type="integer" optional>
  Number of GPUs to allocate. Requires GPU access for the organization.
</ParamField>

<ParamField body="custom_domain" type="string" optional>
  Verified custom domain for the container.
</ParamField>

<ParamField body="host_name" type="string" optional>
  Target host name. Requires target host selection to be enabled for the organization.
</ParamField>

<ParamField body="replace_container_id" type="string" optional>
  Existing container UUID to replace.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X POST "https://api.tinfoil.sh/api/containers"   -H "Authorization: Bearer YOUR_ADMIN_KEY"   -H "Content-Type: application/json"   -d '{
    "name": "my-app",
    "repo": "myorg/my-app",
    "tag": "v1.2.0",
    "variables": {
      "LOG_LEVEL": "info"
    },
    "secrets": ["DATABASE_URL"],
    "debug": false
  }'
```

#### Response

Returns the created container object with status `deploying`. The container transitions to `ready` once the deployment completes.

#### Relaunch Container

<Card>
  <ParamField header="POST /api/containers/:id/relaunch" type="endpoint">
    Relaunches a running or failed container with a new tag, updated configuration, or both. Running single-GPU and CPU-only containers usually use a blue-green deployment flow. Multi-GPU updates use a different flow and cannot always be canceled once started.
  </ParamField>
</Card>

#### Path Parameters

<ParamField path="id" type="string" required>
  The container UUID.
</ParamField>

#### Request Body

All fields are optional. Omitted fields keep their current values.

<ParamField body="tag" type="string" optional>
  New git tag to deploy.
</ParamField>

<ParamField body="variables" type="object" optional>
  New environment variables. This replaces the full existing variable set.
</ParamField>

<ParamField body="secrets" type="string[]" optional>
  New secret-name list. This replaces the full existing secret set.
</ParamField>

<ParamField body="ssh_keys" type="string[]" optional>
  New SSH key-name list.
</ParamField>

<ParamField body="gpus" type="integer" optional>
  New GPU count.
</ParamField>

<ParamField body="debug" type="boolean" optional>
  Toggle debug mode.
</ParamField>

<ParamField body="custom_domain" type="string" optional>
  Set or clear a custom domain. Pass an empty string to revert to the auto-generated domain.
</ParamField>

#### Example Request

```bash theme={"dark"}
curl -X POST "https://api.tinfoil.sh/api/containers/550e8400-e29b-41d4-a716-446655440000/relaunch"   -H "Authorization: Bearer YOUR_ADMIN_KEY"   -H "Content-Type: application/json"   -d '{
    "tag": "v1.3.0"
  }'
```

#### Stop Container

<Card>
  <ParamField header="POST /api/containers/:id/stop" type="endpoint">
    Stops a running container. The container record is preserved and can be started again later.
  </ParamField>
</Card>

#### Start Container

<Card>
  <ParamField header="POST /api/containers/:id/start" type="endpoint">
    Starts a stopped container. You can optionally pass the same body fields as relaunch to update configuration at start time.
  </ParamField>
</Card>

#### Delete Container

<Card>
  <ParamField header="DELETE /api/containers/:id" type="endpoint">
    Permanently deletes a container and its running enclave. The deployment remains while it has other instances. Outstanding billing is finalized before deletion.
  </ParamField>
</Card>

Returns `204 No Content` on success.

#### Get Update Status

<Card>
  <ParamField header="GET /api/containers/:id/update" type="endpoint">
    Returns the status of an in-progress relaunch or restart.
  </ParamField>
</Card>

#### Response

```json theme={"dark"}
{
  "has_update": true,
  "update_deployment_id": "dep_123",
  "update_tag": "v1.3.0",
  "update_status": "deploying"
}
```

If no update is in progress:

```json theme={"dark"}
{
  "has_update": false
}
```

#### Accept Update

<Card>
  <ParamField header="POST /api/containers/:id/update/accept" type="endpoint">
    Promotes a ready staged update and returns the updated container.
  </ParamField>
</Card>

#### Cancel Update

<Card>
  <ParamField header="POST /api/containers/:id/update/cancel" type="endpoint">
    Cancels an in-progress update and returns `204 No Content`. Multi-GPU updates cannot be canceled once started.
  </ParamField>
</Card>

#### Toggle GitHub App Connection

<Card>
  <ParamField header="POST /api/containers/:id/github-connection" type="endpoint">
    Sets whether the container is connected to a GitHub App installation for its repo owner.
  </ParamField>
</Card>

#### Request Body

<ParamField body="connected" type="boolean" required>
  Whether GitHub App connectivity should be enabled.
</ParamField>

#### Get Container Metrics

<Card>
  <ParamField header="GET /api/containers/:id/metrics" type="endpoint">
    Returns CPU, GPU, and memory utilization time series for a container. The `time` query parameter defaults to `24h`.
  </ParamField>
</Card>

### Deployments

A deployment groups all container instances in an organization that use the same GitHub repository.

#### List Deployments

<Card>
  <ParamField header="GET /api/deployments" type="endpoint">
    Returns deployments with aggregate instance counts and settings.
  </ParamField>
</Card>

```json theme={"dark"}
[
  {
    "id": "7f8a4b3e-63c1-4bde-899b-36a35dfb1fc2",
    "repo": "myorg/my-app",
    "default_staging": false,
    "instance_count": 2,
    "ready_count": 1,
    "failed_count": 1,
    "stopped_count": 0,
    "deploying_count": 0
  }
]
```

There is no separate `GET /api/deployments/:id` endpoint. Resolve a deployment by ID or repository from the list response.

#### Update Deployment Settings

<Card>
  <ParamField header="PATCH /api/deployments/:id" type="endpoint">
    Updates shared settings and returns the deployment.
  </ParamField>
</Card>

<ParamField body="default_staging" type="boolean" optional>
  Default staging mode for new instances and deployment-wide updates.
</ParamField>

#### Update Deployment Instances

<Card>
  <ParamField header="POST /api/deployments/:id/update" type="endpoint">
    Updates every eligible instance, or a selected set, to one repository tag.
  </ParamField>
</Card>

<ParamField body="tag" type="string" required>
  Repository release tag to deploy.
</ParamField>

<ParamField body="staging" type="boolean" optional>
  Staging override for this request. Omit it to use the deployment default.
</ParamField>

<ParamField body="instance_ids" type="string[]" optional>
  Container UUIDs to update. Omit it to update every instance.
</ParamField>

The response contains one result per targeted instance with status `updating`, `skipped`, or `failed`. Instances are skipped when they are not running or failed, or already have an update in progress.

### Related Resources

#### Secrets

Org secrets are encrypted values injected into containers at deploy time. Secret names must be `UPPER_SNAKE_CASE` or `kebab-case`.

<Card>
  <ParamField header="GET /api/secrets" type="endpoint">
    Returns all org-level secrets as metadata only. Secret values are never returned.
  </ParamField>
</Card>

<Card>
  <ParamField header="GET /api/secrets/:name" type="endpoint">
    Returns metadata for a single secret, including which containers use it.
  </ParamField>
</Card>

<Card>
  <ParamField header="POST /api/secrets" type="endpoint">
    Creates a new org secret.
  </ParamField>
</Card>

#### Request Body

<ParamField body="name" type="string" required>
  Secret name.
</ParamField>

<ParamField body="value" type="string" required>
  Secret value.
</ParamField>

<Card>
  <ParamField header="PUT /api/secrets/:name" type="endpoint">
    Updates the value of an existing org secret.
  </ParamField>
</Card>

<Card>
  <ParamField header="DELETE /api/secrets/:name" type="endpoint">
    Deletes an org secret. If the secret is currently used by any container, the API returns `409 Conflict` and includes the blocking container names.
  </ParamField>
</Card>

#### SSH Keys

SSH key names must be kebab-case, for example `my-deploy-key`.

<Card>
  <ParamField header="GET /api/ssh-keys" type="endpoint">
    Returns all org-level SSH keys.
  </ParamField>
</Card>

<Card>
  <ParamField header="POST /api/ssh-keys" type="endpoint">
    Adds a new org SSH key for debug-mode containers.
  </ParamField>
</Card>

#### Request Body

<ParamField body="name" type="string" required>
  SSH key name in kebab-case.
</ParamField>

<ParamField body="public_key" type="string" required>
  SSH public key, for example `ssh-ed25519 AAAA...`.
</ParamField>

<Card>
  <ParamField header="DELETE /api/ssh-keys/:name" type="endpoint">
    Deletes an org SSH key. If the key is currently used by any container, the API returns `409 Conflict` and includes the blocking container names.
  </ParamField>
</Card>

#### Custom Domains

<Card>
  <ParamField header="GET /api/domains" type="endpoint">
    Returns all custom domains for the organization, including verification details and which containers use each domain.
  </ParamField>
</Card>

<Card>
  <ParamField header="POST /api/domains" type="endpoint">
    Adds a custom domain for verification and returns the TXT and CNAME records required for setup.
  </ParamField>
</Card>

#### Request Body

<ParamField body="domain" type="string" required>
  Domain name to register, for example `api.example.com`.
</ParamField>

<Card>
  <ParamField header="POST /api/domains/:domain/verify" type="endpoint">
    Checks DNS records and updates the domain's verification state.
  </ParamField>
</Card>

<Card>
  <ParamField header="DELETE /api/domains/:domain" type="endpoint">
    Deletes a custom domain. If the domain is currently used by any container, the API returns `409 Conflict` and includes the blocking container names.
  </ParamField>
</Card>

#### Registry Credentials

Private registry credentials are supported for `ghcr`, `gcr`, and `dockerhub`.

<Card>
  <ParamField header="GET /api/registry-credentials" type="endpoint">
    Returns credential status for each supported registry, including whether credentials exist, whether they are expired, and when they were last updated.
  </ParamField>
</Card>

<Card>
  <ParamField header="PUT /api/registry-credentials/:registry" type="endpoint">
    Creates or updates credentials for a supported registry.
  </ParamField>
</Card>

#### Path Parameters

<ParamField path="registry" type="string" required>
  Registry identifier: `ghcr`, `gcr`, or `dockerhub`.
</ParamField>

#### Request Body

For `ghcr`:

```json theme={"dark"}
{
  "username": "myuser",
  "token": "ghp_xxxxxxxxxxxx"
}
```

For `gcr`:

```json theme={"dark"}
{
  "key": "{...service account json...}"
}
```

For `dockerhub`:

```json theme={"dark"}
{
  "username": "myuser",
  "token": "dckr_pat_xxxxxxxxxxxx"
}
```

<Card>
  <ParamField header="DELETE /api/registry-credentials/:registry" type="endpoint">
    Deletes credentials for a supported registry.
  </ParamField>
</Card>

***

### Error Responses

Example error response:

```json theme={"dark"}
{
  "error": "invalid admin API key",
  "code": "UNAUTHORIZED"
}
```

Common error codes:

| Code               | HTTP Status | Description                                                                           |
| ------------------ | ----------- | ------------------------------------------------------------------------------------- |
| `UNAUTHORIZED`     | 401         | Invalid or expired admin API key                                                      |
| `FORBIDDEN`        | 403         | Your admin key no longer has access to this organization or action                    |
| `BAD_REQUEST`      | 400         | Invalid request parameters                                                            |
| `NOT_FOUND`        | 404         | Resource not found, or the referenced API key is not available through this admin key |
| `PAYMENT_REQUIRED` | 402         | Active subscription required                                                          |
| `INTERNAL_ERROR`   | 500         | Server error                                                                          |
