API Documentation

Integrate Burn the Secret into your applications to create secure, self-destructing secret links programmatically.

Base URL

https://burnthesecret.com/api

AES-256-GCM Encryption

Industry-standard authenticated encryption used by banks and governments.

Zero-Knowledge Architecture

Browser and CLI encryption keeps keys off our servers. Plaintext API submissions use server-side encryption.

Self-Destructing Links

Set view limits and expiration times. Links stop working at their view limit or expiry.

RESTful JSON API

Simple HTTP endpoints with consistent response formats.

Authentication

Authentication is optional. Anyone can create a secret without an API key—sending a key only attributes the secret to your account. To attribute a secret, include your key in the Authorization header:

Authorization: Bearer ks_live_your_api_key

Create API keys in your Dashboard → API Access. If you omit the header the request is still accepted (anonymous, or linked to your signed-in browser session). Only if you send a key that is invalid or revoked is the request rejected—with a 401 INVALID_API_KEY response.

Quick Start

Create a secret link with a single API call:

curl -X POST https://burnthesecret.com/api/secrets \
  -H "Authorization: Bearer ks_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"text": "my-sensitive-password", "ttl": 86400, "maxViews": 1}'

Command-Line Interface

New

Prefer the terminal? The burnthesecret CLI creates one-time links without leaving your shell. It encrypts on your machine (AES-256-GCM) and sends only the ciphertext — the decryption key stays in the link fragment, so it stays zero-knowledge, exactly like the web app. No dependencies; requires Node.js 18+.

# One-off, no install (Node.js 18+)
npx burnthesecret "my secret"

# From a pipe — great for CI and scripts
echo "$DB_PASSWORD" | npx burnthesecret --ttl 3600 --views 1

# Require a passphrase, or attribute to your account with an API key
npx burnthesecret "token" --passphrase "over-the-phone"
npx burnthesecret "token" --api-key ks_live_... --json

Free and open source (MIT). It prints a single link — share that. Anyone with the full link can read the secret once, so treat the link like the secret.

Endpoints

POST/secretsAuth Optional

Create a new secret link. Use one encryption mode for every item: either plaintext text and base64 data-URL files for server-side encryption, or client-encrypted text and files using the same AES key. Mixed modes are rejected. Client ciphertext must be canonical base64 with a 12-byte base64 IV. The browser and CLI preserve exact text, including whitespace.

Request Body

ParameterTypeDescription
textstring | objectSecret content (2 MB combined plaintext; 4 MB encoded JSON maximum). A plain string (server encrypts it—not zero-knowledge) or {ciphertext, iv} you encrypted client-side (zero-knowledge)
ttlnumberTime to live in whole seconds. 1800 (30 min) to 7776000 (90 days). Default: 86400
maxViewsnumber | nullMax views (integer 1-999) or null for unlimited views until expiry. Default: 1
passphrasestringOptional raw passphrase, 4-256 chars (send over HTTPS; hashed server-side with a salted KDF)
filesarrayArray (max 20) of files with filename, mimeType, data

Response

{
  "success": true,
  "secretUrl": "https://burnthesecret.com/secret/Kj8mNp2x...#key=...",
  "metadataUrl": "https://burnthesecret.com/receipt/Yz3nLq9b...",
  "metadata": {
    "key": "Yz3nLq9bFs7hGc...",
    "secretKey": "Kj8mNp2xQr5tVw...",
    "expiresAt": "2026-01-27T12:00:00.000Z",
    "maxViews": 1
  }
}
GET/secrets/:key

Check if a secret exists and get metadata. Does not consume a view.

{
  "exists": true,
  "type": "secret",
  "hasPassphrase": false,
  "expiresAt": "2026-01-27T12:00:00.000Z",
  "maxViews": 1,
  "viewsRemaining": 1
}
POST/secrets/:secretKey

Retrieve secret content. This consumes one view.

Request Body (if passphrase protected)

{ "passphrase": "your-passphrase" }

Response

{
  "success": true,
  "text": {
    "ciphertext": "base64-encoded...",
    "iv": "base64-encoded..."
  },
  "viewsRemaining": 0
}

Decrypt using the key from the URL fragment with AES-256-GCM.

DELETE/secrets/:key

Permanently delete (burn) a secret.

curl -X DELETE https://burnthesecret.com/api/secrets/Yz3nLq9bFs7hGc...

Encryption

Burn the Secret uses AES-256-GCM, the same encryption used by TLS 1.3, Signal, and government systems.

Zero-Knowledge Architecture

The decryption key is stored in the URL fragment (after #), which browsers never send to servers. We only store encrypted ciphertext—useless without the key. Decryption happens entirely in the recipient's browser.

URL Structure:

https://burnthesecret.com/secret/secretKey#key=decryptionKey

Server-Side Encryption

Send plaintext (or the legacy secret field)—we encrypt it server-side and return the URL with the key in the fragment. This path is not zero-knowledge: the server briefly holds the key.

Client-Side Encryption

Encrypt before sending as {ciphertext, iv}—you control the key. True zero-knowledge, maximum security.

Rate Limits

A baseline limit of 1,000 requests per day per IP and operation applies before authentication. Signed-in creation also allows 100/hour and 1,000/day per account. API keys share 1,000/hour per owning account. Protected retrieval allows five attempts/minute per secret and IP, including empty attempts. Limits fail closed if their service is unavailable. Content limits are uniform: 2 MB combined text/files, 4 MB encoded JSON, 90 days, 999 views (or unlimited until expiry), and 20 files.

Response Headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 950
X-RateLimit-Reset: 1706360400

Errors

All errors return a consistent JSON structure:

{
  "error": "Invalid passphrase",
  "code": "INVALID_PASSPHRASE",
  "hint": "Include the correct passphrase in your request.",
  "requestId": "req_abc123..."
}
StatusCodeDescription
200—Success
400INVALID_*Bad request (invalid params)
401INVALID_API_KEYA key was sent but is invalid or revoked (a key is optional; omitting it is fine)
403INVALID_PASSPHRASE / DELETION_NOT_ALLOWEDWrong passphrase, or burn attempted on a secret whose sender disabled deletion
404NOT_FOUNDSecret not found
410SECRET_EXPIRED / SECRET_BURNED / MAX_VIEWS_REACHEDSecret is gone: expired, burned, or max views reached
429RATE_LIMIT_EXCEEDED / TOO_MANY_ATTEMPTSRate limit exceeded, or too many wrong passphrase attempts
500INTERNAL_ERRORServer error

Unknown keys and secrets removed by cleanup return 404 NOT_FOUND. Retrieval may return 410 while an expired, burned, or exhausted status row still exists. Access ends at expiry; hourly cleanup removes expired rows and attachments from the active database, normally within one hour. New secrets last at most 90 days; existing secrets retain their saved expiry dates.

Security Best Practices

Choose the Right Encryption Mode

  • Server-side: Convenient—we handle encryption. Plaintext briefly in memory.
  • Client-side: Maximum security—you control the key entirely.

Passphrase Tips

Use 8+ characters with mixed case, numbers, symbols. Share passphrases via a different channel than the URL.

Minimize Exposure

Use the shortest TTL and lowest maxViews needed. For one-time credentials, use maxViews: 1.

Code Examples

Decrypting Retrieved Secrets

JavaScript

const urlKey = window.location.hash.split('key=')[1];
const rawKey = Uint8Array.from(
  atob(urlKey.replace(/-/g, '+').replace(/_/g, '/')),
  c => c.charCodeAt(0)
);

const key = await crypto.subtle.importKey(
  'raw', rawKey, { name: 'AES-GCM' }, false, ['decrypt']
);

const ciphertext = Uint8Array.from(atob(response.text.ciphertext), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(response.text.iv), c => c.charCodeAt(0));

const plaintext = await crypto.subtle.decrypt(
  { name: 'AES-GCM', iv }, key, ciphertext
);
const secret = new TextDecoder().decode(plaintext);

Python

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64

key = base64.urlsafe_b64decode(url_key + '==')
ciphertext = base64.b64decode(response['text']['ciphertext'])
iv = base64.b64decode(response['text']['iv'])

aesgcm = AESGCM(key)
plaintext = aesgcm.decrypt(iv, ciphertext, None)
secret = plaintext.decode('utf-8')

Client-Side Encryption

For maximum security, encrypt before sending to Burn the Secret:

JavaScript

// Generate key and IV
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 }, true, ['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));

// Encrypt
const plaintext = new TextEncoder().encode('my-secret');
const ciphertext = await crypto.subtle.encrypt(
  { name: 'AES-GCM', iv }, key, plaintext
);

// Export key for URL
const rawKey = await crypto.subtle.exportKey('raw', key);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)));

// Send to API
const response = await fetch('https://burnthesecret.com/api/secrets', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ks_live_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    text: {
      ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertext))),
      iv: btoa(String.fromCharCode(...iv))
    }
  })
});

const data = await response.json();
const fullUrl = data.secretUrl + '#key=' + keyBase64;