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

The instant is measured in UTC by the platform's server clock and sealed with the platform keys published at /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

FieldTypeDescription
id uuidUnique identifier of the proof.
sha256 stringLowercase hex SHA-256 digest of the file (64 characters).
label string | nullName you gave the record. Shown on the receipt and on public verification.
file_name string | nullOriginal file name, when provided. Never shown publicly.
file_size integer | nullFile size in bytes, when provided. Never shown publicly.
registered_at datetimeInstant of registration, UTC, measured by the platform's server clock.
verification_code stringPublic code, format XXXX-XXXX-XXXX. Same field as envelope codes at /verify.
verify_url stringPublic verification page for this proof.
evidence objectCanonical payload the platform signed. Verify the signatures over its canonical JSON.
platform_signature stringEd25519 signature over the canonical evidence, base64.
platform_key_id stringIdentifier of the Ed25519 public key (see /public/platform-key).
platform_signature_pq stringML-DSA-87 (NIST FIPS 204) signature over the same bytes, base64.
platform_pq_key_id stringIdentifier of the ML-DSA-87 public key.
created booleantrue when this call created the record; false when it returned an existing proof of the same digest.

Register a proof

POST /proofs

Send 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 fieldTypeDescription
sha256 requiredstringHex SHA-256 of the file. Computed on your side; the file never leaves your systems.
label stringOptional name, up to 200 characters.
file_name stringOptional, up to 255 characters.
file_size integerOptional, 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\"}"
response
{
  "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

Registering a digest your organization already registered returns the existing proof with status 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

Each new proof consumes one signature credit from your balance, the same unit an envelope signer consumes. Without balance the API answers 402 insufficient_credits and nothing is recorded.

List & retrieve

GET /proofs

Lists your proofs, newest first. search matches the digest prefix, the label or the file name; limit/offset paginate. Requires proofs:read.

GET /proofs/{id}

Returns a single proof. Requires proofs:read.

Receipt (PDF)

GET /proofs/{id}/receipt

Returns 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
curl -L 'https://api.sign.quathos.com/api/v1/proofs/{id}/receipt' \
  -H 'Authorization: Bearer $QUATHOS_SIGN_TOKEN' \
  -o proof-of-existence.pdf

Public verification

No token needed. Rate limited per IP, like envelope verification.

GET /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
curl 'https://api.sign.quathos.com/api/v1/public/proofs/K7MN-P4QR-2ST9'
POST /public/proofs/verify

Checks 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.

cURL
# 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:

Python
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.