Proof of existence
A proof of existence records that a file with a given SHA-256 digest existed at a given instant, registered by your organization. No signers are involved: use it for meeting minutes, technical reports, source code, photos or proposals that need a provable date. The file itself is never uploaded to the platform when you send the digest, and never stored when you send the file.
What it is, and what it is not
/public/platform-key (Ed25519 and ML-DSA-87, both required). Any third
party can verify it. It does not attest authorship or the content of the file, and the date does not
come from an accredited time authority.The proof object
| Field | Type | Description |
|---|---|---|
id | uuid | Unique identifier of the proof. |
sha256 | string | Lowercase hex SHA-256 digest of the file (64 characters). |
label | string | null | Name you gave the record. Shown on the receipt and on public verification. |
file_name | string | null | Original file name, when provided. Never shown publicly. |
file_size | integer | null | File size in bytes, when provided. Never shown publicly. |
registered_at | datetime | Instant of registration, UTC, measured by the platform's server clock. |
verification_code | string | Public code, format XXXX-XXXX-XXXX. Same field as envelope codes at /verify. |
verify_url | string | Public verification page for this proof. |
evidence | object | Canonical payload the platform signed. Verify the signatures over its canonical JSON. |
platform_signature | string | Ed25519 signature over the canonical evidence, base64. |
platform_key_id | string | Identifier of the Ed25519 public key (see /public/platform-key). |
platform_signature_pq | string | ML-DSA-87 (NIST FIPS 204) signature over the same bytes, base64. |
platform_pq_key_id | string | Identifier of the ML-DSA-87 public key. |
created | boolean | true when this call created the record; false when it returned an existing proof of the same digest. |
Register a proof
/proofsSend either a JSON body with the digest you computed, or multipart/form-data with the
file under file (plus an optional label). Requires the proofs:write scope. Supports Idempotency-Key.
| JSON field | Type | Description |
|---|---|---|
sha256 required | string | Hex SHA-256 of the file. Computed on your side; the file never leaves your systems. |
label | string | Optional name, up to 200 characters. |
file_name | string | Optional, up to 255 characters. |
file_size | integer | Optional, in bytes. |
SHA=$(sha256sum minutes.pdf | cut -d' ' -f1)
curl -X POST 'https://api.sign.quathos.com/api/v1/proofs' \
-H 'Authorization: Bearer $QUATHOS_SIGN_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 6f1c0d3e-…' \
-d "{\"sha256\": \"$SHA\", \"label\": \"Board minutes 2026-09-06\"}"{
"id": "0191f2a0-…",
"sha256": "3f9a…c21e",
"label": "Board minutes 2026-09-06",
"file_name": null,
"file_size": null,
"language": "en",
"registered_at": "2026-09-06T14:08:22.517Z",
"verification_code": "K7MN-P4QR-2ST9",
"verify_url": "https://sign.quathos.com/verify?codigo=K7MN-P4QR-2ST9",
"evidence": {
"kind": "existence_proof",
"schema_version": 1,
"proof_id": "0191f2a0-…",
"tenant_id": "…",
"organization": "Acme Ltd",
"hash_algorithm": "SHA-256",
"sha256": "3f9a…c21e",
"label": "Board minutes 2026-09-06",
"file_name": null,
"file_size": null,
"registered_at": "2026-09-06T14:08:22.517000+00:00",
"verification_code": "K7MN-P4QR-2ST9",
"time_source": "platform_server_clock_utc",
"key_id": "a1b2c3d4e5f60718",
"pq_key_id": "f6e5d4c3b2a10897",
"hybrid_policy": "AND"
},
"platform_signature": "…",
"platform_key_id": "a1b2c3d4e5f60718",
"platform_signature_pq": "…",
"platform_pq_key_id": "f6e5d4c3b2a10897",
"evidence_signature_algorithms": ["Ed25519", "ML-DSA-87"],
"created": true
}Idempotent per organization
200 and created: false. The earliest record is the one that counts, and
nothing is charged again. Another organization can register the same digest: that is their own
proof, at their own instant.Cost
402 insufficient_credits and nothing is
recorded.List & retrieve
/proofsLists your proofs, newest first. search matches the digest prefix, the label or the
file name; limit/offset paginate. Requires proofs:read.
/proofs/{id}Returns a single proof. Requires proofs:read.
Receipt (PDF)
/proofs/{id}/receiptReturns the receipt as application/pdf: digest, instant (organization time zone and
UTC), verification code, QR code to the public page, key identifiers, the canonical payload and
step-by-step verification instructions. Generated on demand. Requires proofs:read.
curl -L 'https://api.sign.quathos.com/api/v1/proofs/{id}/receipt' \
-H 'Authorization: Bearer $QUATHOS_SIGN_TOKEN' \
-o proof-of-existence.pdfPublic verification
No token needed. Rate limited per IP, like envelope verification.
/public/proofs/{code}Looks up a proof by its verification code and re-verifies both signatures right now against the key registry. Returns the organization name, the digest, the instant, the canonical evidence, the signatures and the platform public keys. File name and size are never exposed publicly.
curl 'https://api.sign.quathos.com/api/v1/public/proofs/K7MN-P4QR-2ST9'/public/proofs/verifyChecks a file (or a digest) against the registered proofs. The file is hashed in memory and discarded. When several organizations registered the same digest, the earliest proof is returned.
# by file (hashed in memory, discarded)
curl -X POST 'https://api.sign.quathos.com/api/v1/public/proofs/verify' \
-F 'file=@minutes.pdf'
# by digest, if you already have it
curl -X POST 'https://api.sign.quathos.com/api/v1/public/proofs/verify' \
-F 'sha256=3f9a…c21e'To verify independently, serialize evidence as canonical JSON (sorted keys,
separators , and :, no whitespace, non-ASCII unescaped) and check both
signatures with the public keys whose key_id matches:
import base64, json
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.mldsa import MLDSA87PublicKey
proof = ... # GET /public/proofs/{code}
keys = ... # GET /public/platform-key → platform_keys, match by key_id
message = json.dumps(
proof["evidence"], sort_keys=True, separators=(",", ":"), ensure_ascii=False
).encode()
ed = Ed25519PublicKey.from_public_bytes(base64.b64decode(keys["ed25519"]))
ed.verify(base64.b64decode(proof["platform_signature"]), message)
pq = MLDSA87PublicKey.from_public_bytes(base64.b64decode(keys["ml-dsa-87"]))
pq.verify(base64.b64decode(proof["platform_signature_pq"]), message)
# both must pass (hybrid policy AND)The same page your customers use is /verify: the code field accepts proof codes, and the file upload also searches registered proofs.