Developers

API,
for verifiable trust.

Reference endpoints for timestamping, signature verification, range-proof verification, and issuer-gated partner Auth.

Access Model

Public timestamp endpoints do not require accounts or API keys. Claim creation requires an Ed25519 signature plus a challenge proof-of-work, and rate limits apply. Production Auth challenge creation is unavailable until a trusted issuer and an exact client redirect allowlist are configured.

Receipt lists are client-side. Persist receipts in your app (localStorage, extension storage, or your database). GET /receipts/:pubkey returns 410 Gone. Use GET /proof/:receiptId for individual verification.

POST /api/v1/auth/challenge

Create a short-lived hosted Auth challenge after trusted issuer support and the client are configured. redirectUri must exactly match the client's server-side allowlist. Redirect only to the loginUrl returned by this endpoint.

Request Body

{
  "clientId": "hemsted_prod",
  "redirectUri": "https://hemsted.se/auth/callback",
  "scope": ["identity"],
  "state": "opaque-csrf-state"
}

Related endpoints

GET /api/v1/auth/challenge/:id, POST /api/v1/auth/prove, POST /api/v1/auth/verify, and GET /api/v1/auth/userinfo.

GET /challenge

Get a challenge nonce and difficulty for claim proof-of-work. Challenges are single-use and expire server-side.

Response

{
  "challenge": "a1b2c3d4e5f6...",
  "difficulty": 16,
  "expires": "2026-05-19T12:15:00.000Z"
}
POST /claim

Submit a signed hash for timestamping.

Request Body

{
  "hash": "sha256-hex-of-content",
  "signature": "ed25519-signature-hex",
  "pubkey": "ed25519-public-key-hex",
  "pow": {
    "challenge": "challenge-from-/challenge",
    "nonce": "nonce-that-meets-difficulty"
  },
  "filename": "optional-display-name.pdf",
  "notify_email": "[email protected]",
  "notify_webhook": "https://your.app/hooks/otrust",
  "notify_webhook_secret": "optional-hmac-secret"
}

notify_email triggers a one-time Bitcoin confirmation email (stored separately, deleted after send). notify_webhook must be HTTPS; OTRUST POSTs once on confirm with optional X-OTRUST-Signature HMAC. See webhook docs.

Response

{
  "status": "ok",
  "timestamp": "2026-05-19T12:00:00.000Z",
  "receipt_id": "ot_abc123xyz",
  "blockchain_status": "submitted",
  "ots_proof": true
}
POST /claim/bulk

Submit up to 100 signed hashes with one proof-of-work challenge.

Request Body

{
  "pow": { "challenge": "...", "nonce": "..." },
  "claims": [
    {
      "hash": "sha256-hex",
      "signature": "ed25519-signature-hex",
      "pubkey": "ed25519-public-key-hex"
    }
  ],
  "notify_email": "[email protected]",
  "notify_webhook": "https://your.app/hooks/otrust"
}

One notify_email / notify_webhook applies to all newly created receipts in the batch.

POST /verify

Check whether a SHA-256 hash has timestamp claims in OTRUST.

Request Body

{
  "hash": "sha256-hex-of-content"
}

Response

{
  "status": "found",
  "hash": "sha256-hex",
  "claims": [
    {
      "receipt_id": "ot_abc123xyz",
      "pubkey": "ed25519-public-key-hex",
      "timestamp": "2026-05-19T12:00:00.000Z",
      "blockchain_confirmed": false,
      "proof_url": "/proof/ot_abc123xyz"
    }
  ]
}
POST /verify/bulk

Verify up to 100 hashes in one request.

Request Body

{
  "hashes": ["sha256-hex-1", "sha256-hex-2"]
}

Response

{
  "status": "ok",
  "results": [
    { "hash": "...", "status": "found", "claims": [] },
    { "hash": "...", "status": "not_found" }
  ]
}
GET /stats

Public transparency metrics: records, anchored claims, activity counters, latest Bitcoin block. See also Trust Log.

GET /stats/badges.json

Compact stats for embed widgets (/embed). Cached 5 minutes.

{
  "service": "OTRUST",
  "anchored_records": 1234,
  "latest_block": 912345,
  "updated_at": "2026-07-04T12:00:00.000Z"
}
GET /lookup/:hash

Browser-friendly lookup for a SHA-256 hash. Prefer POST /verify when you want to keep hashes out of URLs and logs.

Response

{
  "status": "found",
  "claims": []
}
GONE /receipts/:pubkey

Removed. Always returns 410 Gone with error: "local_history_only". Store receipt IDs client-side when you create timestamps.

GET /proof/:receiptId

Return a human-readable timestamp record page by default. Add ?format=json for metadata or ?format=ots to download the raw OpenTimestamps receipt.

Response

{
  "status": "pending",
  "receipt_id": "ot_abc123xyz",
  "hash": "sha256-hex",
  "download_url": "/proof/ot_abc123xyz?format=ots"
}
POST /verify/signature

Verify that an Ed25519 signature matches a hash and public key.

Request Body

{
  "hash": "sha256-hex-of-content",
  "signature": "ed25519-signature-hex",
  "pubkey": "ed25519-public-key-hex"
}
POST /verify/blockchain

Verify a SHA-256 hash against a base64 OpenTimestamps receipt payload.

Request Body

{
  "hash": "sha256-hex-of-content",
  "ots_proof": "base64-encoded-ots-file"
}

Example Workflow

Complete example using curl:

# 1. Hash your file locally
HASH=$(sha256sum document.pdf | cut -d' ' -f1)

# 2. Sign the hash with your Ed25519 key

# 3. Submit the claim
curl -X POST https://www.otrust.eu/claim \
  -H "Content-Type: application/json" \
  -d '{"hash":"'$HASH'","signature":"...","pubkey":"...","pow":{"challenge":"...","nonce":"..."}}'

# 4. Check status
curl https://www.otrust.eu/proof/ot_abc123xyz?format=json

# 5. Download OTS receipt
curl https://www.otrust.eu/proof/ot_abc123xyz?format=ots \
  -o document.pdf.ots

File Upload Policy

When using /sign/upload, certain file types are blocked for security reasons.

Blocked Extensions

Server-executable files that could pose security risks:

CategoryExtensions
Windows Executables.exe .dll .bat .cmd .com .msi .scr
Shell Scripts.sh .bash .zsh .csh
PHP.php .php3 .php4 .php5 .phtml .phar
ASP.NET.asp .aspx .ashx .asmx .ascx
Java Server.jsp .jspx .jsf .jspa .jar .war .ear
CGI/Config.cgi .htaccess .htpasswd
Legacy.swf .fla

Allowed File Types

All other file types are allowed, including:

CategoryExtensions
Documents.pdf .doc .docx .odt .txt .rtf .md
Images.png .jpg .jpeg .gif .webp .svg .bmp .tiff
Source Code.js .ts .py .rb .go .rs .c .cpp .java .cs
Data Files.json .xml .yaml .yml .csv .toml
Archives.zip .tar .gz .7z .rar
Note: Uploaded files are stored temporarily (1-12 hours) for the signing flow, then automatically purged. Files are never executed by the server - they are always served as downloads. The extension blocking is a defense-in-depth measure.