Momento is now post-quantum — v2 receipts use ML-DSA-65. Learn more →⚠️ npm delayed — recovery requested, awaiting npm support. How to use Momento meanwhile →
Momento Timestamp

API reference

Timestamp a file’s hash, check a receipt, and fetch the public signing keys.

Momento signs a file’s SHA-256 hash and returns a timestamp receipt. Keep that receipt with your file so you can check it later. Your file stays on your device; only its hash is sent to Momento.

yes no Hash the file locally POST /api/v2/stamp with the hash Save the signed receipt Hash the file again later POST /api/v2/verify or verify offline valid is true Trust the timestamp Reject and see the reason

Before you start

Use the interactive OpenAPI reference to explore request schemas and try endpoints, or download the schema for your API client.

Base URL
https://momento.mthatguy.workers.dev

No API key is required. Requests and responses use JSON. For POST requests, set Content-Type: application/json. You can call the API from a browser; cross-origin requests are allowed.

MethodEndpointWhat it does
POST/api/v2/stampCreate a signed receipt for a hash
POST/api/v2/verifyCheck a receipt against a hash
GET/api/v2/keysGet the public signing keys
GET/api/healthCheck whether the API responds
GET/api/readyCheck signing configuration and key self-test

Create a timestamp

POST /api/v2/stamp

Compute your file’s SHA-256 hash locally, then send it to this endpoint. Momento adds the current server time and signs the receipt.

Request body

FieldTypeDescription
hashstring · requiredSHA-256 digest: exactly 64 lowercase hexadecimal characters (0–9, a–f).

Send only hash. Extra fields, including a timestamp, are rejected. The body may be up to 1,024 bytes.

Request example

These examples timestamp the SHA-256 hash of an empty file. Replace it with your own file’s hash.

curl --fail-with-body https://momento.mthatguy.workers.dev/api/v2/stamp \
  -H 'Content-Type: application/json' \
  --data '{"hash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}' \
  --output receipt.json

This saves the response to receipt.json for the verification example below.

On Windows Command Prompt, single quotes are not unwrapped: use double quotes around --data and escape the inner quotes (--data "{\"hash\":\"…\"}"). PowerShell and Bash run the command as shown.

Response

201 Created — the complete receipt. The ID and signature below are placeholders; use the values returned by your request.

201 Created
{
  "payload": {
    "version": 2,
    "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
    "issuedAt": "2026-09-24T12:34:56.789Z",
    "receiptId": "<base64url-encoded 16 random bytes>",
    "keyId": "momento-v2-ml-dsa65"
  },
  "signature": "<base64url-encoded ML-DSA-65 signature>"
}
FieldTypeDescription
payload.versionnumberReceipt format version. Currently 2.
payload.hashstringThe SHA-256 hash you submitted.
payload.issuedAtstringServer time in UTC, in ISO 8601 format.
payload.receiptIdstringRandom ID. Repeated requests for the same hash create separate receipts.
payload.keyIdstringIdentifies the public key to use for verification.
signaturestringML-DSA-65 signature, encoded as base64url.

The response uses Cache-Control: no-store.

Verify a receipt

POST /api/v2/verify

Hash the file you want to check, then send that hash with its saved receipt. Momento checks the signature and confirms that the receipt belongs to the hash you supplied.

Request body

FieldTypeDescription
hashstring · requiredYour file’s SHA-256 digest, as 64 lowercase hexadecimal characters.
receiptobject · requiredThe complete response from the stamp endpoint, including payload and signature.

The body may be up to 8,192 bytes. Choose one receipt format and send only its documented fields.

Request example

Use the receipt.json saved above. This example uses jq to build the request body.

jq --arg hash 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' \
  '{hash: $hash, receipt: .}' receipt.json |
  curl --fail-with-body https://momento.mthatguy.workers.dev/api/v2/verify \
    -H 'Content-Type: application/json' \
    --data-binary @-

Replace the example hash with a fresh hash of the file you are checking.

Response

200 OK
{
  "valid": true,
  "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "issuedAt": "2026-09-24T12:34:56.789Z",
  "receiptId": "<receipt ID>",
  "keyId": "momento-v2-ml-dsa65"
}

The returned fields come from the verified receipt.

Check valid, not just the HTTP status

Both successful and failed verification return HTTP 200. Only accept the receipt when valid === true.

Get public keys

GET /api/v2/keys

Fetch the public keys used to verify receipts. Match the receipt’s payload.keyId to an entry in keys.

curl --fail-with-body https://momento.mthatguy.workers.dev/api/v2/keys
200 OK
{
  "algorithm": "ML-DSA-65",
  "keys": {
    "momento-v2-ml-dsa65": "<base64url-encoded raw ML-DSA-65 public key>"
  }
}

The key shown above is a placeholder. Use the actual key returned by the endpoint, or a trusted copy you saved earlier for offline verification. The response also includes metadata indexed by key ID: status (active, retired, or compromised), createdAt (or null when unknown), a lowercase SHA-256 fingerprint of raw ML-DSA-65 key bytes, and fingerprintAlgorithm: sha256-ml-dsa65-raw. All older Ed25519 keys must be discarded; v1 receipts are rejected. Future ML-DSA-65 rotation can retain uncompromised v2 verification keys. See Protocol v2.

Check service health

GET /api/health

Check whether the API is responding. This does not check signing configuration or rate-limit availability.

curl --fail-with-body https://momento.mthatguy.workers.dev/api/health
200 OK
{ "ok": true }

Check signing readiness

GET /api/ready

Returns 200 with { "ready": true } when the signing and verification keys pass an ML-DSA-65 self-test and the rate-limit bindings are configured. Otherwise returns 503 with { "ready": false }. Self-tests are cached up to 30 seconds per Worker isolate. This check does not consume request quota, test the rate-limit backend, or certify the server clock. Responses use Cache-Control: no-store.

The status page performs this live check. It does not report historical uptime.

Errors

Request errors use an error field. They are separate from a receipt that fails verification. API responses include a generated X-Request-ID for support. Application request logs omit submitted hashes, request bodies, client IPs, and query strings.

400 Bad Request
{ "error": "invalid_hash" }
StatusErrorWhat to do
400invalid_jsonSend valid UTF-8 JSON.
400invalid_hashUse a 64-character lowercase SHA-256 digest. For stamp requests, send only hash.
400invalid_requestUse one of the documented verification body formats without extra fields.
400invalid_receipt_encodingEncode the complete receipt as valid Base64 JSON.
404not_foundCheck the endpoint path and HTTP method.
413request_too_largeKeep stamp bodies within 1,024 bytes and verify bodies within 8,192 bytes.
415unsupported_media_typeSet Content-Type: application/json.
429rate_limitedWait before retrying. The response includes Retry-After: 60.
503rate_limit_unavailableTry again later; the service could not check its rate limits.
503signing_not_configured, signing_key_mismatchThe service’s signing setup needs attention.
500signing_failedThe service could not sign the receipt. Try again later.

Rate limits

EndpointPer client IPPer Cloudflare location
POST /api/v2/stamp30 requests/minute300 requests/minute
POST /api/v2/verify120 requests/minute1,200 requests/minute

If you receive 429, wait for the Retry-After interval before trying again. People sharing an IP address share the per-client limit. The application does not rate-limit the two GET endpoints.

These are abuse controls, not guaranteed quotas: Cloudflare’s counters are local to each location and eventually consistent. The hosting plan also has a daily request ceiling. See the rate limits guide for counting rules, retry behavior, and how to stay within them.

Verify offline

You can verify a saved receipt without calling this API. Use a trusted copy of the public key and the original file.

  1. Validate the receipt’s structure and version.
  2. Select the public key identified by payload.keyId.
  3. Decode the base64url signature and verify it with ML-DSA-65 over the UTF-8 bytes of the array below.
  4. Compute SHA-256 over the file’s raw bytes and compare its lowercase hexadecimal digest with payload.hash.
Signed message
JSON.stringify([
  'Momento timestamp receipt v2 / ML-DSA-65',
  payload.version,
  payload.hash,
  payload.issuedAt,
  payload.receiptId,
  payload.keyId,
]);

The order matters. The signature covers this array, not the serialized receipt object. File names are not signed.

The browser tool and CLI use the shared protocol implementation in packages/protocol/src/index.ts for receipt validation and offline verification. Checking the signature authenticates the receipt; comparing the file hash ties it to your file. See Trust and limits for what a Momento timestamp proves.

On this page