Quickstart: protect a document in five minutes
Create permanent, independently verifiable, court-ready proof that a file existed at a specific moment. Preferred for AI pairs: install the agent skill, complete its short setup, then let it store selected files on immut. Alternate: hash-only CLI/API when you must not upload bytes.
Agent summary
- Goal: first protected document with court-ready proof.
- Preferred: Agent Skill
npx skills add enroh-ops/immut-agent→immut setup→ store viaPOST /documents.- Live auth: API key (documents + folders scopes) + workspace + upload consent. README.
- Alternate hash-only: CLI
immut proof create --file --sidecarorPOST /proofswith fieldhashonly.- Verify (no key):
GET https://backend.immut.io/api/public/verify/{txHash}or app.immut.io/verify.- Full agent playbook: agents.
- Raw twin: /docs/quickstart.md
What do you get?
- A permanent proof reference and document record on immut (store path), or proof ids from hash-only path.
- A court-ready certificate PDF (when entitled).
- Public verification anyone can run without an immut account.
- With the public skill: files organised in folders for your business objective.
Preferred path: Agent Skill
npx skills add enroh-ops/immut-agent
Then, in your project folder, say to the agent (this is a chat phrase, not a shell command):
immut setup
Complete the five-question setup (objective → accept immut folder proposal → connect AI tools → entire project default → optional always-protect folder). It installs a daily recurring check by default and announces what it installed. Before it can protect anything you need:
- An API key from Organization Settings → AI Agents with
documents:write,documents:read,folders:read,folders:write,certificates:read,workspaces:read. IMMUT_API_KEYandIMMUT_WORKSPACE_IDexported (list workspaces below).- Then say
immut sweepto protect what it finds, orimmut protectfor a single file.
Human + AI how-to: github.com/enroh-ops/immut-agent. Playbook: Agent playbook.
What do I need for live API calls?
- An API key at Organization Settings → AI Agents (API access must be enabled on the organisation). Keys look like
imut_live_+ 32 characters; shown once. - A workspace id:
export IMMUT_API_KEY="imut_live_..."
curl -s https://backend.immut.io/api/v1/workspaces \
-H "Authorization: Bearer $IMMUT_API_KEY"
{ "success": true, "data": [ { "_id": "6a343b4d0de29d485cd1f710", "name": "Default Workspace" } ] }
export IMMUT_WORKSPACE_ID="6a343b4d0de29d485cd1f710"
Store one file with REST (public skill path)
curl -s -X POST https://backend.immut.io/api/v1/documents \
-H "Authorization: Bearer $IMMUT_API_KEY" \
-F "file=@report-final.pdf" \
-F "workspace=$IMMUT_WORKSPACE_ID"
# optional: -F "folder=<folderObjectId>"
Create folders with POST /api/v1/folders (name, workspace, optional parentFolder). Scope: folders:write.
Alternate path: CLI hash-only (no file upload)
Fingerprints locally and calls POST /proofs. Not the public skill path.
npm install -g immut-cli
immut proof create --file report-final.pdf --sidecar
immut cert <proofId> -o certificate.pdf
immut verify <txHash> --file report-final.pdf
Every command accepts --json.
Alternate path: REST hash-only (POST /proofs)
How do I build the hash field?
The API field is named hash. It must be the 64-character lowercase hex fingerprint of the file (SHA-256 of the file bytes). Compute it locally. Do not upload the file.
# macOS / Linux
shasum -a 256 report-final.pdf | cut -d' ' -f1
// Node.js (streams, large files)
const crypto = require('crypto');
const fs = require('fs');
const hash = crypto.createHash('sha256');
fs.createReadStream('report-final.pdf')
.on('data', (d) => hash.update(d))
.on('end', () => console.log(hash.digest('hex')));
# Python
import hashlib
print(hashlib.sha256(open("report-final.pdf", "rb").read()).hexdigest())
How do I create the proof?
curl -s -X POST https://backend.immut.io/api/v1/proofs \
-H "Authorization: Bearer $IMMUT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"hash": "d28771e1bf6a7aa31693a4c81203b9cb1d51f9d1c20c1d7ad6db4035f4eaff6b",
"workspace": "'$IMMUT_WORKSPACE_ID'",
"fileName": "report-final.pdf",
"fileSize": 48213,
"mimeType": "application/pdf",
"metadata": { "description": "Q2 risk assessment, finalised version" }
}'
Successful create returns HTTP 201. Same content already proven in that workspace returns HTTP 200 with "alreadyProven": true and the original proof data.
{
"success": true,
"data": {
"proofId": "6a55f4b3f8878465844f8b43",
"txHash": "02790DA05EF2999C77A2B462CE0F7A1E11A52FB295F3AE3FF542112B12B7A8E5",
"verifyUrl": "https://livenet.xrpl.org/transactions/02790DA05EF2999C77A2B462CE0F7A1E11A52FB295F3AE3FF542112B12B7A8E5/detailed",
"certPath": "/api/v1/certificates/6a55f4b3f8878465844f8b43",
"ledger": "mainnet",
"ledgerIndex": 105588998,
"timestamp": "2026-07-14T08:34:59.412Z",
"hashScheme": "hmac-sha256-nonce-v3",
"proofCommitment": "9f2c41d8a0b3e5f6c7d8e9fa0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c",
"proofNonce": "4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f"
}
}
Field notes (codebase-accurate):
txHash: permanent reference used for public verification.verifyUrl: explorer link returned by the API for the transaction (opaque URL; optional for humans).proofNonce: required to verify under the default salted scheme. Store with the file owner. Recover viaGET /api/v1/proofs/{proofId}?includeSalt=trueor the certificate PDF.proofId: status polling and certificate download.ledger/ledgerIndex: opaque network metadata from the API.
What must I store?
Write the response next to the file, for example report-final.pdf.immut.json. Keep at least proofId, txHash, and proofNonce.
Proof creation is synchronous: a 201 means the proof is complete. Poll GET /api/v1/proofs/{proofId} only if a response was lost or status shows processing.
Download the court-ready certificate PDF (scope certificates:read):
curl -s https://backend.immut.io/api/v1/certificates/$PROOF_ID \
-H "Authorization: Bearer $IMMUT_API_KEY" -o certificate.pdf
How does anyone verify later?
Prefer the CLI or the browser verifier. Both need the original file; neither uploads it for the default flow.
immut verify 02790DA05EF2999C77A2B462CE0F7A1E11A52FB295F3AE3FF542112B12B7A8E5 --file report-final.pdf
Public keyless API:
curl -s https://backend.immut.io/api/public/verify/02790DA05EF2999C77A2B462CE0F7A1E11A52FB295F3AE3FF542112B12B7A8E5
Browser: app.immut.io/verify.
Do not treat verifyUrl from the create response as the only verify path. For independent checks use the public verify API, CLI, or web page. Cryptographic details of salted vs plain schemes: Verify a document.
What happens if I prove the same file twice?
Idempotent per fingerprint and workspace. A second POST with the same hash returns 200 with "alreadyProven": true and the original data (including nonce). Nothing is written twice and nothing extra is charged.
What errors should my integration handle?
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_HASH | hash is not a 64-character hex fingerprint |
| 400 | INVALID_WORKSPACE | workspace missing or not a valid id |
| 401 | Missing or invalid API key | |
| 403 | API_ACCESS_DISABLED | Plan does not include API access |
| 403 | Key lacks required scope | |
| 429 | Rate limit; respect Retry-After | |
| 502 | XRPL_ERROR | Proof network error; safe to retry (failed attempt is rolled back) |
Rate limits: 60 requests per minute and 10,000 per day per key.
Where do I go next?
- API reference (raw: /docs/api.md)
- Agent playbook (raw: /docs/agents.md)
- Verify a document (raw: /docs/verify.md)
- Machine bootstrap:
GET https://backend.immut.io/api/v1/docs