# API Key

URL: /business/nouns/api-key · Id: `noun:api-key`

> An API Key is a secret a Startup issues to one Customer, which signs that Customer’s calls to its API, SDK, CLI and MCP server.

An API Key is a secret a [Startup](/business/nouns/startup) issues to one [Customer](/business/nouns/customer), which signs that Customer’s calls to its API, SDK, CLI and MCP server.

## Properties
| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | What the Customer calls it, so a list of keys reads by purpose. |
| `environment` | `string` | Yes | One of `test` or `live`: a `test` key reaches test records and moves no money. |
| `verbs` | list of [Verb](/business/nouns/verb) | No | The Verbs it may call. Unset: every Verb the Customer’s Entitlements grant. |
| `prefix` | `string` | No | The first characters of the secret, shown in place of it everywhere after issue. |
| `lastUsedAt` | `datetime` | No | When it last signed a call. |
| `expiresAt` | `datetime` | No | When it stops working on its own. Unset: it works until rolled or revoked. |
| `revokedAt` | `datetime` | No | When it was revoked. A revoked key fails its next call. |

## Object
```json
{
  "$id": "https://api.sb/petpantry.shop/api-keys/api_key_1",
  "$type": "APIKey",
  "name": "Nightly sync",
  "environment": "test",
  "verbs": [
    "verb_1"
  ],
  "prefix": "sk_live_4f2a",
  "lastUsedAt": "2026-10-22T12:00:00Z",
  "expiresAt": "2026-10-22T12:00:00Z",
  "revokedAt": "2026-10-22T12:00:00Z"
}
```

## Relationships
- <a id="rel-reaches"></a>An API Key **reaches** one [Tenant](/business/nouns/tenant): every call it signs reads and writes only that [Customer](/business/nouns/customer)’s records. `reaches`, inverse [`apiKeys`](/business/nouns/tenant#rel-api-keys).

## Verbs
| Verb | Description | Subject | Event | Class |
| --- | --- | --- | --- | --- |
| [APIKey.issue](/business/nouns/api-key/issue) | Issue creates an API Key for one Customer and returns its secret once; afterwards only the prefix shows. | [Agent](/business/nouns/agent) | [`APIKey.issued`](/business/events/APIKey.issued) | Two-way: `try`, `send`, `do` |
| [APIKey.revoke](/business/nouns/api-key/revoke) | Revoke stops an API Key at once: its next call fails with 401, and the record and its history stay. | [Agent](/business/nouns/agent) | [`APIKey.revoked`](/business/events/APIKey.revoked) | Two-way: `try`, `send`, `do` |
| [APIKey.roll](/business/nouns/api-key/roll) | Roll issues a replacement API Key with the same scope and keeps the old one working until a set time. | [Agent](/business/nouns/agent) | [`APIKey.rolled`](/business/events/APIKey.rolled) | Two-way: `try`, `send`, `do` |
| <a id="get"></a>[APIKey.get](/business/nouns/api-key/get) | Returns one API Key by id. Inherited from [Noun](/business/nouns/noun). | None. | None. | Read: `try`, `do` |
| <a id="list"></a>[APIKey.list](/business/nouns/api-key/list) | Returns API Keys, newest first, one page at a time. Inherited from [Noun](/business/nouns/noun). | None. | None. | Read: `try`, `do` |

An API Key is kept forever and never edited: it has no `update` or `delete`.

## Permissions
Which Access level may call each Verb. One-way Verbs wait their delay window.

| Operation | `owner` | `admin` | `member` | `viewer` |
| --- | --- | --- | --- | --- |
| `APIKey.get` | Yes | Yes | Yes | Yes |
| `APIKey.list` | Yes | Yes | Yes | Yes |
| `APIKey.issue` | Yes | Yes | Yes | No |
| `APIKey.revoke` | Yes | Yes | Yes | No |
| `APIKey.roll` | Yes | Yes | Yes | No |

## Call it

**Ask**

```text
Issue the API key “Nightly sync” with a customer.
```

**API**

```bash
curl -X POST https://api.sb/tutorloop.com/api-keys/issue \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"customer":"customer_1","name":"Nightly sync","environment":"test"}'
```

```json
{
  "api": "https://api.sb/tutorloop.com",
  "apiKey": {
    "$id": "https://api.sb/tutorloop.com/api-keys/api_key_1",
    "$type": "APIKey",
    "name": "Nightly sync",
    "environment": "test"
  },
  "secret": "sk_live_4f2a…",
  "action": {
    "$id": "https://api.sb/tutorloop.com/actions/action_1",
    "operation": "APIKey.issue",
    "status": "done"
  },
  "links": {
    "self": "https://api.sb/tutorloop.com/actions/action_1"
  }
}
```

**SDK**

```ts
import { $ } from 'startup-builder'

const { apiKey, secret } = await $.APIKey.issue({ customer: 'customer_1', name: 'Nightly sync', environment: 'test' })
```

**CLI**

```bash
npx startup-builder do APIKey.issue --json '{"customer":"customer_1","name":"Nightly sync","environment":"test"}'
```

**MCP**

```json
{
  "name": "do",
  "arguments": {
    "code": "return await $.APIKey.issue({ customer: 'customer_1', name: 'Nightly sync', environment: 'test' })"
  }
}
```

## Example
Leaseline sells renewal dates through its API. A property manager's admin issues a `live` key named `Nightly sync` that may call only `Collection.query`, and the [Portal](/business/nouns/portal) shows it as `leaseline_sk_live_4f2a`. When the key turns up in a public repository, it is revoked within minutes and the admins get an email to roll a replacement.