Guide

Authentication

How API keys, scopes, and rotation work.

Every Clement API request carries a bearer token in the Authorization header:

Authorization: Bearer cl_live_a1b2c3d4e5f6...

Live and test keys

Keys come in two flavors:

  • cl_live_… — production. Sends real email through real inboxes.
  • cl_test_… — sandbox. Same API surface; sends never reach a real recipient. Detailed in Test mode.

The SDK warns if a single process mixes test and live keys — common cause of “I sent prod email from a staging script.”

Scopes

Each key has one of three scope tiers:

  • read — GET only. Useful for dashboards and analytics jobs that shouldn’t be able to send or mutate.
  • write — read + non-destructive POST/PATCH/PUT. Create contacts, send, import, manage sequences.
  • admin — write + destructive ops: delete inboxes, rotate keys, manage webhooks, change billing.

Pick the smallest scope that does the job. If your background worker only needs to create sends and read events, give it a write key. If a script rotates webhooks, that’s an admin job — and probably wants auto_confirm: true so it doesn’t have to ack every confirmation prompt.

Creating and rotating keys

curl -X POST "https://api.clement.so/v1/api_keys" \
  -H "Authorization: Bearer $CLEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "Production worker",
    "scope": "write",
    "mode": "live"
  }'

The response includes a secret field — this is the only time you’ll see it. Store it in your secrets manager immediately. The dashboard and GET /v1/api_keys only ever return the last four characters. Clement doesn’t store the secret with an Idempotency-Key either: retrying the create with the same key returns 409 idempotency_replay_unavailable (naming the key that was created) instead of a second key.

An API key only manages keys of its own mode. mode defaults to the calling key’s mode, and a cl_test_… admin key can’t create, list, rotate or revoke cl_live_… keys (creating one returns 403). Mint live keys from the dashboard or with a live admin key.

To rotate, POST /v1/api_keys/{id}/rotate. The new secret is returned, the old one is invalidated within 60 seconds. Plan a brief overlap window so in-flight requests don’t fail mid-flight.

import { Clement } from "@clement/sdk";
const c = new Clement({ apiKey: process.env.CLEMENT_API_KEY! });

const rotated = await c.apiKeys.rotate("key_01HZ8X3J9K7P2QV6R5N4M3T2L1");
console.log(rotated.secret);

Revocation

DELETE /v1/api_keys/{id} revokes immediately (well, within 60s — cached lookups expire). Lost a key? Revoke and rotate; do not try to recover the secret.

This is a destructive operation, so it requires ?confirm=true unless the key has auto_confirm enabled.

Dashboard sessions

The dashboard uses Better-Auth session cookies, not API keys. Both resolve through the same middleware to (workspace_id, scope). There is no “internal” API — anything the dashboard can do, an API key can do.

The TypeScript SDK supports both:

// Server-to-server
new Clement({ apiKey: process.env.CLEMENT_API_KEY! });

// Browser (the dashboard's own usage)
new Clement({ session: "cookie" });