---
title: "API keys"
description: "`keys.list`, `listAll`, `iterate`, `get`, `create`, `update`, `delete`, `rotate` and `revoke`, and the request log and activity readers."
url: "https://nextmail.sh/docs/sdk/keys"
area: "TypeScript"
category: "Mailbox"
---

# API keys

`keys.list`, `listAll`, `iterate`, `get`, `create`, `update`, `delete`, `rotate` and `revoke`, and the request log and activity readers.

## Every method

**keys.ts**

```
const key = await nextmail.keys.create({
  name: 'Billing sender',
  scopes: ['emails:send'],
  domainAllowlist: ['billing.acme.com'],
  expiresInMinutes: 60 * 24 * 90,
})

await store(key.token)

await nextmail.keys.update(key.id, { enabled: false })
const rotated = await nextmail.keys.rotate(key.id)
await nextmail.keys.revoke(key.id, { reason: 'Replaced' })
await nextmail.keys.delete(key.id)
```

`create` and `rotate` are the only calls that return a secret, in `token`, once. Every read returns `maskedKey` instead. `update` switches a key off and on with `enabled`, which is the reversible alternative to `revoke`, and `delete` only removes a key that has been revoked. Reading needs `keys:read` and every change needs `keys:manage`.

## Never wider than the caller

A key never makes or reaches a key wider than itself: scopes, role, expiry, mode and send scope all have to sit inside the calling key, or the call is refused with 403 `beyond_caller_authority`. A key narrowed to some domains or addresses only sees the keys inside its own send scope. `rotate` on the calling key also works with `keys:write`, like `me.rotate()`.

> Step-up verification cannot apply to a call made with a key, so `keys:manage` is a credential that makes credentials. Give it only to automation that provisions keys, give that key a role, a send scope and an expiry, and watch `listWorkspaceActivity`, where everything it does is recorded against it.

## Request log and activity

**key-logs.ts**

```
const failures = await nextmail.keys.listRequests('4c1b257a66287fd113bd89d0', {
  failedOnly: true,
  since: new Date(Date.now() - 24 * 60 * 60 * 1000),
})

for await (const change of nextmail.keys.iterateWorkspaceActivity()) {
  console.log(change.keyName, change.type, change.actor?.label)
}
```

`listRequests` and `listActivity` read one key, `listWorkspaceRequests` and `listWorkspaceActivity` read every key or the ones `keyIds` names, and each has a `listAll…` and an `iterate…` beside it. They take `since` and `until`, and the request readers also take `failedOnly`, `statuses` and `path`.

`statuses` keeps the calls answered with those exact status codes, and `path` keeps the calls to one route, whatever the method. `path` matches the route, not the exact path: an id counts as `:id` and an email address as `:address`, so `/emails/:id` reads the calls made to any single email, and a path that ends in `*` reads every route that starts with it.

A key reads its own log with no scope at all. `me.listRequests`, `me.listActivity` and `me.stats` answer for the calling key and no other, so a key that can only send can still watch itself. `me.stats` fills `sends` only when the key holds `emails:read`.
