Home
Home/Developers/Documents/anchoring
Ed25519 signaturesSHA-256 hashes8 SDKs
Status
Sign inStart free
Home
Start here
Overview
Authentication
Errors
FAQ
API
Events
Canonical JSON and hashes
Documents
Anchoring a file
AI attestations
Attestation schemaVerifying attestations
Traces
Sealing a trace
Audit logs
OrganizationsStreamsPortalPublic proofEvent schemaExporting eventsIntegrationsEmbeddable viewer
All endpoints
Reference
SDKs
Verification
Docs · Documents

Anchoring a file

Hash the file, send the hash, get a signed record. Later, hash the file again and compare.

Anchor endpointDocuments API
sha-256 · 3a352297…2592
sha-256 · cc6246c9…ec89
What an anchor is

You hash the file on your side and send the hash. The file itself is optional.

The backend takes one document from your monthly quota first, so a rejected request still counts. Then it checks the hash format and whether the hash is already anchored, queues the record and answers 201 with an event_id.

A writer signs a short payload with the Ed25519 key of your organization and writes the row to the event ledger.

To verify later, Invoance needs only the hash. It never needs the file.

Signed payload
vPayload version. Always 2.
tenant_idYour tenant id.
actor_typeAlways api_key.
actor_idThe id of the API key that anchored the file.
event_typeAs sent, or document_anchor.
document_hash_hexThe hash as stored, lowercase.
tsThe created_at value from the 201 response. It differs from the created_at that get returns, which is the row's anchored_at.

Compact JSON in this order, signed as is with Ed25519. get returns it as signed_payload_b64, next to signature_b64 and public_key_b64.

  1. Hash

    SHA-256 over the file bytes, written as 64 hex characters.

  2. Send

    POST the hash. Add a reference, an event type, metadata, the file itself or a trace id if you want them.

  3. Check

    One document leaves your monthly quota. The hash is validated. A hash already anchored for your tenant gets 409.

  4. Sign and store

    A writer signs the payload with your Ed25519 key, writes the ledger row and stores the original if you sent one.

  5. Verify

    Hash the file again and call verify, or open the public proof page. Only the hash travels.

Hashing on your side

Hash the raw bytes of the file with SHA-256. Do not hash a base64 string or a text copy.

The digest is 64 hex characters. The backend trims it and lowercases it before validation and storage. Anything else gets 400 invalid_document_hash.

One changed byte gives a different digest, so keep the exact file you hashed.

Every SDK has a file helper that reads the file, hashes it and calls anchor for you. See the anchor endpoint.

import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";

const bytes = readFileSync("./INV-2026-0917.pdf");
const documentHash = createHash("sha256").update(bytes).digest("hex");

console.log(documentHash); // 64 lowercase hex characters
Sending the original

Include original_bytes_b64 when you want Invoance to keep a copy you can download later. Leave it out to anchor the hash only.

It must be standard base64 with padding, else 400 invalid_original_bytes_base64. The backend decodes it and rejects an empty result and anything over 5 MiB.

The decoded bytes must hash to document_hash, or the request gets 400 original_bytes_hash_mismatch.

  • The handler validates the bytes and passes them to the writer. It does not store them.
  • The object key is tenants/{tenant_id}/documents/{yyyy}/{mm}/{dd}/{event_id}.bin in the documents bucket.
  • The writer decodes the bytes, checks the hash again and uploads them to object storage.
  • It records the object key, the byte size and your plan's retention_days, and adds the size to your storage usage.
  • No worker acts on that retention today: document records and originals are neither sealed nor removed when it ends.
Request limits
document_hashexactly 64 hexadecimal characters (a SHA-256 digest)
metadataup to 8,192 bytes once serialized as JSON
original_bytes_b641 byte to 5 MiB after base64 decoding, and the decoded bytes must hash to document_hash
Request body2 MB, the framework default; the API routes set no override, so an uploaded original is capped at about 1.5 MB decoded in practice
document_refno length check; only the request body limit applies. Defaults to Untitled
Request size today
  • The customer API router sets no body limit, so the framework default of 2 MB applies before the handler runs.
  • Base64 adds a third, so a body that fits carries about 1.5 MB of file. The 5 MiB check is never reached above that.
  • A larger body gets a plain-text 413, not the JSON error shape.
  • Anchor larger files hash only and keep the original yourself.
Getting the original backEndpoint

The response body is the file exactly as uploaded, not JSON. There is no file name; use document_ref as yours.

has_original on get and list tells you whether one exists before you call.

PathGET /v1/document/{event_id}/original
Key scoperead, or write
BodyThe file exactly as uploaded
Content-Typeapplication/octet-stream
Content-Dispositioninline, with no file name
404 document_not_foundNo original was uploaded, the record belongs to another tenant, or it is not active
QuotaNone consumed
curl https://api.invoance.com/v1/document/7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4/original \
  -H "Authorization: Bearer $INVOANCE_API_KEY" \
  -o ./INV-2026-0917.pdf
Checking a file later

Hash the file you hold now, the same way as before. Then pick a check.

POST /v1/document/{event_id}/verifyWith your key. Returns match_result, both hashes, anchored_at, document_ref and the organization. Takes one API verification from your quota. Does not check the signature.
POST /v1/proof/{event_id}/verifyNo key. Returns match_result and signature_valid with both hashes and anchored_at. Recorded as a verification for the issuer.
www.invoance.com/proof/document/{event_id}No account. Hashes the file in the browser and sends only the hash to the public verify call.
Hash compareSHA-256 · byte for byte
Anchored · 2026-03-02invoice_1042.pdf9b74c98e…5d21af06
Presented todayinvoice_1042.pdf9b74c98e…5d21af06Hashes match
Edited copyinvoice_1042.pdf41e07fa2…c8836b1dNo match · one byte changed
What the result means
match_resultTrue when the hash you sent equals the anchored hash byte for byte. The file you hold is the file that was anchored.
signature_validTrue when the stored signature verifies over the signed payload with the key registered for the tenant. False when no key is registered. Public verify only.

To check the signature yourself, fetch the record with get and verify signature_b64 over signed_payload_b64 with public_key_b64.

Public verify response
event_iduuid · required

The anchor checked.

match_resultboolean · required

True when the submitted hash equals the anchored document hash.

signature_validboolean · required

True when the stored signature verifies over the signed bytes with the tenant's registered key; false when no key is registered.

anchored_hashhex · required

The document hash stored at anchor time.

submitted_hashhex · required

The hash compared, lowercased.

anchored_attimestamp (ISO 8601) · required

When the document was anchored.

{
  "document_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
}
EndpointsDocuments API
Documents
POST/v1/document/anchorAnchor a documentGET/v1/documentList documentsGET/v1/document/{event_id}Get a documentGET/v1/document/{event_id}/originalDownload the originalPOST/v1/document/{event_id}/verifyVerify a document hash
Questions
Can I anchor the same file twice?
No. The hash is unique per tenant, so a second anchor gets 409 document_already_anchored. New bytes give a new hash and a new record.
Can I add the original later?
No. The original is stored only with the anchor request. A later request with the same hash gets 409.
Why does get return 404 right after anchor?
The 201 means the record is on the writer queue. The ledger row appears shortly after. Retry the get.
What does original return for a hash-only anchor?
404 document_not_found. Check has_original on get or list before you call.

Proof infrastructure. Records are hashed, signed with your organization's Ed25519 key, and stored append-only, so anyone can check them later.

Products

  • Audit Logs
  • Event Ledger
  • AI Attestation
  • Document Anchoring
  • Traces

Developers

  • Documentation
  • API reference
  • SDKs
  • How it works
  • How traces seal
  • System status

Verify

  • Audit Log
  • Event
  • AI Attestation
  • Document
  • Trace

Company

  • Why Invoance
  • Pricing
  • Security
  • Compliance teams
  • Finance teams
  • Partners
  • Resources
  • Help center
  • Contact
© 2026 Invoance
PrivacyLegal noticeLegal FAQGitHubLinkedInX