# Authentication and keys

> API keys, OAuth for applications acting on a workspace, signer sessions, and how each one is scoped, rotated, and revoked.

Source: https://signclad.com/docs/authentication/

Signclad has three ways to authenticate, and they never overlap. A signer session cannot call the management API; an API key cannot act as a signer.

## API keys

The default for server-to-server work.

```http
X-Api-Key: sk_live_9f2a...
```

| Prefix | Mode | Behaviour |
|---|---|---|
| `sk_live_` | Live | Bills, sends real email, subject to the normal rate limits. |
| `sk_test_` | Sandbox | Never bills. Delivers test emails to recipients. Subject to the same rate limits as live keys. |

A key belongs to one workspace. Every key acts with the same permissions — the admin capabilities of that workspace, which is what `GET /me` reports as its role — with one deliberate exception: a key cannot touch billing, so a leaked integration key cannot cancel the subscription or transfer ownership. There is no per-key `read` or `write` scope; narrow a credential by using a sandbox key, not by scoping a live one.

Keys are shown once at creation and stored hashed. A workspace holds at most 25 unrevoked keys.

Creating a key needs the API access your plan includes — Light and above — and creating a sandbox key needs the same. A sandbox key can create only sandbox keys, so the first live key has to come from the app or from another live key.

### Rotation

Rotation issues a new key and gives the old one a fixed 24-hour expiry, so you can deploy the new value before the old one stops. Both keys work during the overlap and both appear in the request log, so you can see when the last caller moved over. The rotate response carries the new plaintext and the old key's `stops_working_at`.

Rotation is not revocation, and the overlap is not adjustable. If a key leaks, revoke it: `DELETE /api_keys/{id}` stops it immediately. A key cannot revoke itself — that answers `409 self_revoke` — so revoke it with another credential or from the app.

### What a sandbox key may reach

A sandbox key is confined to an allow-list of route families, and anything outside it answers `403 sandbox_key_not_allowed`. Documents, templates, files, bulk sends, webhook endpoint configuration, the event catalog, its own keys and `/me` are in. The workspace address book, suppression list, webhook delivery logs and replay, data exports, members, billing, workspace settings and OAuth applications are out — they hold live data with nothing to filter test rows on.

## OAuth 2.0

For an application acting on behalf of a workspace that is not yours — an integration you distribute, or a product embedding Signclad for its own customers.

Authorization code with PKCE. PKCE is required and `S256` is the only accepted method — a request without `code_challenge_method=S256` is refused rather than defaulted. Discovery lives at `/.well-known/oauth-authorization-server`. The workspace being acted on needs API access, the same Light-and-above entitlement an API key needs.

Scopes:

| Scope | Grants |
|---|---|
| `documents:read` | Documents, recipients, fields, events, and completed files. |
| `documents:write` | Create, send, update, cancel, and delete documents. |
| `templates:read` | Templates and placeholders. |
| `templates:write` | Create, update, duplicate, and delete templates. |
| `webhooks:read` | Webhook endpoint configuration and delivery logs. |
| `webhooks:write` | Manage webhook endpoints. |
| `account:read` | The membership, account, and workspace. |
| `offline_access` | Issues a refresh token. Without it, an authorization lasts one access token. |

A write scope satisfies its read half, so asking for `documents:write` alone is enough to read documents too. Ask for the narrowest set that works.

Access tokens are bearer tokens, passed as `Authorization: Bearer <token>`, and live an hour. An authorization code lives ten minutes and is single use. Refresh tokens rotate on every use and the whole family is revoked if an old one is replayed.

## Signer sessions

A short-lived opaque bearer token held by the signer app, obtained by exchanging a signing link or an embedded signing URL and passing whatever gate the document has — a passcode or nothing. Only a hash of it is stored, like an API key.

- Thirty minutes from issue. It does not extend with use.
- Scoped to **one recipient of one document**. It cannot read another recipient's fields, let alone another document.
- Revoked by bumping the recipient's or the document's token version, which is what happens when a document is cancelled or a recipient reassigns.
- Accepted only by the `Signer` operations, and by nothing else.

You will not normally issue these yourself; they exist so that the signer app can be a normal client of the same API.

## Working out who you are

```bash
curl https://api.signclad.com/api/v1/me -H "X-Api-Key: $SIGNCLAD_API_KEY"
```

Returns the identity behind the credential: `user`, `role`, and an `account` object with the plan tier, the active template and user counts, and whether another template or document may be created right now. `workspace` is a **verbatim duplicate of `account`**, not a separate object — the real tenant id is `current_workspace_id`. For an API key there is no membership of its own, so `user` is the workspace owner and `role` is `admin`.

Useful as a health check and for showing "connected as" in your own settings screen.

## Keeping keys safe

- Never put a live key in client-side code. Embedded signing and the builders use short-lived URLs precisely so that you do not have to.
- Restrict who can create keys: only owners and admins can, and every key creation is recorded in the audit log.
- The request log at [Settings → API keys → Request log](https://app.signclad.com/app/settings/api-keys/log) keeps 30 days of method, path, status, latency, error code, IP, user agent, and request id per key. No request or response body is ever stored, and credential-shaped query parameters — anything containing `key`, `token`, `secret`, `password`, `passcode`, `signature`, `code` — are redacted before the row is written. Each entry comes with a `copy_as_curl` line that reproduces the shape of the request, with the key replaced by its display prefix.
- If a key leaks, revoke it rather than rotating it. Rotation always leaves the old value working for 24 hours.

## Related

- [Sandbox and test mode](https://signclad.com/docs/sandbox-and-test-mode/)
- [Errors and rate limits](https://signclad.com/docs/errors-and-rate-limits/)
