Trousse d'intégration — API Seal
Tu n'as pas besoin de code pour sceller une œuvre. Tout est dans /moi (personne) ou dans ton portail (organisation). Cette page sert à automatiser, pas à sceller.
Sceller sans code →
⚠️ La description est le SEUL texte qui voyage vers Seal — et elle y est conservée, parce que la personne qui approuve doit voir ce qu'elle approuve. N'y mets jamais un nom, un numéro de compte ni une donnée personnelle : mets une référence que ton propre système sait relire (« Prêt #4521 », « Dossier C-90218 »).
Deux appels HTTPS suffisent : créer une demande d'autorisation, puis lire son état jusqu'au verdict. Appels SORTANTS uniquement, aucun port à ouvrir, aucune donnée de votre entreprise ne transite — seulement la description que vous choisissez d'écrire.
Avant de commencer : créez une clé au portail (carte Autorisations → « Créer une clé »), et autorisez les appels sortants vers seal.lifaio.com si votre réseau filtre les sorties.
1. Créer la demande
POST /api/autorisation-creer — champs : jeton (votre clé), description (ce qui sera autorisé, visible par les approbateurs), employes (codes E- à exiger, export CSV au portail), minutes (validité, 5 à 1440), minimum (quorum : 2 sur 4 par exemple ; absent = tous), refusBloquant (true = un refus arrête tout). Réponse : {ok, id, url}.
2. Attendre le verdict
GET /api/autorisation?id=AU-XXXXXXXX — répond etat : en_attente, autorisee, refusee ou expiree, avec confirmations, minimum, requises et le sceau une fois autorisée. Interrogez toutes les 5 à 30 secondes.
3. Décider et archiver
Archivez la réponse complète (avec le sceau) dans le dossier de l'opération : c'est VOTRE preuve. Imprimez aussi le lien public /a/AU-… dans vos documents — n'importe qui pourra la revérifier chez Seal des années plus tard.
Règle d'or : si l'état n'est pas « autorisee », ne livrez rien, ne déboursez rien, ne copiez rien.
PowerShell
# PowerShell 5+ / 7+ — Seal LIFAIO
$base = "https://seal.lifaio.com"
$cle = $env:SEAL_CLE # never hard-coded
# 1) create the request
$corps = @{
jeton = $cle
description = "Wire transfer of $250,000 to supplier F-2214"
employes = @("E-12345678","E-22334455","E-33445566","E-44556677")
minutes = 30
minimum = 2 # 2 of 4; remove the line = all of them
refusBloquant = $true
} | ConvertTo-Json
$d = Invoke-RestMethod -Method Post -Uri "$base/api/autorisation-creer" `
-ContentType "application/json" -Body $corps
Write-Host "Request" $d.id "-> send for approval:" $d.url
# 2) wait for the verdict (30 min max)
$fin = (Get-Date).AddMinutes(30)
do {
Start-Sleep -Seconds 10
$e = Invoke-RestMethod "$base/api/autorisation?id=$($d.id)"
Write-Host $e.etat $e.confirmations "/" $e.requises
} while ($e.etat -eq "en_attente" -and (Get-Date) -lt $fin)
# 3) decide
if ($e.etat -ne "autorisee") { throw "Operation NOT authorised: $($e.etat)" }
$e | ConvertTo-Json -Depth 6 | Out-File "preuve-$($d.id).json" -Encoding utf8
# ... your sensitive operation here ...C# / .NET
// C# / .NET 6+ — Seal LIFAIO
using System.Net.Http.Json;
using System.Text.Json;
var http = new HttpClient { BaseAddress = new Uri("https://seal.lifaio.com") };
var cle = Environment.GetEnvironmentVariable("SEAL_CLE"); // never hard-coded
// 1) create the request
var demande = await (await http.PostAsJsonAsync("/api/autorisation-creer", new {
jeton = cle,
description = "Access to the customer file (full export)",
employes = new[] { "E-12345678", "E-22334455", "E-33445566" },
minutes = 30,
minimum = 2,
refusBloquant = true
})).Content.ReadFromJsonAsync<JsonElement>();
var id = demande.GetProperty("id").GetString();
Console.WriteLine($"Send for approval: {demande.GetProperty("url").GetString()}");
// 2) wait for the verdict
JsonElement etat; string valeur;
var fin = DateTime.UtcNow.AddMinutes(30);
do {
await Task.Delay(10_000);
etat = await http.GetFromJsonAsync<JsonElement>($"/api/autorisation?id={id}");
valeur = etat.GetProperty("etat").GetString()!;
} while (valeur == "en_attente" && DateTime.UtcNow < fin);
// 3) decide
if (valeur != "autorisee")
throw new InvalidOperationException($"Operation NOT authorised: {valeur}");
File.WriteAllText($"preuve-{id}.json", etat.GetRawText()); // your proof
// ... your sensitive operation here ...Java
// Java 17+ — Seal LIFAIO (java.net.http, no dependency)
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
var http = HttpClient.newHttpClient();
var base = "https://seal.lifaio.com";
var cle = System.getenv("SEAL_CLE"); // never hard-coded
// 1) create the request
var corps = """
{"jeton":"%s",
"description":"Change of the supplier bank account",
"employes":["E-12345678","E-22334455","E-33445566","E-44556677"],
"minutes":30,"minimum":2,"refusBloquant":true}
""".formatted(cle);
var rep = http.send(HttpRequest.newBuilder(URI.create(base + "/api/autorisation-creer"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(corps)).build(),
HttpResponse.BodyHandlers.ofString()).body();
var id = rep.replaceAll(".*\"id\"\\s*:\\s*\"([^\"]+)\".*", "$1"); // or Jackson/Gson
// 2) wait for the verdict
String etat = "en_attente", corpsEtat = "";
var fin = System.currentTimeMillis() + Duration.ofMinutes(30).toMillis();
while (etat.equals("en_attente") && System.currentTimeMillis() < fin) {
Thread.sleep(10_000);
corpsEtat = http.send(HttpRequest.newBuilder(
URI.create(base + "/api/autorisation?id=" + id)).GET().build(),
HttpResponse.BodyHandlers.ofString()).body();
etat = corpsEtat.replaceAll(".*\"etat\"\\s*:\\s*\"([^\"]+)\".*", "$1");
}
// 3) decide
if (!etat.equals("autorisee")) throw new IllegalStateException("NOT authorised: " + etat);
java.nio.file.Files.writeString(java.nio.file.Path.of("preuve-" + id + ".json"), corpsEtat);
// ... your sensitive operation here ...Python
# Python 3.8+ — Seal LIFAIO (no dependency: urllib)
import json, os, time, urllib.request
BASE = "https://seal.lifaio.com"
CLE = os.environ["SEAL_CLE"] # never hard-coded
def appel(chemin, donnees=None):
corps = json.dumps(donnees).encode() if donnees else None
r = urllib.request.Request(BASE + chemin, data=corps,
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(r, timeout=20) as rep:
return json.load(rep)
# 1) create the request
d = appel("/api/autorisation-creer", {
"jeton": CLE,
"description": "Permanent deletion of the 2019 archives",
"employes": ["E-12345678", "E-22334455", "E-33445566"],
"minutes": 30,
"minimum": 2, # 2 of 3; remove the line = all of them
"refusBloquant": True,
})
print("Send for approval:", d["url"])
# 2) wait for the verdict
fin = time.time() + 30 * 60
etat = {"etat": "en_attente"}
while etat["etat"] == "en_attente" and time.time() < fin:
time.sleep(10)
etat = appel("/api/autorisation?id=" + d["id"])
print(etat["etat"], etat.get("confirmations"), "/", etat.get("requises"))
# 3) decide
if etat["etat"] != "autorisee":
raise SystemExit("Operation NOT authorised: " + etat["etat"])
open("preuve-%s.json" % d["id"], "w").write(json.dumps(etat, indent=2))
# ... your sensitive operation here ...
Variante : votre propre navette (pour isoler le logiciel principal)
Si votre logiciel cœur ne doit appeler aucun service externe, ne l'intégrez pas directement : faites-lui déposer un fichier dans un dossier, et confiez le relais à une petite tâche planifiée que VOUS écrivez et contrôlez. Vingt lignes suffisent — voici le modèle.
# Votre propre navette — le logiciel coeur n'appelle RIEN.
# Il ecrit un fichier ; ce script (sur une AUTRE machine) fait le relais.
# Dossiers : requests\ responses\ (noms techniques, jamais traduits)
$base = "https://seal.lifaio.com"; $cle = $env:SEAL_CLE
while ($true) {
Get-ChildItem "requests\*.json" | ForEach-Object {
$q = Get-Content $_.FullName -Raw | ConvertFrom-Json
$corps = @{ jeton=$cle; description=$q.description; employes=$q.employes
minutes=30; minimum=$q.minimum; refusBloquant=$true } | ConvertTo-Json
$d = Invoke-RestMethod -Method Post -Uri "$base/api/autorisation-creer" `
-ContentType "application/json" -Body $corps
do { Start-Sleep 10
$e = Invoke-RestMethod "$base/api/autorisation?id=$($d.id)"
} while ($e.etat -eq "en_attente")
$e | ConvertTo-Json -Depth 6 | Set-Content ("responses\" + $_.Name) -Encoding utf8
Move-Item $_.FullName ("processed\" + $_.Name) -Force
}
Start-Sleep 5
}
🎬 Authenticité des médias — sceller vos vidéos et vos audios
Une vraie vidéo de votre porte-parole, avec la voix remplacée par un clone qui dit autre chose : pas un pixel n'a bougé, et le mensonge est parfait. Le sceau média lie l'IMAGE ET LE SON de chaque intervalle, ensemble. Remplacer l'un des deux, ou simplement décaler l'un par rapport à l'autre, casse la correspondance.
1. Le calcul se fait CHEZ VOUS. La bibliothèque découpe l'œuvre et calcule une empreinte par demi-seconde, dans le navigateur du publieur. Votre vidéo ne nous est jamais envoyée : seules les empreintes voyagent.
2. Demandez la cérémonie. Vous soumettez la LISTE des œuvres à sceller — figée à ce moment-là. Une deuxième personne voit combien et lesquelles, puis approuve. Une œuvre absente de cette liste est refusée, et le refus est journalisé.
3. Scellez. Une seule cérémonie peut couvrir une œuvre ou dix mille. Chaque œuvre ne peut être scellée qu'une fois sous la même autorisation.
4. Déclarez où l'œuvre a le droit de paraître. Vos comptes et vos domaines. Une œuvre authentique présentée ailleurs n'est PAS confirmée : le résultat signale une republication non autorisée et nomme les lieux légitimes.
5. N'importe qui vérifie, gratuitement, sans compte. La vérification est libre et illimitée. Vous n'approuvez rien à la lecture — une œuvre vue un milliard de fois ne vous demande rien.
À savoir, et nous préférons le dire nous-mêmes : le ré-encodage des plateformes est absorbé, mais une altération à la fois très petite et très brève peut passer sous le seuil. Les plateformes protégées par DRM (Netflix, Disney+, Prime…) ne laissent aucun navigateur lire leur flux : on vérifie le fichier que vous détenez ou une copie qui circule, jamais le flux tel que diffusé. Et le sceau confirme QUI a publié et que RIEN n'a été modifié — jamais que ce qui est dit est vrai.
import SceauMedia, { empreintesVideo } from "./sceau-media.mjs";
const s = new SceauMedia({ jeton: process.env.SEAL_CLE });
// 1) Les empreintes se calculent ICI. Le fichier ne part jamais.
const a = await empreintesVideo(fichierA, (fait, total) =>
console.log(Math.round(100 * fait / total) + "%"));
const b = await empreintesVideo(fichierB);
// 2) La liste est FIGEE avant la signature : le deuxieme humain approuve
// exactement ces oeuvres-la, et rien ne peut y etre ajoute ensuite.
const d = await s.demanderScellage("Bulletin du 1er septembre", ["E-000123"], 30, [
{ hash: await s.hash(a.empreintes), titre: "Ouverture" },
{ hash: await s.hash(b.empreintes), titre: "Entrevue" },
]);
// 3) On attend la signature de l'autre personne.
let etat;
do {
await new Promise((r) => setTimeout(r, 5000));
etat = await s.etatDemande(d.autorisationId);
} while (etat.statut === "en_attente");
// 4) Scellage. Une seule ceremonie, deux oeuvres.
for (const [f, e, titre] of [[fichierA, a, "Ouverture"], [fichierB, b, "Entrevue"]]) {
await s.sceller(f, {
autorisationId: d.autorisationId,
titre,
lieux: ["youtube.com/machaine", "monsite.ca"],
});
}
// 5) APRES LA PUBLICATION — rescellez la copie que la plateforme sert
// REELLEMENT. Recuperez le fichier tel que YouTube, Spotify ou votre
// diffuseur le renvoie apres traitement, et scellez-le a son tour en le
// rattachant a l'oeuvre d'origine. Environ 6 Ko par version : le cout est
// negligeable, et le sceau porte alors sur le fichier reellement servi,
// pas sur un original dont on espere qu'il lui ressemble encore.
const copieYt = await s.sceller(fichierTelechargeDeYouTube, {
autorisationId: d2.autorisationId, // une nouvelle ceremonie
titre: "Ouverture",
oeuvreParent: sceauOrigine.id, // rattachement a l'oeuvre
versionNom: "YouTube",
lieux: ["https://www.youtube.com/watch?v=VOTRE_ID"],
});
// 6) N'importe qui verifie, gratuitement, sans compte :
// https://seal.lifaio.com/verifier-media
// Avec l'adresse seule : Seal dit A QUI appartient la chaine et quelle
// oeuvre y est declaree. Avec le fichier : il dit s'il a ete MODIFIE, et
// de quelle plateforme vient la copie.
🔐 Coffre — chiffrer vos archives, ouvrir à plusieurs
Un attaquant qui prend le contrôle de votre serveur passe SOUS votre application : aucune demande d'autorisation ne le voit passer, il lit vos fichiers directement. La seule parade est que ce qu'il emporte soit illisible. Le Coffre chiffre vos archives et vos exports ; CHIFFRER ne demande rien, DÉCHIFFRER exige la confirmation de plusieurs personnes. Seal ne détient AUCUNE clé : sa part est recalculée à la demande, il n'y a rien à voler chez nous.
1. Créer le coffre. Dans votre portail, carte Coffre : donnez-lui un nom, vous obtenez un identifiant CF-XXXXXX. Téléchargez l'outil, créez une clé d'API, puis lancez « init ». IMPRIMEZ l'export de secours affiché — il ne le sera jamais deux fois.
2. Chiffrer, en continu et sans personne. Le chiffrement utilise la clé PUBLIQUE du coffre : mettez la commande dans votre tâche planifiée, elle tourne la nuit sans autorisation ni humain. Les fichiers .sealbox peuvent partir en sauvegarde n'importe où.
3. Déchiffrer, à plusieurs. La commande crée une demande d'autorisation, chaque personne confirme depuis son espace protégé, puis la part de Seal est livrée et les fichiers sont restaurés. Le demandeur ne peut pas approuver sa propre demande.
# 1. Creer le coffre au portail -> vous obtenez CF-XXXXXX curl -O https://seal.lifaio.com/coffre/coffre-seal.mjs export SEAL_JETON="seal_..." # cle d'API creee au portail node coffre-seal.mjs init CF-XXXXXX "archives comptables" # -> IMPRIMEZ l'export de secours affiche (les deux parts). Une seule fois. # 2. Chiffrer — aucune autorisation : a mettre dans votre tache planifiee node coffre-seal.mjs encrypt /var/archives /var/archives-chiffrees # 3. Dechiffrer — exige N personnes distinctes export SEAL_EMPLOYES="E-AAA,E-BBB" # qui doit confirmer export SEAL_DEMANDEUR="E-CCC" # qui demande (ne peut PAS approuver) export SEAL_MINIMUM=2 # quorum ; absent = tous export SEAL_MINUTES=60 # delai laisse aux personnes export SEAL_FENETRE=15 # minutes pour recuperer apres le OUI export SEAL_DESCRIPTION="Restauration des archives comptables" node coffre-seal.mjs decrypt /var/archives-chiffrees /var/restaure
⚠️ Si vous perdez À LA FOIS votre fichier coffre.json et l'export de secours imprimé, vos fichiers sont DÉFINITIVEMENT illisibles. Ni vous ni Seal ne pouvez les récupérer. C'est le prix d'un chiffrement qui tient contre celui qui possède votre serveur.
À savoir, et nous préférons le dire nous-mêmes : une fois l'ouverture autorisée, la clé vit en mémoire pendant l'opération. Le Coffre protège contre le vol d'un instantané de vos fichiers — le cas MOVEit — pas contre un attaquant installé à demeure qui attendrait ce moment précis.
🗄️ Base de données lue en continu — le seuil de lecture
Le coffre ci-dessus protège des archives : il retient la clé. Une base transactionnelle, elle, a besoin de sa clé à CHAQUE requête — la retenir est impossible. On fait donc l'inverse : on donne la clé, mais ON LA COMPTE. Chaque enregistrement a sa propre clé, enveloppée par une clé dérivée chez Seal. À la lecture, votre code envoie l'ENVELOPPE SEULE — jamais le contenu — Seal la déballe et compte. Ce qui rend la séparation solide : l'usage normal lit un enregistrement à la fois, l'exfiltration en lit des millions. On sépare par la QUANTITÉ, que l'attaquant ne peut pas cacher, et non par l'identité, qu'il falsifie.
1. Écrire — jamais compté, jamais bloqué. proteger() chiffre la valeur avec une clé neuve et fait envelopper cette clé par Seal, qui ne voit jamais la valeur. Rangez l'objet retourné dans votre colonne. Aucune autorisation, aucun humain : votre application écrit comme avant.
2. Lire — compté. lireLot() envoie jusqu'à 500 enveloppes en UN appel, donc UN comptage. Faire 500 appels de 1 donne le même total au compteur mais 500 fois la latence : le lot n'est pas une optimisation, c'est la forme normale.
3. Au-delà du seuil. lire() lève une erreur de code coffreSeuilAtteint. Traitez-la comme un refus normal, pas comme une panne : c'est le mécanisme qui fonctionne. Une demande d'ouverture, N confirmations, et un crédit borné EN QUANTITÉ ET EN DURÉE s'ouvre. Ce crédit compte dans le même seuil — une seule cérémonie n'ouvre pas plus qu'elle n'annonce.
import { CoffreDb, ErreurCoffre } from "./coffre-db.mjs";
const c = new CoffreDb({ jeton: process.env.SEAL_JETON, coffre: "CF-XXXXXX" });
// ECRITURE — aucun appel humain, jamais bloquee, jamais comptee
const protege = await c.proteger(dossier.numeroDeCompte);
await sql`UPDATE clients SET compte = ${JSON.stringify(protege)} WHERE id = ${id}`;
// LECTURE EN LOT — 500 max, UN appel, UN comptage
try {
const clairs = await c.lireLot(lignes.map((l) => JSON.parse(l.compte)));
} catch (e) {
if (e.code === "coffreSeuilAtteint") {
// Refus NORMAL, pas une panne : le mecanisme fonctionne.
const o = await c.demanderOuverture({
description: "Export annuel des dossiers clients",
employes: ["E-AAA", "E-BBB"], // qui doit confirmer
demandeur: "E-CCC", // qui demande (ne peut PAS approuver)
minutes: 60,
});
await c.attendreOuverture(o.id, { credit: 100000, fenetre: 15 });
// ... puis relancer la lecture
} else { throw e; }
}
À savoir, et nous préférons le dire nous-mêmes : ici Seal peut déballer seul, c'est la condition pour ne déranger personne sous le seuil. Seal détient donc de quoi déballer, mais ne reçoit JAMAIS aucun contenu — le compromettre donne des clés sans données, compromettre votre serveur donne des données sans clés. Et un attaquant patient qui lit LENTEMENT, sous le seuil, pendant des mois, finira par tout obtenir : le seuil transforme une exfiltration de dix minutes en une exfiltration de dix ans et la rend visible au journal. Il ne la rend pas impossible.
🔒 La clé vaut un mot de passe : gardez-la dans votre gestionnaire de secrets, jamais dans le code source. Elle ne permet QUE les autorisations — ni portail, ni employés, ni paiements — et se révoque en un clic.