LIFAIO
Seal LIFAIO
verified by LIFAIO

Integration kit — Seal API

You do not need code to seal a work. Everything is in /moi (person) or in your portal (organisation). This page is for automating, not for sealing.
Seal without code →

⚠️ The description is the ONLY text that travels to Seal — and it is kept there, because the person approving must see what they are approving. Never put a name, an account number or personal data in it: put a reference your own system can resolve (“Loan #4521”, “File C-90218”).

Two HTTPS calls are enough: create an authorization request, then read its state until the verdict. OUTBOUND calls only, no port to open, and none of your company's data travels — only the description you choose to write.

Before you start: create a key in the portal (Authorizations card → “Create a key”), and allow outbound calls to seal.lifaio.com if your network filters egress.

1. Create the request

POST /api/autorisation-creer — fields: jeton (your key), description (what will be authorized, visible to approvers), employes (E- codes required, CSV export in the portal), minutes (validity, 5 to 1440), minimum (quorum: 2 of 4 for example; absent = all), refusBloquant (true = one refusal stops everything). Response: {ok, id, url}.

2. Wait for the verdict

GET /api/autorisation?id=AU-XXXXXXXX — returns etat: en_attente, autorisee, refusee or expiree, with confirmations, minimum, requises and the seal once authorized. Poll every 5 to 30 seconds.

3. Decide and archive

Archive the full response (seal included) in the operation's file: that is YOUR proof. Also print the public link /a/AU-… in your documents — anyone can re-verify it at Seal years later.

Golden rule: if the state is not “autorisee”, deliver nothing, disburse nothing, copy nothing.

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 ...

Variant: your own shuttle (to isolate the core software)

If your core software must call no external service, do not integrate it directly: have it drop a file into a folder, and hand the relay to a small scheduled task that YOU write and control. Twenty lines are enough — here is the pattern.

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

🎬 Media authenticity — seal your video and audio

A genuine video of your spokesperson, with the voice replaced by a clone saying something else: not a pixel has moved, and the lie is perfect. The media seal binds the IMAGE AND THE SOUND of each interval together. Replacing either one, or simply shifting one against the other, breaks the match.

1. The computation happens ON YOUR SIDE. The library splits the work and computes one fingerprint per half-second, in the publisher's browser. Your video is never sent to us: only fingerprints travel.

2. Request the ceremony. You submit the LIST of works to be sealed — fixed at that moment. A second person sees how many and which ones, then approves. A work absent from that list is refused, and the refusal is logged.

3. Seal. One ceremony can cover one work or ten thousand. Each work can be sealed only once under the same authorisation.

4. Declare where the work may appear. Your accounts and domains. An authentic work presented elsewhere is NOT confirmed: the result signals unauthorised republication and names the legitimate locations.

5. Anyone verifies, free, without an account. Verification is free and unlimited. You approve nothing at read time — a work seen a billion times asks nothing of you.

Worth knowing, and we would rather say it ourselves: platform re-encoding is absorbed, but an alteration both very small and very brief can slip under the threshold. DRM-protected platforms (Netflix, Disney+, Prime…) let no browser read their stream: we verify the file you hold or a copy in circulation, never the stream as broadcast. And the seal confirms WHO published and that NOTHING was altered — never that what is said is true.

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.

🔐 Vault — encrypt your archives, open them together

An attacker who takes over your server passes BENEATH your application: no approval step ever sees them, they read your files directly. The only defence is that what they carry off be unreadable. The Vault encrypts your archives and exports; ENCRYPTING asks nothing, DECRYPTING requires confirmation from several people. Seal holds NO key: its share is recomputed on demand, there is nothing to steal from us.

1. Create the vault. In your portal, Vault card: give it a name and you get a CF-XXXXXX identifier. Download the tool, create an API key, then run “init”. PRINT the recovery export shown — it will never be shown twice.

2. Encrypt, continuously and with nobody. Encryption uses the vault's PUBLIC key: put the command in your scheduled task and it runs overnight with no authorisation and no human. The .sealbox files can go to any backup anywhere.

3. Decrypt, together. The command creates an authorisation request, each person confirms from their protected space, then Seal's share is delivered and the files are restored. The requester cannot approve their own request.

# 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

⚠️ If you lose BOTH your coffre.json file and the printed recovery export, your files are PERMANENTLY unreadable. Neither you nor Seal can recover them. That is the price of encryption that holds against whoever owns your server.

Worth knowing, and we prefer to say it ourselves: once an opening is authorised, the key lives in memory during the operation. The Vault protects against theft of a snapshot of your files — the MOVEit case — not against an attacker permanently installed who would wait for that exact moment.

🗄️ Database read continuously — the read threshold

The vault above protects archives: it withholds the key. A transactional database needs its key at EVERY request — withholding is impossible. So we do the opposite: we give the key, but WE COUNT IT. Each record has its own key, wrapped under a key derived at Seal. On reading, your code sends the ENVELOPE ONLY — never the content — Seal unwraps it and counts. What makes the separation solid: ordinary use reads one record at a time, extraction reads millions. We separate by QUANTITY, which the attacker cannot conceal, and not by identity, which he forges.

1. Writing — never counted, never blocked. proteger() encrypts the value under a fresh key and has Seal wrap that key, never seeing the value. Store the returned object in your column. No authorisation, no human: your application writes as before.

2. Reading — counted. lireLot() sends up to 500 envelopes in ONE call, therefore ONE count. Making 500 calls of 1 gives the same total to the counter but 500 times the latency: the batch is not an optimisation, it is the normal form.

3. Beyond the threshold. lire() throws an error whose code is coffreSeuilAtteint. Treat it as an ordinary refusal, not a failure: it is the mechanism working. An opening request, N confirmations, and a credit bounded IN QUANTITY AND IN TIME is granted. That credit counts against the same threshold — one ceremony does not open more than it announces.

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

Worth knowing, and we prefer to say it ourselves: here Seal can unwrap alone, which is the condition for disturbing nobody below the threshold. Seal therefore holds the means to unwrap, but NEVER receives any content — compromising it yields keys without data, compromising your server yields data without keys. And a patient attacker reading SLOWLY, below the threshold, for months, will eventually obtain everything: the threshold turns a ten-minute exfiltration into a ten-year one and makes it visible in the log. It does not make it impossible.

🔒 The key is a password: keep it in your secret manager, never in source code. It allows ONLY authorizations — no portal, no employees, no payments — and is revoked in one click.

🌐 Available in 18 languages
© Technologies Marco Prive — UK patents pending — GB2619490.2 · GB2619948.9 · GB2620153.3 · GB2620211.9 · GB2620220.0 · GB2620253.1 · GB2620315.8 · GB2620629.2 · GB2621074.0 · GB2621382.7
Terms · Privacy · Méthode · ⏱️ Séquentiel · Refunds · Site protection · Cookies · Data processing · Nothing to steal · Measurement · Tickets: how it works
Seal LIFAIO v3.51.0