Seal and verify
Sealing takes a snapshot of a document as it stands, together with the record of how it was written, and gives it a short link at proof.exalto.ai. The text can then go anywhere, such as an email, a submission form or a pull request, and the link goes with it. Anyone who opens the link sees the sealed text, where its words came from, and whether a person was there, unless its owners made its seals private.
By the end of this guide, your users can seal from the editor or from your own button, your app knows about every seal, and you know what readers will see.
Seal from the editor
Section titled “Seal from the editor”Owners see a Seal button in the editor (your own button can open it with editor.openSeal()). It seals on confirmation, and is where owners choose who reads the document’s seals and withdraw them. You don’t need to build anything for this, but your app should hear about it:
editor.on("seal", ({ code, url, line }) => { sealList.add({ code, url, line });});The same seal also arrives as a seal.created webhook, which is the one to rely on for anything you store: the page may be closed before the event reaches it. See Send notifications.
Seal from your server
Section titled “Seal from your server”To seal from your own button, or as part of your own flow such as submitting an assignment, call the API with the user who is sealing. Only one of the document’s owners can seal, and not while it has pending suggestions.
app.post("/assignments/:id/submit", requireSignIn, async (req, res) => { const assignment = await db.assignments.find(req.params.id);
const seal = await proof.seals.create(assignment.documentId, { user: req.user.proofUserId });
await db.submissions.insert({ assignment: assignment.id, seal: seal.code, at: seal.sealed_at }); res.json({ url: seal.url, line: seal.line });});The seal comes back with everything a reader will see:
{ "code": "AH271NSHS", "url": "https://proof.exalto.ai/AH271NSHS", "document": "doc_8Hq2Lm4R", "version": 1, "sealed_at": "2026-10-13T23:31:04Z", "sealed_by": "usr_5Kq2Lm8R", "method": "v0.2", "line": "proof.exalto.ai/AH271NSHS · 87% typed/shaped · 8% AI · 5% external · 4 prompts · 2 sessions, 1hr48min", "composition": { "typed": 71, "shaped": 16, "ai": 8, "external": 5 }, "presence": { "share": 97, "verified_share": 0, "verified": false }, "prompts": 4, "sittings": 2, "duration": "1hr48min", "attested_by": { "platform": "acme", "key_ids": ["key_2wLx7Hq"] }, "withdrawn_at": null}Sealing a document that reads exactly as its newest seal is refused with 409 unchanged, so a form submitted twice doesn’t seal twice.
Before sealing
Section titled “Before sealing”To show where a document stands before anyone seals it, such as on a submit screen, read its live provenance. It uses the same method a seal would, but nothing is kept:
const reading = await proof.documents.provenance("doc_8Hq2Lm4R");reading.composition; // { typed: 71.4, shaped: 15.9, ai: 7.8, external: 4.9 }reading.presence; // { share: 97, verified_share: 0, verified: false }The editor’s own seal, in the corner, shows the same reading as people write.
Show the seal line
Section titled “Show the seal line”The seal line is one string meant to travel with the text: the link, then the shares. Show it with a copy button wherever your users finish their work.
proof.exalto.ai/AH271NSHS · 87% typed/shaped · 8% AI · 5% external · 4 prompts · 2 sessions, 1hr48minWhen check-ins vouched for every sitting, the line ends (verified); when they vouched for some, it says how much, such as (73% verified). Show seal.line as it comes: don’t rebuild it from the numbers, since rounding and wording are part of the method.
Public seal links unfurl in chat apps and social sites with an image of the seal and its numbers.
Seal again
Section titled “Seal again”A seal never changes. People can keep editing after sealing, and seal again whenever they like: each seal gets its own code and link, and its version counts up for that document. List them all with:
const seals = await proof.seals.list("doc_8Hq2Lm4R");Withdraw a seal
Section titled “Withdraw a seal”An owner can withdraw a seal, for example one shared by mistake. Its link then says it was withdrawn, instead of showing the text. A seal can’t be edited, only withdrawn.
await proof.seals.withdraw("AH271NSHS");Deleting a document withdraws all its seals. You hear about each through seal.withdrawn. While a document is in its owners’ trash, its seals are down, and come back when it’s restored.
Private seals
Section titled “Private seals”In the seal dialog, owners choose who reads a document’s seals, every version alike: anyone with the link, or only its members, the readers they name by email (or *@domain for everyone at a domain), and whoever has a password, if they set one.
A private seal’s page offers the password, and signing in with your platform. To offer that, set a reader URL with PATCH /v1/platform, and give it a page that works like your consent page:
app.get("/proof/read", requireSignIn, async (req, res) => { const user = req.user; const { redirect_url } = await proof.readerRequests.approve(String(req.query.request), { user: await proofUserId(user), email: user.emailVerified ? user.email : null, }); res.redirect(redirect_url);});Proof lets them in if they’re a member or their email is on the list, and remembers them in that browser for 30 days. Pass the email only if you’ve verified it. See Reader requests.
What a reader sees
Section titled “What a reader sees”-
The sealed text, exactly as it stood, so a reader can compare it with what they were sent.
-
The seal. The outer ring is the whole document by where its words came from: typed by people, AI text people shaped, AI text kept as written, and external (pasted or imported). The inner ring is presence, in electric blue where check-ins vouched for it.
-
The breakdown. Prompts and agent work, who did what, the sittings on a timeline, which signals were on, and the date and method the seal was made under.
-
Who vouches for what. The page names your platform as where it was written, which vouches for who wrote it. Proof vouches for how the text was written, because it recorded that itself. Your platform vouches for who the people are, because it signs them in. Check-ins are shown apart, as the part neither of you could produce.
Each seal records the method version it was made under (v0.2 above). When Proof’s method improves, existing seals keep their numbers; the page adds the reading under the newer method beside them, labelled as such, and never rewrites a seal.
Each seal keeps the ids of the secret keys that started its writing sessions (attested_by.key_ids), so a seal written with a key you later find leaked can be traced to it. See Test and go live.
- Seals: what each number means.
- Trust model: what a seal proves, and what it doesn’t.
- Seals in the API reference.