API Documentation
Create encrypted secrets programmatically with our REST API. Zero-knowledge encryption — your secrets and passwords never reach our servers.
Your responsibility: When you create a secret via API, you must share (1) the secret URL and (2) the unlock password with the recipient through two different channels for security.
Authorization header:Authorization: Bearer enigma_xxxxxxxxxxxxxxxxWithout a valid token, requests use anonymous rate limits and may require reCAPTCHA.
Request Body
{
"encryptedContent": "string (REQUIRED) - pre-encrypted v2 blob",
"authToken": "string (REQUIRED) - 64 hex chars derived from the password",
"name": "string (optional, max 100 chars, stored unencrypted)",
"expiresAt": "ISO8601 in the future, or null (optional)",
"maxAccess": integer 1-1000 or null (optional)
}Response (200)
{
"success": true,
"message": "Message stored successfully",
"data": {
"messageId": "a8Kx9pQ2",
"deleteToken": "xxx (only for anonymous secrets)"
}
}Example (cURL)
curl -X POST https://enigmacrypt.app/api/messages \
-H "Authorization: Bearer enigma_xxx" \
-H "Content-Type: application/json" \
-d '{
"encryptedContent": "v2.<salt>.<iv>.<ciphertext>",
"authToken": "<64 hex chars>",
"name": "API Secret",
"expiresAt": null,
"maxAccess": 1
}'Algorithm
- master = PBKDF2-HMAC-SHA256(password, salt, 600,000 iterations, 32 bytes)
- encryption key = HMAC-SHA256(master, "enigmacrypt-v2-enc")
- authToken = hex(HMAC-SHA256(master, "enigmacrypt-v2-auth"))
- AES-256-GCM with a random 16-byte salt and a random 12-byte IV
Format
v2.<salt hex>.<iv hex>.<base64(ciphertext + 16-byte GCM tag)>
Message object before encryption: { content: "your-secret", created: "ISO8601" }
The server stores only a hash of authToken and releases the ciphertext to whoever presents it, which is how view limits are enforced without the server being able to decrypt. The token cannot be turned back into the encryption key.
Passwordless links: use a random 32-character hex string as the password and append it to the URL as a fragment (/s/<messageId>#<key>). The fragment is never sent to the server. The legacy v1 format (AES-CBC) is still accepted but deprecated: its view limits cannot be enforced by the server.
See integration examples below for complete code in Node.js, Python, Go, and cURL.
// No dependencies: uses Node's built-in crypto
const crypto = require("crypto")
function encryptSecret(secret, password) {
const salt = crypto.randomBytes(16)
const master = crypto.pbkdf2Sync(password, salt, 600000, 32, "sha256")
const encKey = crypto.createHmac("sha256", master).update("enigmacrypt-v2-enc").digest()
const authToken = crypto.createHmac("sha256", master).update("enigmacrypt-v2-auth").digest("hex")
const iv = crypto.randomBytes(12)
const cipher = crypto.createCipheriv("aes-256-gcm", encKey, iv)
const payload = JSON.stringify({ content: secret, created: new Date().toISOString() })
const ciphertext = Buffer.concat([cipher.update(payload, "utf8"), cipher.final(), cipher.getAuthTag()])
return {
encryptedContent: `v2.${salt.toString("hex")}.${iv.toString("hex")}.${ciphertext.toString("base64")}`,
authToken,
}
}
async function createSecret(secret, password, apiToken) {
const { encryptedContent, authToken } = encryptSecret(secret, password)
const res = await fetch("https://enigmacrypt.app/api/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
encryptedContent,
authToken,
name: "API Secret",
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
maxAccess: 1,
}),
})
const data = await res.json()
if (!data.success) throw new Error(data.error)
return { url: `https://enigmacrypt.app/s/${data.data.messageId}`, password }
}- Anonymous (no token): 20 requests per 15 minutes
- With API token: 100 requests per 15 minutes