# Quickstart: create a blockchain proof from a file hash immut records a commitment to your file's SHA-256 hash on the XRP Ledger. The file never leaves your machine: you hash it locally and send only the 64-character hex digest. This page takes you from nothing to a verified proof using curl. ## What do I need before I start? Two things, both one-time setup: 1. **An API key.** A human account owner creates one at [app.immut.io/account?tab=api-keys](https://app.immut.io/account?tab=api-keys) (Professional and Enterprise plans include API access). For agents, request only the scopes `documents:write` and `certificates:read`. Keys look like `imut_live_` followed by 32 characters and are shown once at creation. 2. **A workspace id.** Every proof belongs to a workspace. List yours once and save the id: ```bash export IMMUT_API_KEY="imut_live_..." curl -s https://backend.immut.io/api/v1/workspaces \ -H "Authorization: Bearer $IMMUT_API_KEY" ``` ```json { "success": true, "data": [ { "_id": "6a343b4d0de29d485cd1f710", "name": "Default Workspace" } ] } ``` Save the `_id` of the workspace you want proofs to live in: ```bash export IMMUT_WORKSPACE_ID="6a343b4d0de29d485cd1f710" ``` ## Step 1: how do I hash the file locally? Any SHA-256 implementation works. The digest must be 64 hex characters. ```bash # macOS / Linux shasum -a 256 report-final.pdf | cut -d' ' -f1 ``` ```javascript // Node.js (streams, works for 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 # Python import hashlib print(hashlib.sha256(open("report-final.pdf", "rb").read()).hexdigest()) ``` ## Step 2: how do I create the proof? POST the hash and workspace id. `fileName`, `fileSize`, `mimeType`, and `metadata.description` are optional but make certificates and listings more useful. ```bash 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" } }' ``` A successful response returns HTTP 201: ```json { "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" } } ``` ## Step 3: what must I store? Write the response next to the file, for example as a `report-final.pdf.immut.json` sidecar. Three fields matter forever: - `txHash`: the permanent anchor on the XRP Ledger. - `proofNonce`: **required to verify a salted proof.** Under the default scheme the value on the public ledger is `HMAC-SHA-256(proofNonce, sha256(file))`, not the raw file hash, so verification without the nonce is impossible. If you lose your local copy, recover it via `GET /api/v1/proofs/{proofId}?includeSalt=true` or from the certificate PDF, which embeds it. - `proofId`: needed for status polling and certificate download. Anchoring is synchronous: a 201 response means the transaction is already validated on the ledger and `blockchainStatus` is `completed`. Polling `GET /api/v1/proofs/{proofId}` exists for the rare case where a response was lost mid-flight or status shows `processing`. Download the court-ready certificate PDF any time (scope `certificates:read`): ```bash curl -s https://backend.immut.io/api/v1/certificates/$PROOF_ID \ -H "Authorization: Bearer $IMMUT_API_KEY" -o certificate.pdf ``` ## Step 4: how does anyone verify the proof later? Verification is public and needs no API key: ```bash curl -s https://backend.immut.io/api/public/verify/02790DA05EF2999C77A2B462CE0F7A1E11A52FB295F3AE3FF542112B12B7A8E5 ``` ```json { "success": true, "data": { "transactionHash": "02790DA05EF2999C77A2B462CE0F7A1E11A52FB295F3AE3FF542112B12B7A8E5", "network": "mainnet", "verified": true, "ledgerIndex": 105588998, "ledgerCloseTime": "2026-07-14T08:35:02.000Z", "memo": { "fileHash": "...", "hashScheme": "hmac-sha256-nonce-v3" } } } ``` Then confirm the file matches. For the default salted scheme: recompute `sha256(file)`, compute `HMAC-SHA-256` with the stored `proofNonce` as key over the raw digest bytes, and compare the result with `memo.fileHash`. For plain-scheme proofs (`sha256-plain-v1`), compare `sha256(file)` with `memo.fileHash` directly. Non-technical verifiers can use the web page at [app.immut.io/verify](https://app.immut.io/verify), which does this in the browser. ## What happens if I prove the same file twice? The endpoint is idempotent per hash and workspace. A second POST with the same hash returns HTTP 200 with `"alreadyProven": true` and the original proof data, including the stored 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 SHA-256 digest | | 400 | `INVALID_WORKSPACE` | `workspace` is missing or not a valid id | | 401 | | Missing or invalid API key | | 403 | `API_ACCESS_DISABLED` | The organisation's plan does not include API access | | 403 | | The key lacks the required scope | | 429 | | Rate limit exceeded. Respect the `Retry-After` header and back off | Rate limits are 60 requests per minute and 10,000 per day per key. ## Where do I go next? - [API reference](/docs/api) for every endpoint including status polling and certificates - [Agent playbook](/docs/agents) if an AI agent will run this flow autonomously - [Security](/docs/security) for scopes, the commitment scheme, and data handling rules