> For a complete page index of the Captain API documentation, fetch https://docs.captain.dev/llms.txt?excludeSpec=true

# Create Key

POST https://acme.captain.dev/v1/keys
Content-Type: application/json

Mint a key. A write key for every index mints search, scoped, write and issuer keys. An issuer key mints scoped keys only, for its own indexes, with an `expires_at` within its `max_ttl`, and its own filter added. Any other key gets 403. The secret is in this reply only.

Reference: https://docs.captain.dev/reference/product-search/keys/create-key

## Authentication

- `Authorization` header (bearer token, required) — A write key, issued by Captain. Server side only. Sent as Authorization: Bearer \<key>.
- `Authorization` header (bearer token, required) — An issuer key, issued by Captain. Server side only. It only mints scoped keys. Sent as Authorization: Bearer \<key>.

## Request

### Body (application/json)

This endpoint expects a KeyCreate.

- `indexes` (list of string, required) — index names, or ["*"] for every index, now and later. A scoped key names its indexes
- `type` (enum, optional, default: search) — search (default): search, typeahead, browse, similar, Get Records, events and analytics on its indexes. scoped: search, typeahead, browse, similar, Get Records and events on its indexes, with an optional filter added to every query and usually an expiry; no analytics. issuer: a server-side key that only mints scoped keys within its indexes, filter and max_ttl; it cannot search, and revoking it revokes every key it minted. write: a server-side key for its indexes: add, update and delete records, change settings (all but acl, tenant_field and audit), synonyms, rules and aliases, list and delete indexes, import from Algolia, and read jobs and usage. It cannot search or read analytics. A write key for every index (["*"]) also creates and copies indexes, and creates, lists and revokes keys. console: Captain's own; a write key that asks for one gets 400. An issuer key may only ask for scoped.
  - Allowed values: `search`, `scoped`, `issuer`, `console`, `write`
- `principals` (list of string, optional) — scoped keys only: the users, groups or roles this key acts for (record access control)
- `filter_expr` (string, optional) — scoped keys only: a filter expression ANDed into every query
- `max_ttl` (integer, optional, default: 3600) — issuer keys: the longest expiry it may give
- `filter` (KeyCreateFilter, optional) — scoped keys only: ANDed into every query; a request can narrow it, never widen it. Same syntax as the search filters. Query understanding (typo correction, completion) learns only from the records it matches.
- `expires_at` (integer, optional) — unix seconds
- `allowed_origins` (list of string, optional) — browser origins allowed to use the key; empty = any
- `description` (string, optional)
- `rate_limit` (double, optional, deprecated) — accepted and ignored: no key is rate limited

## Response

### 201

created; the key is in this response only

- `id` (string, optional)
- `type` (enum, optional)
  - Allowed values: `search`, `scoped`, `issuer`, `console`, `write`
- `prefix` (string, optional)
- `operator` (string, optional) — minted by a console key: the person it was minted for, fixed. Its events record it, and a request with it may not send X-Captain-Operator
- `principals` (list of string, optional)
- `filter_expr` (string, optional)
- `max_ttl` (integer, optional)
- `issued_by` (string, optional)
- `issued_by_captain` (boolean, optional) — in a write key's view: a key Captain issued (a console or support key). Its operator and description are left out, and only Captain revokes it
- `indexes` (list of string, optional)
- `filter` (KeyFilter, optional, nullable)
- `expires_at` (integer, optional, nullable)
- `allowed_origins` (list of string, optional)
- `description` (string, optional)
- `created_at` (integer, optional)
- `key` (string, optional) — only in the response to POST /v1/keys

## Types

### KeyCreateFilter

scoped keys only: ANDed into every query; a request can narrow it, never widen it. Same syntax as the search filters. Query understanding (typo correction, completion) learns only from the records it matches.

### KeyFilter

## Examples

### Mint a search key for staging

**Request**

```json
{
  "indexes": [
    "products-staging"
  ],
  "type": "search",
  "description": "staging storefront"
}
```

**Response**

```json
{
  "id": "key_8a1f3c0d9e2b4f67",
  "type": "search",
  "prefix": "cap_k3Jd9x",
  "indexes": [
    "products-staging"
  ],
  "description": "staging storefront",
  "created_at": 1759433001,
  "key": "cap_k3Jd9xEXAMPLEONLY",
  "created_by": "key_2c7e90a1b4d35f18",
  "note": "store this key now: it is shown once and kept only as a hash"
}
```

**SDK Code**

```python Python REST
import json
import requests

BASE_URL = "https://acme.captain.dev"
API_KEY = "your_write_key"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

response = requests.post(
    f"{BASE_URL}/v1/keys",
    headers=headers,
    json={
        "type": "search",
        "indexes": ["products-staging"],
        "description": "staging storefront",
    },
    timeout=30.0,
)

print(json.dumps(response.json(), indent=2))
```

```typescript TypeScript REST
const BASE_URL = "https://acme.captain.dev";
const API_KEY = "your_write_key";

const response = await fetch(`${BASE_URL}/v1/keys`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "search",
    indexes: ["products-staging"],
    description: "staging storefront",
  }),
});

const result = await response.json();
console.log(JSON.stringify(result, null, 2));
```

### Mint a scoped key with an issuer key

**Request**

```json
{
  "indexes": [
    "products"
  ],
  "type": "scoped",
  "filter": {
    "status": "published"
  },
  "expires_at": 1759440600,
  "description": "storefront session"
}
```

**Response**

```json
{
  "id": "key_5d2e8f1a7c3b9e40",
  "type": "scoped",
  "prefix": "cap_Zp4m1Q",
  "issued_by": "key_7b9a2c4e6d1f3a58",
  "indexes": [
    "products"
  ],
  "filter": {
    "status": "published"
  },
  "expires_at": 1759440600,
  "allowed_origins": [
    "https://www.example.com"
  ],
  "description": "storefront session",
  "created_at": 1759437001,
  "key": "cap_Zp4m1QEXAMPLEONLY",
  "note": "store this key now: it is shown once and kept only as a hash"
}
```

**SDK Code**

```python Python REST
import json
import requests
import time

BASE_URL = "https://acme.captain.dev"
API_KEY = "your_issuer_key"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

response = requests.post(
    f"{BASE_URL}/v1/keys",
    headers=headers,
    json={
        "type": "scoped",
        "indexes": ["products"],
        "filter": {"status": "published"},
        "expires_at": int(time.time()) + 3600,
        "description": "storefront session",
    },
    timeout=30.0,
)

print(json.dumps(response.json(), indent=2))
```

```typescript TypeScript REST
const BASE_URL = "https://acme.captain.dev";
const API_KEY = "your_issuer_key";

const response = await fetch(`${BASE_URL}/v1/keys`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    type: "scoped",
    indexes: ["products"],
    filter: { status: "published" },
    expires_at: Math.floor(Date.now() / 1000) + 3600,
    description: "storefront session",
  }),
});

const result = await response.json();
console.log(JSON.stringify(result, null, 2));
```