{"openapi":"3.1.0","info":{"title":"Momento Timestamp API","description":"Signed timestamps for SHA-256 hashes. No authentication is required. Files stay on your device; send only their hashes.","version":"2.0.0"},"servers":[{"url":"https://momento.mthatguy.workers.dev","description":"Public API"}],"components":{"schemas":{"Hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"Lowercase SHA-256 digest","example":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},"Signature":{"type":"string","pattern":"^[A-Za-z0-9_-]{4412}$","description":"Unpadded Base64url encoding of a 3309-byte ML-DSA-65 signature"},"Payload":{"type":"object","additionalProperties":false,"required":["version","hash","issuedAt","receiptId","keyId"],"properties":{"version":{"type":"integer","const":2},"hash":{"$ref":"#/components/schemas/Hash"},"issuedAt":{"type":"string","format":"date-time","pattern":"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$","description":"Server timestamp in UTC with milliseconds"},"receiptId":{"type":"string","pattern":"^[A-Za-z0-9_-]{22}$","description":"Unpadded Base64url encoding of 16 random bytes"},"keyId":{"type":"string","pattern":"^[A-Za-z0-9._-]{1,128}$","example":"momento-v2-ml-dsa65"}}},"Receipt":{"type":"object","additionalProperties":false,"required":["payload","signature"],"properties":{"payload":{"$ref":"#/components/schemas/Payload"},"signature":{"$ref":"#/components/schemas/Signature"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"TooLarge":{"description":"request_too_large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnsupportedMediaType":{"description":"unsupported_media_type: use Content-Type: application/json","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"rate_limited. Cache-Control: no-store.","headers":{"Retry-After":{"schema":{"type":"string","enum":["60"]},"description":"Seconds to wait before retrying"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/api/health":{"get":{"operationId":"getHealth","summary":"Check service health","description":"Checks that the API responds, not signing configuration or rate-limit availability.","responses":{"200":{"description":"Service responds","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","const":true}}}}}}}}},"/api/ready":{"get":{"operationId":"getReadiness","summary":"Check signing readiness","description":"Imports the signing and verification keys and performs an ML-DSA-65 self-test. Checks rate-limit bindings are configured without consuming quota. Self-test results are cached up to 30 seconds per isolate; this does not test the rate-limit backend or prove uptime.","responses":{"200":{"description":"Ready to sign","content":{"application/json":{"schema":{"type":"object","required":["ready"],"properties":{"ready":{"type":"boolean","const":true}}}}}},"503":{"description":"Signing configuration or bindings need attention","content":{"application/json":{"schema":{"type":"object","required":["ready"],"properties":{"ready":{"type":"boolean","const":false}}}}}}}}},"/api/v2/keys":{"get":{"operationId":"getPublicKeys","summary":"Get public signing keys","responses":{"200":{"description":"ML-DSA-65 public keys and lifecycle metadata, indexed by key ID","content":{"application/json":{"schema":{"type":"object","required":["algorithm","keys","metadata"],"properties":{"algorithm":{"type":"string","const":"ML-DSA-65"},"keys":{"type":"object","additionalProperties":{"type":"string","description":"Base64url-encoded raw ML-DSA-65 public key"}},"metadata":{"type":"object","additionalProperties":{"type":"object","required":["status","createdAt","fingerprint","fingerprintAlgorithm"],"properties":{"status":{"type":"string","enum":["active","retired","compromised"]},"createdAt":{"anyOf":[{"type":"string"},{"enum":[null]}],"description":"Actual creation time if known; otherwise null"},"fingerprint":{"type":"string","pattern":"^[0-9a-f]{64}$"},"fingerprintAlgorithm":{"type":"string","const":"sha256-ml-dsa65-raw"}}}}}}}}}}}},"/api/v2/stamp":{"post":{"operationId":"createStamp","summary":"Create a timestamp","description":"Signs a SHA-256 hash with the current server time. JSON body limit: 1,024 bytes. Only hash is accepted. Rate limits: 30 requests/minute per client IP and 300 per Cloudflare location. Responses are not stored by the service.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["hash"],"additionalProperties":false,"properties":{"hash":{"$ref":"#/components/schemas/Hash"}}}}}},"responses":{"201":{"description":"Signed receipt. Cache-Control: no-store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Receipt"}}}},"400":{"description":"invalid_json or invalid_hash","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"$ref":"#/components/responses/TooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"signing_failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"rate_limit_unavailable, signing_not_configured, or signing_key_mismatch","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v2/verify":{"post":{"operationId":"verifyStamp","summary":"Verify a receipt","description":"Checks the receipt signature and supplied file hash. JSON body limit: 8,192 bytes. Choose exactly one request format. Invalid receipts return HTTP 200 with valid: false. Rate limits: 120 requests/minute per client IP and 1,200 per Cloudflare location.","requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"title":"Receipt object","type":"object","additionalProperties":false,"required":["hash","receipt"],"properties":{"hash":{"$ref":"#/components/schemas/Hash"},"receipt":{"$ref":"#/components/schemas/Receipt"}}},{"title":"Flat receipt","type":"object","additionalProperties":false,"required":["hash","payload","signature"],"properties":{"hash":{"$ref":"#/components/schemas/Hash"},"payload":{"$ref":"#/components/schemas/Payload"},"signature":{"$ref":"#/components/schemas/Signature"}}},{"title":"Base64 receipt","type":"object","additionalProperties":false,"required":["hash","receiptBase64"],"properties":{"hash":{"$ref":"#/components/schemas/Hash"},"receiptBase64":{"type":"string","maxLength":8192,"description":"Complete receipt JSON encoded as UTF-8, then standard Base64 (optional padding) or unpadded Base64url."}}}]}}}},"responses":{"200":{"description":"Verification result. Check valid, not just HTTP status. Cache-Control: no-store.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["valid","hash","issuedAt","receiptId","keyId"],"properties":{"valid":{"type":"boolean","const":true},"hash":{"$ref":"#/components/schemas/Hash"},"issuedAt":{"type":"string","format":"date-time"},"receiptId":{"type":"string"},"keyId":{"type":"string"}}},{"type":"object","required":["valid","reason"],"properties":{"valid":{"type":"boolean","const":false},"reason":{"type":"string","enum":["hash_mismatch","invalid_signature","unknown_key","invalid_receipt"]}}}]}}}},"400":{"description":"invalid_json, invalid_hash, invalid_request, or invalid_receipt_encoding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"$ref":"#/components/responses/TooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"rate_limit_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}