REST API

Automate the seal.

Sealed's HTTP API speaks JSON. Encryption happens on your side (browser, script, CI runner) — the server only handles ciphertext. No SDK to install; fetch or curl is enough.

Auth

Most endpoints don't require authentication — anyone can create or consume a secret if they hold the URL. Endpoints that require an account (creating requests, planting canaries, viewing your dashboard) authenticate via the Supabase session cookie that the magic-link flow sets. From a script, you'll need to authenticate against Supabase Auth directly.

Client-side encryption (REQUIRED)

The server stores whatever ciphertext you send. If you send plaintext, the server stores plaintext — defeating the entire point. Always encrypt with AES-256-GCM on your side using a key you put into the URL fragment. See the open-source reference implementation on /source.

Endpoints

POST/api/secrets

Create a one-time encrypted secret. Caller is responsible for client-side encryption — the server only ever sees ciphertext.

Request body

{
  "id": "AbCdEf0123456789",          // 16-32 chars [A-Za-z0-9_-]
  "ciphertext": "base64url-bytes",   // up to 6 MB
  "view_limit": 1,                   // 1-100
  "expires_in_sec": 604800,          // up to 30 days
  "unlock_in_sec": 0,                // 0 = no time-lock; up to 1 year
  "password": "optional",            // server hashes with SHA-256 (gating only)
  "notify_email": "you@co.com",      // view-receipt
  "notify_url": "https://hook.example/sealed",
  "recipient_label": "Bob @ Acme"
}

Response (200 OK)

{ "ok": true, "id": "AbCdEf0123456789", "expires_at": 1781234567890 }

cURL

curl -X POST https://usesealed.com/api/secrets \
  -H "Content-Type: application/json" \
  -d '{"id":"AbCdEf0123456789","ciphertext":"AAA","view_limit":1,"expires_in_sec":3600}'
GET/api/secrets/[id]

Peek metadata without consuming a view. Returns whether the link is locked, password-gated, or already burned.

Response (200 OK)

{
  "ok": true,
  "requires_password": false,
  "view_limit": 1,
  "view_count": 0,
  "expires_at": 1781234567890,
  "earliest_view_at": 0,
  "is_locked": false
}

cURL

curl https://usesealed.com/api/secrets/AbCdEf0123456789
POST/api/secrets/[id]

Consume one view: returns ciphertext, decrements view_count, burns when limit hits zero. Time-locked secrets respond 425 Too Early.

Request body

{ "password": "if-needed" }

Response (200 OK)

{ "ok": true, "ciphertext": "...", "views_remaining": 0, "burned": true }

cURL

curl -X POST https://usesealed.com/api/secrets/AbCdEf0123456789 \
  -H "Content-Type: application/json" -d '{}'

Status 425 means time-locked — body includes `unlock_at` and `seconds_remaining`.

POST/api/secrets

Batch create up to 25 secrets in one call. Use for multi-recipient handoffs.

Request body

{ "batch": [
  { "id":"AbCdEf0123456789", "ciphertext":"AAA", "recipient_label":"Alice" },
  { "id":"GhIjKl4567890ABC", "ciphertext":"BBB", "recipient_label":"Bob" }
] }

Response (200 OK)

{ "ok": true, "results": [{ "ok": true, "id":"..." }, { "ok": true, "id":"..." }] }

cURL

# See request schema above; batch is a top-level array of CreateOne entries.
POST/api/requests

Create a Secret Request. Requires auth. Caller must generate an RSA-OAEP keypair in their browser and post the public JWK.

Request body

{
  "id": "AbCdEf0123456789",
  "label": "AWS prod root API key",
  "recipient_email": "bob@co.com",
  "expires_in_sec": 604800,
  "public_key_jwk": { "kty":"RSA", "n":"...", "e":"AQAB", ... }
}

Response (200 OK)

{ "ok": true, "id":"...", "expires_at": "2026-07-09T..." }

cURL

# Use the browser flow on /requests/new — full keypair generation is client-side.
POST/api/canaries

Create a canary token. Requires auth. Browser generates the poison credential + encrypts it, server marks the row as a canary.

Request body

{
  "id": "AbCdEf0123456789",
  "ciphertext": "base64url-bytes",
  "kind": "aws_access_key",
  "label": "Engineering wiki — fake AWS doc",
  "notify_email": "you@co.com"
}

Response (200 OK)

{ "ok": true, "id":"AbCdEf0123456789" }

cURL

# Recommended: use /canaries/new which handles the generator + encryption.
GET/api/transparency

Public aggregate counters. No auth required. Cache-friendly (5s revalidation).

Response (200 OK)

{
  "ok": true,
  "stats": {
    "active_secrets_now": 0,
    "secrets_created_total": 0,
    "secrets_destroyed_total": 0,
    "decryptions_total": 0,
    "canary_triggers_total": 0
  },
  "generated_at": "2026-06-09T..."
}

cURL

curl https://usesealed.com/api/transparency