API v1
Base: https://eclair.kayzen-lyon.com/api/v1. JSON everywhere, responses never cached. Binary data is base64url without padding. openapi.json
Golden rule: encrypt before calling the API. Never send the link key, the PIN or plaintext. The easiest way is to use our JavaScript client (api-client package) or the CLI.
Routes
| Method | Route | Purpose |
|---|---|---|
POST | /secrets | Stocker un secret déjà chiffré |
POST | /secrets/{id}/preview | Libellé chiffré et indicateurs, sans le contenu |
POST | /secrets/{id}/reveal | Révéler (consomme une lecture) |
GET | /manage/{id} | Statut et historique (accusé de lecture) |
DELETE | /manage/{id} | Détruire avant lecture |
POST | /uploads | Ouvrir un envoi de fichiers chiffrés |
POST | /uploads/{id}/parts | URL présignée (5 min) pour une partie |
POST | /requests | Créer une demande de secret (lien de dépôt) |
POST | /requests/{id}/deposits | Déposer un contenu chiffré pour le demandeur |
GET | /requests/{id}/deposits | Lister les dépôts (demandeur) ou vérifier que le lien est ouvert (sans jeton) |
DELETE | /requests/{id} | Fermer la demande et supprimer les dépôts |
POST | /abuse | Signaler un lien abusif |
POST | /webhooks | Enregistrer un webhook https |
DELETE | /webhooks/{id} | Supprimer un webhook |
POST | /domains | Réserver un domaine personnalisé |
GET | /domains/{domain} | État d'un domaine |
POST | /domains/{domain} | Vérifier l'enregistrement TXT et rattacher le domaine |
DELETE | /domains/{domain} | Libérer le domaine |
GET | /config | Limites de l'instance |
GET | /health | Santé du service |
Example (TypeScript, local encryption)
import { sendSecret, parseSecretLink, receiveSecret } from "@sesame-eclair/api-client";
const options = { baseUrl: "https://eclair.kayzen-lyon.com" };
// Encrypts in this process, then uploads only the ciphertext.
const sent = await sendSecret(options, {
content: { v: 1, type: "text", text: "DB_PASSWORD=..." },
views: 1,
ttlSeconds: 3600,
});
if (sent.ok) console.log(sent.url, sent.manageUrl);
// Recipient side: the key comes from the link fragment.
const link = parseSecretLink(sent.ok ? sent.url : "")!;
const received = await receiveSecret(options, link);Status codes
404— same answer for unknown, expired, already read or destroyed secrets.401— wrong PIN, with attemptsLeft.403— access_denied: the reader's network or country does not meet the secret's restrictions. The secret stays intact.423— not readable yet (delayed reveal), with availableAt.429— too many requests, retry in an hour.
Webhooks
Register an https URL with POST /webhooks, then pass webhookId when creating the secret. You receive { event, secretId, at } for created, revealed, burned, revoked and expired: never the content. The signingSecret is shown once; verify each call like this:
import { createHmac, timingSafeEqual } from "node:crypto";
// header = request.headers["eclair-signature"], body = raw request body (string)
function verify(header: string, body: string, signingSecret: string): boolean {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay window
const expected = createHmac("sha256", Buffer.from(signingSecret, "base64url"))
.update(`${t}.${body}`)
.digest();
return timingSafeEqual(expected, Buffer.from(v1, "hex"));
}Access restrictions
accessRules: { cidrs: ["203.0.113.0/24"], countries: ["FR"] } limits opening to networks or countries (20 entries max each). The check happens before any ciphertext is sent; an unknown country is refused as soon as a country rule exists.
Custom domain
POST /domains claims a domain and returns a TXT verification record and a CNAME. Once created, POST /domains/{domain} checks the TXT and attaches the domain; the TLS certificate is issued automatically. The Custom domain page does all this for you.