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

Decryption keys never touch our servers. Your secrets stay secret.

Self-Destructing Links

Set view limits and expiration times. Secrets auto-delete after access.

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}'

Endpoints

POST/secretsAuth Optional

Create a new secret link.

Request Body

ParameterTypeDescription
textstring | objectSecret content (50MB max total). 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 86400000 (999 days). Default: 86400
maxViewsnumber | nullMax views (integer 1-999) or null for unlimited. 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

1,000 requests per hour per IP address using a sliding-window algorithm. Content, TTL, view, and file limits are uniform (50MB, 999 days, 999 views, 20 files)—there are no per-tier limits.

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
200Success
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 or never-existing keys return 404 NOT_FOUND. A secret that existed but is now gone (expired, burned, or out of views) returns 410 with the specific code above.

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;