Skip to main content
For developers

The vault, scriptable. The plaintext, still nobody’s.

Stemma has an early API: scoped ctm_ keys that can be limited to specific vaults, signed webhooks, honest usage numbers, and an OpenAPI reference generated from the server itself. It is deliberately small, and this page tells you exactly where its edges are.

What it does

Three surfaces, one boundary.

Everything the API exposes respects the same rule as the product: the server holds ciphertext and metadata, never readable records and never keys.

Vaults & items

Read and write the record — as ciphertext.

List vaults, create and update items, manage the trash lifecycle. Every item payload is an encrypted envelope sealed on the client: your integration moves records, and only your own client-side keys can open them.

Webhooks

Signed events when the record changes.

Subscribe an HTTPS endpoint to item and vault events. Each delivery is HMAC-signed with a secret only you were shown, and carries an idempotency key so retries never double-process.

Audit

The account history, readable by a key you scope to it.

A read-only scope exposes the account audit trail, so compliance tooling can watch the record of actions without ever holding a write credential.

Auth model

Keys with edges you draw yourself.

An API key is a narrow instrument, not a second login. You choose its permissions from the server’s catalog, optionally pin it to specific vaults, give it an expiry, and rotate or revoke it from the desk at any time.

01
CREATE

Mint a key from the desk

Keys are created in Settings → Developer on the Professional plan. Every key starts with the ctm_ prefix, is shown exactly once, and is stored by us only as a hash.

02
SCOPE

Give it only what it needs

Permissions come from a server-published catalog — read or write on vaults, items, and audit. A key can also be restricted to specific vaults, so a tax integration can see the household binder and nothing else.

03
CALL

Bearer auth, honest errors

Send the key as a Bearer token. Out-of-scope requests fail closed with 403 INSUFFICIENT_SCOPE — a vault-restricted key cannot even list vaults outside its set.

Plate II — First call
# List the vaults this key is allowed to see
curl https://api.trystemma.com/api/v1/vaults \
  -H "Authorization: Bearer $STEMMA_API_KEY"

# A vault-scoped key answering for a vault outside its set:
# 403 { "error": { "code": "INSUFFICIENT_SCOPE", … } }
GET
/api/v1/developer/scopes

The permission and webhook-event catalog — the single source of truth this page defers to. Events the server accepts but does not deliver yet are flagged emitted: false.

GET
/api/v1/developer/usage

Real request totals with a per-key daily series, counted per authenticated request — not an estimate.

POST
/api/v1/developer/keys

Create a key with explicit permissions, an optional expiry, and an optional vault allow-set.

GET
/api/openapi.json

The machine-readable OpenAPI spec, generated from the server’s own route annotations.

Webhooks

Verify every delivery before you trust it.

Each webhook body is signed with HMAC-SHA256 using the whsec_ secret shown once at creation. The signature arrives in the X-Stemma-Signature header as sha256=<hex>, computed over the raw request body. Compare it timing-safely, and use the Idempotency-Key header to drop redeliveries.

Plate III — Signature verification
import { createHmac, timingSafeEqual } from 'node:crypto';

// Verify a Stemma webhook delivery.
// secret: the whsec_… value shown once when the webhook was created.
export function verifyStemmaWebhook(rawBody, headers, secret) {
  const received = headers['x-stemma-signature'] ?? '';   // "sha256=<hex>"
  const expected = 'sha256=' +
    createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(received);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);  // timing-safe
}
The honest register

An early API, labeled as one.

We publish what the API is today, not what a roadmap hopes it becomes. Four things worth knowing before you build:

The surface is small on purpose

Authentication, vaults, items, webhooks, usage, and audit. It will grow, and responses may gain fields — build tolerant parsers.

Some events are catalogued but not yet delivered

The scope catalog marks each webhook event with an emitted flag. If it says false, subscribing works but nothing will arrive yet — we would rather tell you than let an integration wait on a ghost.

No OAuth, no per-key rate-limit SLAs yet

Keys are personal bearer credentials for your own account. Third-party app authorization and published rate guarantees are future work, not fine print.

Zero knowledge is a constraint you inherit

The API never returns plaintext vault content and cannot be asked to. Decryption belongs to clients holding the keys — that boundary is the product, and it applies to integrations too.