{"openapi":"3.1.0","info":{"title":"PollyPay API","version":"1.0.0","description":"Create a stamp, check one, list a log, make an attestation. Hash a file yourself first, sha256, lowercase hex; PollyPay never sees the file itself. Every response carries CORS headers for any origin, so the public endpoints (verify, attested, batch-verify, logs, health) work from any web page with a plain fetch. Writes are limited to 60 a minute per account or address.\n\nAuthentication is covered here only where a script can drive it: an emailed code, and a recovery code. The ceremonies a browser has to run (passkey registration and login, magic links) are not, because they need a browser. Sign in at https://polly.hadi-rizvi123.workers.dev/login with a passkey or an email link to get a session cookie, or create an API key at https://polly.hadi-rizvi123.workers.dev/developers and send it as an Authorization: Bearer pk_... header in place of the cookie. See the securitySchemes below. Passkey/magic-link routes under /api/auth are not documented here."},"servers":[{"url":"https://polly.hadi-rizvi123.workers.dev"}],"tags":[{"name":"Stamps"},{"name":"Files","description":"Keep a copy of a recorded file in storage."},{"name":"Transfers","description":"Hand a record to another account."},{"name":"Countersign","description":"Ask another account to put its name to a record."},{"name":"Verify","description":"Public, no account needed."},{"name":"Logs"},{"name":"Profiles","description":"The public page at /@handle and its posts."},{"name":"Batches"},{"name":"Attestations"},{"name":"Credits"},{"name":"Account"},{"name":"Teams"},{"name":"API keys"},{"name":"Webhooks"},{"name":"Health"}],"security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"paths":{"/api/me/api-keys":{"post":{"tags":["API keys"],"summary":"Create an API key","description":"Session-only: an API key can never create or revoke other API keys.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Key created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}}}},"get":{"tags":["API keys"],"summary":"List API keys","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Keys for this account or team. Never includes the raw key.","content":{"application/json":{"schema":{"type":"object","required":["api_keys"],"properties":{"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeySummary"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/me/api-keys/{id}":{"delete":{"tags":["API keys"],"summary":"Revoke an API key","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/me/api-keys/{id}/rotate":{"post":{"tags":["API keys"],"summary":"Rotate an API key","description":"Returns a new key with the same name, scope and expiry; the old one stops working at once.","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"New key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/me/log-handle":{"post":{"tags":["Logs"],"summary":"Claim a public log handle","description":"Session-only. Needed before any stamp can be marked public.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogHandleClaim"}}}},"responses":{"200":{"description":"Claimed.","content":{"application/json":{"schema":{"type":"object","required":["log_handle"],"properties":{"log_handle":{"type":"string"}}}}}},"400":{"description":"Bad handle shape, or handle_reserved/handle_confusable etc.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Bad handle shape, or handle_reserved/handle_confusable etc.","code":"handle_invalid"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"409":{"description":"That handle is already claimed by another account, team or creator page. Also returned as handle_retired when the name belonged to an account or team that has since gone: names used in public are not reused, because links and printed codes outlive the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That handle is already claimed by another account, team or creator page. Also returned as handle_retired when the name belonged to an account or team that has since gone: names used in public are not reused, because links and printed codes outlive the account.","code":"handle_taken"}}}}}}},"/api/stamps":{"post":{"tags":["Stamps"],"summary":"Create a stamp","description":"Writes a memo transaction pollypay:sha256:<hash>. Returns once the transaction lands, usually under two seconds.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 128 characters, unique per request. A retry with the same key and the same body returns the first response again with Idempotent-Replayed: true instead of writing a second record. Kept for 24 hours; not remembered for a request that failed with a 5xx.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCreate"}}}},"responses":{"201":{"description":"Created (status may be pending, submitted or confirmed).","headers":{"Idempotent-Replayed":{"schema":{"type":"string","enum":["true"]},"description":"Present and \"true\" only when this response is a replay of an earlier request with the same Idempotency-Key."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampCreateResponse"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"422":{"description":"That Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That Idempotency-Key was already used with a different request body.","code":"idempotency_key_reused"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}},"502":{"description":"The transaction failed, or an on-chain error occurred after broadcast. Body is StampCreateResponse with status \"failed\", or an Error.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/StampCreateResponse"},{"$ref":"#/components/schemas/Error"}]}}}},"503":{"description":"Could not reach the network. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Could not reach the network. Try again in a minute.","code":"stamp_unavailable"}}}}}}},"/api/stamps/mine":{"get":{"tags":["Stamps"],"summary":"List your stamps","description":"Newest first, up to 100 a page. With q, only the ones whose name contains it or whose fingerprint starts with it.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Search your own records by name or fingerprint.","schema":{"type":"string","maxLength":80}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from a previous page.","schema":{"type":"string","maxLength":64}},{"name":"limit","in":"query","required":false,"description":"Page size, up to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"include_hidden","in":"query","required":false,"description":"Set to '1' to also include stamps hidden with PATCH; omitted, hidden ones are left out.","schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"A page of stamps.","content":{"application/json":{"schema":{"type":"object","required":["stamps","next_cursor"],"properties":{"stamps":{"type":"array","items":{"$ref":"#/components/schemas/Stamp"}},"next_cursor":{"type":["string","null"]}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/stamps/lookup":{"get":{"tags":["Stamps"],"summary":"Look up your own records of a fingerprint","description":"So a page can say 'you already recorded this' before someone stamps a duplicate.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"hash","in":"query","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."}}],"responses":{"200":{"description":"Up to 5 matches, newest first. Empty when there are none.","content":{"application/json":{"schema":{"type":"object","required":["records"],"properties":{"records":{"type":"array","items":{"$ref":"#/components/schemas/Stamp"}}}}}}},"400":{"description":"hash is missing or not a lowercase sha256 hex string.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"hash is missing or not a lowercase sha256 hex string.","code":"bad_hash"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/stamps/{id}":{"get":{"tags":["Stamps"],"summary":"Get one stamp","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The stamp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stamp"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}},"patch":{"tags":["Stamps"],"summary":"Update a stamp","description":"hidden and/or label; everything else about a stamp is immutable.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampPatch"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stamp"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/verify/{token}":{"get":{"tags":["Verify"],"summary":"Verify a stamp, no account needed","description":"token is a hash or a signature. Live re-checks against Solana, not just the database. The certificate page at /s/:signature calls this endpoint.","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Always 200, even when not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}}}}},"/api/verify/{token}/proof":{"get":{"tags":["Verify"],"summary":"Download a proof pack","description":"One JSON file with the fingerprint, the ledger transaction, its memo, the chain of custody and plain instructions for checking all of it without PollyPay. Confirmed records only. Served as an attachment.","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"application/json attachment, format pollypay-proof/1."},"404":{"description":"No confirmed record for that.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No confirmed record for that.","code":"record_not_found"}}}}}}},"/api/logs/{handle}":{"get":{"tags":["Logs"],"summary":"Read a public log","description":"No account needed. A running audit trail of one account or team's public stamps.","security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"description":"A stamp_id to page further back from.","schema":{"type":"string","maxLength":64}}],"responses":{"200":{"description":"Up to 20 stamps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogPage"}}}},"404":{"description":"No account or team has claimed that handle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No account or team has claimed that handle.","code":"log_not_found"}}}}}}},"/api/stamps/{id}/file":{"put":{"tags":["Files"],"summary":"Keep a copy of the recorded file","description":"Body is the raw bytes of the file (or a gzip of them, with X-Content-Encoding: gzip). Only once the record is confirmed. The bytes are re-hashed on arrival and refused with file_mismatch unless they match the record. Replaces any copy already kept for this stamp.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Content-Type","in":"header","required":false,"description":"The file's MIME type. Defaults to application/octet-stream; a type a browser would run or render inline is stored as application/octet-stream instead.","schema":{"type":"string"}},{"name":"X-File-Name","in":"header","required":false,"description":"URL-encoded file name, shown on download. Defaults to \"file\".","schema":{"type":"string"}},{"name":"X-Visibility","in":"header","required":false,"description":"'public' to let anyone with the certificate download it; defaults to private.","schema":{"type":"string","enum":["private","public"]}},{"name":"X-Reveal-At","in":"header","required":false,"description":"Unix seconds. Seals the copy: private until then, public after (the hourly sweep flips it). Forces private.","schema":{"type":"integer"}},{"name":"X-Content-Encoding","in":"header","required":false,"description":"Send gzip if the body is a gzip of the file; the worker inflates it to check the fingerprint but stores the compressed bytes as-is.","schema":{"type":"string","enum":["identity","gzip"]}}],"requestBody":{"required":true,"content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"Kept (or replaced).","content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"$ref":"#/components/schemas/KeptFile"}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"409":{"description":"not_confirmed if the record has not landed yet, or file_mismatch if the bytes' fingerprint does not match the record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"not_confirmed if the record has not landed yet, or file_mismatch if the bytes' fingerprint does not match the record.","code":"file_mismatch"}}}},"413":{"description":"file_too_big (25 MB per file, after decompression) or storage_full (500 MB per account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"file_too_big (25 MB per file, after decompression) or storage_full (500 MB per account).","code":"file_too_big"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}},"patch":{"tags":["Files"],"summary":"Change a kept copy's visibility","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampFilePatch"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","required":["file"],"properties":{"file":{"$ref":"#/components/schemas/KeptFile"}}}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such stamp, or no kept copy exists for it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such stamp, or no kept copy exists for it.","code":"file_not_found"}}}}}},"delete":{"tags":["Files"],"summary":"Drop a kept copy","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Dropped."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such stamp, or no kept copy exists for it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such stamp, or no kept copy exists for it.","code":"file_not_found"}}}}}}},"/api/stamps/{id}/file/share":{"post":{"tags":["Files"],"summary":"Open a time-limited link to a private copy","description":"Works without a session, until it expires or is revoked. One link at a time; creating a new one replaces the old.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileShareCreate"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","required":["url","expires_at"],"properties":{"url":{"type":"string","format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/api/files/01J...?k=..."},"expires_at":{"type":"integer","description":"Unix seconds."}}}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such stamp, or no kept copy exists for it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such stamp, or no kept copy exists for it.","code":"file_not_found"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}},"delete":{"tags":["Files"],"summary":"Revoke the share link","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked (or there was none)."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/files/{id}":{"get":{"tags":["Files"],"summary":"Download a kept copy","description":"id is the stamp_id, not a file id. Always sent as an attachment, never rendered inline, never executable.","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"k","in":"query","required":false,"description":"A token from POST .../file/share. Opens a private copy without a session until it expires.","schema":{"type":"string"}}],"responses":{"200":{"description":"The file bytes, with Content-Disposition: attachment.","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"The copy is private and neither a session nor a valid share token was sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The copy is private and neither a session nor a valid share token was sent.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/stamps/{id}/email":{"post":{"tags":["Stamps"],"summary":"Email yourself the certificate link","description":"Sends the certificate for a confirmed record to the signed-in account's own address. Nowhere else.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{sent: true}"},"400":{"description":"Wait until the record is confirmed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Wait until the record is confirmed.","code":"not_confirmed"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/stamps/from-url":{"post":{"tags":["Stamps"],"summary":"Record what a link serves, or how it looks","description":"PollyPay fetches the address (up to 25 MB, https only) or takes a full-page picture of it, fingerprints that, and records it under your account. Same quota as POST /api/stamps. With keep, the fetched bytes become the kept copy.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StampFromUrl"}}}},"responses":{"201":{"description":"A Stamp plus file (KeptFile or null), source_url, fetched_at and copy_error (string or null)."},"400":{"description":"Use a full https address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Use a full https address.","code":"bad_url"}}}},"413":{"description":"Over 25 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Over 25 MB.","code":"too_big"}}}},"501":{"description":"Full-page pictures are not switched on here.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Full-page pictures are not switched on here.","code":"capture_unavailable"}}}},"502":{"description":"The address did not answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The address did not answer.","code":"fetch_failed"}}}}}}},"/api/stamps/{id}/cosigns":{"post":{"tags":["Countersign"],"summary":"Ask another account to countersign a record","description":"Only once the record is confirmed. Opens a link good for 14 days; several can be open at once. Emails, when given, are capped per hour and per recipient.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CosignCreate"}}}},"responses":{"201":{"description":"{cosign: Cosign} with link set."},"400":{"description":"Ask once the record is confirmed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Ask once the record is confirmed.","code":"not_confirmed"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}},"get":{"tags":["Countersign"],"summary":"List countersign requests on a record","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{cosigns: Cosign[]}"},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/stamps/{id}/cosigns/{cid}/cancel":{"post":{"tags":["Countersign"],"summary":"Withdraw an open request","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Withdrawn."},"400":{"description":"No longer open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No longer open.","code":"not_open"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/cosigns/{token}":{"get":{"tags":["Countersign"],"summary":"See what you are asked to sign","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{cosign, from_handle, record}"},"403":{"description":"Sent to a different email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Sent to a different email address.","code":"wrong_account"}}}},"410":{"description":"Expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Expired.","code":"cosign_expired"}}}}}}},"/api/cosigns/{token}/sign":{"post":{"tags":["Countersign"],"summary":"Sign it","description":"Writes a memo pollypay:cosign:<stamp_id>:<proof> to the ledger from the fee payer, then marks the request signed. Both accounts get a stamp.cosigned webhook.","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{signed: true, ledger, stamp_id, certificate_url}"},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"502":{"description":"The ledger did not accept it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The ledger did not accept it.","code":"cosign_failed"}}}}}}},"/api/stamps/{id}/transfers/{tid}/photo":{"put":{"tags":["Transfers"],"summary":"Attach the sender's photo to an open handover","description":"Raw JPEG, PNG or WebP body, up to 5 MB. Replaces an earlier photo. Public once the handover is part of the chain of custody.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"tid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{transfer: Transfer}"},"413":{"description":"Up to 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Up to 5 MB.","code":"photo_too_big"}}}},"415":{"description":"JPEG, PNG or WebP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"JPEG, PNG or WebP.","code":"bad_photo"}}}}}}},"/api/transfers/{token}/photo":{"put":{"tags":["Transfers"],"summary":"Attach the recipient's photo before accepting","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ok: true}"},"413":{"description":"Up to 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Up to 5 MB.","code":"photo_too_big"}}}},"415":{"description":"JPEG, PNG or WebP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"JPEG, PNG or WebP.","code":"bad_photo"}}}}}}},"/api/custody/{tid}/photo/{side}":{"get":{"tags":["Transfers"],"summary":"A handover photo","description":"No account needed; part of the public chain of custody. side is 'from' or 'to'.","security":[],"parameters":[{"name":"tid","in":"path","required":true,"schema":{"type":"string"}},{"name":"side","in":"path","required":true,"schema":{"type":"string","enum":["from","to"]}}],"responses":{"200":{"description":"The image, inline."},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/reports":{"post":{"tags":["Verify"],"summary":"Report something public here","description":"No account needed: the person who most needs this is usually the one being impersonated. A person reads the queue. Ledger lines are never erased, but a page can stop being shown, a kept copy can be dropped, a handle freed and an account closed.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportCreate"}}}},"responses":{"201":{"description":"{received: true}"},"403":{"description":"The browser check did not pass.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The browser check did not pass.","code":"turnstile_failed"}}}}}}},"/api/demo/notes":{"post":{"tags":["Verify"],"summary":"Put a practice note on the test network","description":"No account. Five an hour per address. Only answers on the devnet origin; the production landing page calls it there.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoNote"}}}},"responses":{"201":{"description":"{hash, status, recorded_at, certificate_url, note}"},"404":{"description":"Only on the test network.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Only on the test network.","code":"not_here"}}}},"429":{"description":"Five an hour.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Five an hour.","code":"demo_limit"}}}}}}},"/api/teams/brand":{"get":{"tags":["Teams"],"summary":"What your team shows on certificates","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"{brand_name, logo_url}"},"404":{"description":"Not in a team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Not in a team.","code":"no_team"}}}}}},"patch":{"tags":["Teams"],"summary":"Set the name shown on certificates","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamBrand"}}}},"responses":{"200":{"description":"{brand_name, logo_url}"},"403":{"description":"Owners and admins only.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Owners and admins only.","code":"not_a_team_admin"}}}}}}},"/api/teams/logo":{"put":{"tags":["Teams"],"summary":"Upload the team logo","description":"Raw PNG, JPEG or WebP body, up to 300 KB.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"{logo_url}"},"413":{"description":"Up to 300 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Up to 300 KB.","code":"logo_too_big"}}}},"415":{"description":"PNG, JPEG or WebP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"PNG, JPEG or WebP.","code":"bad_logo"}}}}}},"delete":{"tags":["Teams"],"summary":"Remove the team logo","security":[{"cookieAuth":[]}],"responses":{"204":{"description":"Removed."}}}},"/api/teams/{id}/logo":{"get":{"tags":["Teams"],"summary":"A team logo","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The image, inline."},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/stamps/{id}/transfers":{"post":{"tags":["Transfers"],"summary":"Hand a record to another account","description":"Only once the record is confirmed. Opens a link good for 7 days; replaces any handover already open on this record.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransferCreate"}}}},"responses":{"201":{"description":"Opened.","content":{"application/json":{"schema":{"type":"object","required":["transfer"],"properties":{"transfer":{"$ref":"#/components/schemas/Transfer"}}}}}},"400":{"description":"not_confirmed if the record has not landed yet, or same_account if email is your own.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"not_confirmed if the record has not landed yet, or same_account if email is your own.","code":"not_confirmed"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}},"get":{"tags":["Transfers"],"summary":"List handovers on a record","description":"Newest first, up to 20. Every status: open, accepted, cancelled.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Handovers.","content":{"application/json":{"schema":{"type":"object","required":["transfers"],"properties":{"transfers":{"type":"array","items":{"$ref":"#/components/schemas/Transfer"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/stamps/{id}/transfers/{tid}/cancel":{"post":{"tags":["Transfers"],"summary":"Cancel an open handover","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"tid","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Cancelled."},"400":{"description":"That handover is no longer open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That handover is no longer open.","code":"not_open"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/transfers/{token}":{"get":{"tags":["Transfers"],"summary":"See what's on offer","description":"Session required, but not yet a match to any particular account; whether it can be accepted is checked on accept.","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The offer.","content":{"application/json":{"schema":{"type":"object","required":["transfer","from_handle","record"],"properties":{"transfer":{"$ref":"#/components/schemas/Transfer"},"from_handle":{"type":["string","null"]},"record":{"type":"object","required":["hash","label","recorded_at","certificate_url"],"properties":{"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"label":{"type":["string","null"]},"recorded_at":{"type":"integer","description":"Unix seconds."},"certificate_url":{"type":["string","null"],"format":"uri"}}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"410":{"description":"That handover link has expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That handover link has expired.","code":"transfer_expired"}}}}}}},"/api/transfers/{token}/sign/options":{"post":{"tags":["Transfers"],"summary":"Options for signing this handover with your own device","description":"WebAuthn options whose challenge is derived from the handover itself: a sha256 of the record id, both account ids and this transfer. Sign them and send the assertion as `signed` when accepting, and the certificate carries a signature anyone can check against your account rather than taking PollyPay at its word. Nothing is stored here; the challenge is derived again on accept from the same ids. Refused with no_passkey when the account has none, in which case accept without it.","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"PublicKeyCredentialRequestOptions, as generated by the WebAuthn library. Pass to navigator.credentials.get.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"This account has no passkey to sign with.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This account has no passkey to sign with.","code":"no_passkey"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No open handover for that link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No open handover for that link.","code":"transfer_not_found"}}}}}}},"/api/transfers/{token}/accept":{"post":{"tags":["Transfers"],"summary":"Accept a handover","description":"Writes a pollypay:transfer:<proof> memo, then moves the record (and any kept copy) to your account. Both sides get a stamp.transferred webhook; the certificate then shows this step in its custody list. Send `signed` with an assertion from /sign/options and the step carries the recipient's own device signature, the memo gains a fifth segment naming it, and the proof pack ships everything needed to check it offline. An assertion that does not check out fails the accept rather than being dropped.","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Accepted (ledger may be pending, submitted or confirmed).","content":{"application/json":{"schema":{"type":"object","required":["accepted","ledger","stamp_id","certificate_url"],"properties":{"accepted":{"const":true},"ledger":{"type":"string","enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"certificate_url":{"type":["string","null"],"format":"uri"}}}}}},"400":{"description":"You cannot accept your own handover.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You cannot accept your own handover.","code":"own_transfer"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"This handover was sent to a different email address. Sign in with that one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This handover was sent to a different email address. Sign in with that one.","code":"wrong_account"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"410":{"description":"That handover link has expired. Ask for a new one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That handover link has expired. Ask for a new one.","code":"transfer_expired"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}},"502":{"description":"The ledger did not accept the handover, or transfer_unavailable if the network could not be reached. Try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The ledger did not accept the handover, or transfer_unavailable if the network could not be reached. Try again.","code":"transfer_failed"}}}}}}},"/api/me/profile":{"get":{"tags":["Profiles"],"summary":"Get your page profile","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}},"put":{"tags":["Profiles"],"summary":"Update your page profile","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdate"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Profile"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/me/posts":{"get":{"tags":["Profiles"],"summary":"List your posts","description":"Newest first, up to 50.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Posts.","content":{"application/json":{"schema":{"type":"object","required":["posts"],"properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/Post"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}},"post":{"tags":["Profiles"],"summary":"Write a post","description":"Requires a log handle to already be set (POST /api/me/log-handle), or no page exists to show it on.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagePostCreate"}}}},"responses":{"201":{"description":"Posted.","content":{"application/json":{"schema":{"type":"object","required":["post"],"properties":{"post":{"$ref":"#/components/schemas/Post"}}}}}},"400":{"description":"Pick a name for your page first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Pick a name for your page first.","code":"no_handle"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"stamp_id was sent but is not one of your own stamps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"stamp_id was sent but is not one of your own stamps.","code":"stamp_not_found"}}}}}}},"/api/me/posts/{id}":{"delete":{"tags":["Profiles"],"summary":"Delete a post","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/profiles/{handle}":{"get":{"tags":["Profiles"],"summary":"Read a public page","description":"No account needed. handle is the same log handle used by /api/logs/{handle}.","security":[],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The page.","content":{"application/json":{"schema":{"type":"object","required":["page"],"properties":{"page":{"$ref":"#/components/schemas/PublicProfilePage"}}}}}},"404":{"description":"No active account has claimed that handle.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No active account has claimed that handle.","code":"page_not_found"}}}}}}},"/api/me/files":{"get":{"tags":["Account"],"summary":"Kept copies, biggest first","description":"What is taking up the storage allowance, so a full account can be tidied. Up to 500 copies.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Kept copies with what they cost against the allowance.","content":{"application/json":{"schema":{"type":"object","required":["files","used_bytes","limit_bytes"],"properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/KeptFileRow"}},"used_bytes":{"type":"integer"},"limit_bytes":{"type":"integer"}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/auth/magic/code":{"post":{"tags":["Auth"],"summary":"Sign in with the six digit code from the email","description":"The same sign-in as the link. Five wrong tries on a code retire it. Wrong email and wrong code answer the same way.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MagicCode"}}}},"responses":{"200":{"description":"{account}"},"400":{"description":"No match, expired, or already used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No match, expired, or already used.","code":"bad_code"}}}}}}},"/api/me/storage":{"post":{"tags":["Account"],"summary":"Rent or release kept-copy storage","description":"Storage past the free allowance is rented by the gigabyte by the month, charged to the balance. Increasing charges the difference at once; decreasing gives no refund and is refused while more than that is stored. Non-payment lapses the extra room, it never deletes files.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageUpdate"}}}},"responses":{"200":{"description":"{blocks, paid_until, limit_bytes, balance_micro}"},"400":{"description":"Not enough in the balance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Not enough in the balance.","code":"no_balance"}}}}}}},"/api/me":{"get":{"tags":["Account"],"summary":"Who this session belongs to","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"The account this session belongs to.","content":{"application/json":{"schema":{"type":"object","required":["account"],"properties":{"account":{"$ref":"#/components/schemas/Account"}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}},"delete":{"tags":["Account"],"summary":"Delete this account","description":"Removes the email, sign-in methods, keys, webhooks, page, posts and kept copies. Records and statements already on the ledger stay there, no longer tied to anyone. Step-up first, and send {\"confirm\": \"DELETE\"}.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDelete"}}}},"responses":{"200":{"description":"{deleted: true}"},"403":{"description":"Confirm with your passkey first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Confirm with your passkey first.","code":"stepup_required"}}}}}}},"/api/me/email":{"post":{"tags":["Account"],"summary":"Move this account to another address","description":"Step-up first. Sends a link to the new address; sign-in only moves once that link is opened, and the old address is told.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChange"}}}},"responses":{"200":{"description":"{sent: true}"},"403":{"description":"Confirm with your passkey first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Confirm with your passkey first.","code":"stepup_required"}}}}}}},"/api/me/sessions":{"get":{"tags":["Account"],"summary":"Where this account is signed in","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Signed-in browsers, newest first, at most 50.","content":{"application/json":{"schema":{"type":"object","required":["sessions"],"properties":{"sessions":{"type":"array","items":{"$ref":"#/components/schemas/Session"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/me/sessions/{id}":{"delete":{"tags":["Account"],"summary":"Sign one other device out","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{revoked: true}"}}}},"/api/me/sessions/revoke-all":{"post":{"tags":["Account"],"summary":"Sign every other device out","description":"Leaves the session making the call signed in.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"{revoked: <count>}"}}}},"/api/me/passkeys":{"get":{"tags":["Account"],"summary":"The passkeys on this account","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"The passkeys registered to this account.","content":{"application/json":{"schema":{"type":"object","required":["passkeys"],"properties":{"passkeys":{"type":"array","items":{"$ref":"#/components/schemas/Passkey"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/me/passkeys/{id}":{"delete":{"tags":["Account"],"summary":"Remove a passkey","description":"Step-up first. The last passkey on an account cannot be removed; add another one first.","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{removed: true}"},"400":{"description":"Add another passkey before removing this one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Add another passkey before removing this one.","code":"last_passkey"}}}},"403":{"description":"Confirm with your passkey first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Confirm with your passkey first.","code":"stepup_required"}}}}}}},"/api/me/recovery":{"get":{"tags":["Account"],"summary":"How many recovery codes are left","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"How many of the ten are unused, and when the set was made.","content":{"application/json":{"schema":{"type":"object","required":["remaining","made_at"],"properties":{"remaining":{"type":"integer"},"made_at":{"type":["integer","null"],"description":"Unix seconds."}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}},"post":{"tags":["Account"],"summary":"Make ten recovery codes","description":"Step-up first. Returns the codes once and only once, and stops any earlier set working.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"{codes: string[]}"},"403":{"description":"Confirm with your passkey first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Confirm with your passkey first.","code":"stepup_required"}}}}}}},"/api/me/export":{"get":{"tags":["Account"],"summary":"Download everything this account has","description":"One JSON attachment: records, statements, bundles, posts, kept copy metadata, handovers and countersigns, with certificate and proof pack links.","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"application/json attachment, format pollypay-export/1."}}}},"/api/auth/recovery":{"post":{"tags":["Auth"],"summary":"Sign in with a recovery code","description":"For when both the inbox and the passkey are out of reach. A code works once, and using one signs every other browser out. Wrong email and wrong code answer the same way.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecoverySignIn"}}}},"responses":{"200":{"description":"{account, remaining}"},"400":{"description":"No match, or already used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No match, or already used.","code":"bad_code"}}}}}}},"/api/me/summary":{"get":{"tags":["Account"],"summary":"Numbers for the signed-in home page","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeSummary"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/credits":{"get":{"tags":["Credits"],"summary":"Get prepaid balance","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"Balance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditsBalance"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}},"post":{"tags":["Credits"],"summary":"Start a top-up","description":"Only relevant once the free hourly quota is used up. Follow with POST .../tx then POST .../submit.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 128 characters, unique per request. A retry with the same key and the same body returns the first response again with Idempotent-Replayed: true instead of writing a second record. Kept for 24 hours; not remembered for a request that failed with a 5xx.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupCreate"}}}},"responses":{"201":{"description":"Top-up intent created.","headers":{"Idempotent-Replayed":{"schema":{"type":"string","enum":["true"]},"description":"Present and \"true\" only when this response is a replay of an earlier request with the same Idempotency-Key."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupCreateResponse"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"422":{"description":"That Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That Idempotency-Key was already used with a different request body.","code":"idempotency_key_reused"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}}},"/api/credits/{id}/tx":{"post":{"tags":["Credits"],"summary":"Build the top-up transaction","description":"Sign the returned tx_b64 with any Solana wallet holding USDC, then submit it.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupTx"}}}},"responses":{"200":{"description":"Unsigned transaction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupTxResponse"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"409":{"description":"This top-up already submitted or confirmed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"This top-up already submitted or confirmed.","code":"topup_closed"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}}},"/api/credits/{id}/submit":{"post":{"tags":["Credits"],"summary":"Submit the signed top-up transaction","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupSubmit"}}}},"responses":{"200":{"description":"Broadcast result. status is \"submitted\" if it did not confirm within the request; poll GET /api/credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupSubmitResponse"}}}},"400":{"description":"The signed transaction did not match what was built, or was malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The signed transaction did not match what was built, or was malformed.","code":"signature_rejected"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"409":{"description":"Call POST .../tx first, or topup_closed if already confirmed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Call POST .../tx first, or topup_closed if already confirmed.","code":"topup_not_built"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}},"502":{"description":"The network rejected the transaction, or topup_unavailable if it could not be reached. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The network rejected the transaction, or topup_unavailable if it could not be reached. Nothing was charged.","code":"transaction_rejected"}}}}}}},"/api/attestations":{"post":{"tags":["Attestations"],"summary":"Make an attestation","description":"Both subject and claim are written in the open on chain, in the memo itself.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 128 characters, unique per request. A retry with the same key and the same body returns the first response again with Idempotent-Replayed: true instead of writing a second record. Kept for 24 hours; not remembered for a request that failed with a 5xx.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttestationCreate"}}}},"responses":{"201":{"description":"Created (status may be pending, submitted or confirmed).","headers":{"Idempotent-Replayed":{"schema":{"type":"string","enum":["true"]},"description":"Present and \"true\" only when this response is a replay of an earlier request with the same Idempotency-Key."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttestationCreateResponse"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"422":{"description":"That Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That Idempotency-Key was already used with a different request body.","code":"idempotency_key_reused"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}},"502":{"description":"The transaction failed. Body is AttestationCreateResponse with status \"failed\", or an Error.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AttestationCreateResponse"},{"$ref":"#/components/schemas/Error"}]}}}},"503":{"description":"Could not reach the network. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Could not reach the network. Try again in a minute.","code":"attestation_unavailable"}}}}}}},"/api/attestations/mine":{"get":{"tags":["Attestations"],"summary":"List your attestations","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Search your own statements by what they are about or what they say.","schema":{"type":"string","maxLength":80}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from a previous page.","schema":{"type":"string","maxLength":64}},{"name":"limit","in":"query","required":false,"description":"Page size, up to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of attestations.","content":{"application/json":{"schema":{"type":"object","required":["attestations","next_cursor"],"properties":{"attestations":{"type":"array","items":{"$ref":"#/components/schemas/Attestation"}},"next_cursor":{"type":["string","null"]}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/attestations/{id}":{"get":{"tags":["Attestations"],"summary":"Get one attestation","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The attestation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Attestation"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/attested/{token}":{"get":{"tags":["Verify"],"summary":"Check what's been attested, no account needed","description":"Pass a signature for one attestation (kind: \"attestation\", live re-verified); pass anything else to treat it as a subject and list every confirmed attestation about it (kind: \"subject\").","security":[],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"description":"Paginates the subject case the same way as Logs.","schema":{"type":"string","maxLength":64}}],"responses":{"200":{"description":"Always 200. Shape depends on whether token was a signature.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/AttestedBySignature"},{"$ref":"#/components/schemas/AttestedBySubject"},{"$ref":"#/components/schemas/NotFoundLookup"}]}}}}}}},"/api/batches":{"post":{"tags":["Batches"],"summary":"Batch stamp up to 500 hashes","description":"Every hash goes into a Merkle tree; only the root touches Solana, in one transaction. Draws from the same hourly quota as a single stamp.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 128 characters, unique per request. A retry with the same key and the same body returns the first response again with Idempotent-Replayed: true instead of writing a second record. Kept for 24 hours; not remembered for a request that failed with a 5xx.","schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchStampCreate"}}}},"responses":{"201":{"description":"Created (status may be pending, submitted or confirmed).","headers":{"Idempotent-Replayed":{"schema":{"type":"string","enum":["true"]},"description":"Present and \"true\" only when this response is a replay of an earlier request with the same Idempotency-Key."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchCreateResponse"}}}},"400":{"description":"Every hash in a batch must be unique (or invalid_request for shape failures).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Every hash in a batch must be unique (or invalid_request for shape failures).","code":"duplicate_hash"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"422":{"description":"That Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That Idempotency-Key was already used with a different request body.","code":"idempotency_key_reused"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}},"502":{"description":"The transaction failed. Body is BatchCreateResponse with status \"failed\", or an Error.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BatchCreateResponse"},{"$ref":"#/components/schemas/Error"}]}}}},"503":{"description":"Could not reach the network. Try again in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Could not reach the network. Try again in a minute.","code":"batch_unavailable"}}}}}}},"/api/batches/mine":{"get":{"tags":["Batches"],"summary":"List your batches","description":"Batch.items is omitted here; fetch GET /api/batches/{id} for the per-hash proofs.","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"description":"The next_cursor from a previous page.","schema":{"type":"string","maxLength":64}},{"name":"limit","in":"query","required":false,"description":"Page size, up to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"A page of batches.","content":{"application/json":{"schema":{"type":"object","required":["batches","next_cursor"],"properties":{"batches":{"type":"array","items":{"$ref":"#/components/schemas/Batch"}},"next_cursor":{"type":["string","null"]}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/batches/{id}":{"get":{"tags":["Batches"],"summary":"Get one batch, with every item and its proof","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The batch.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Batch"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/batch-verify/{batch_id}":{"get":{"tags":["Verify"],"summary":"Batch overview, no account needed","description":"The root, its transaction, and every hash it covers.","security":[],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Always 200.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BatchVerifyOverview"},{"$ref":"#/components/schemas/NotFoundLookup"}]}}}}}}},"/api/batch-verify/{batch_id}/{hash}":{"get":{"tags":["Verify"],"summary":"Verify one hash in a batch, no account needed","description":"Recomputes the Merkle proof against root and live-reverifies that root is really the one written on chain, same trust model as /api/verify.","security":[],"parameters":[{"name":"batch_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"hash","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Always 200.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/BatchVerifyItemConfirmed"},{"$ref":"#/components/schemas/BatchVerifyItemPending"},{"$ref":"#/components/schemas/BatchVerifyNotInBatch"},{"$ref":"#/components/schemas/NotFoundLookup"}]}}}}}}},"/api/teams":{"post":{"tags":["Teams"],"summary":"Create a team","description":"Session-only, like every team route. You must not already be on a team.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamCreate"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamCreateResponse"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"409":{"description":"Leave or delete your current team first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Leave or delete your current team first.","code":"already_on_a_team"}}}}}},"delete":{"tags":["Teams"],"summary":"Delete your team","description":"Owner only. Every member reverts to a solo account; nothing is deleted for them.","security":[{"cookieAuth":[]}],"responses":{"204":{"description":"Deleted."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}}}}},"/api/teams/mine":{"get":{"tags":["Teams"],"summary":"Get your team","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"team is null if you are not on one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamMineResponse"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/teams/invites":{"post":{"tags":["Teams"],"summary":"Invite a teammate by email","description":"Owner or admin only. The invite is an email link; the invitee signs in as that address, then accepts.","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamInvite"}}}},"responses":{"201":{"description":"Sent.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"const":true}}}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"409":{"description":"Or already_on_another_team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Or already_on_another_team.","code":"already_a_member"}}}}}}},"/api/teams/invites/{token}/accept":{"post":{"tags":["Teams"],"summary":"Accept a team invite","description":"Session required. Sign in with the email the invite was sent to first.","security":[{"cookieAuth":[]}],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Joined.","content":{"application/json":{"schema":{"type":"object","properties":{"team":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":"string"}}}}}}}},"400":{"description":"The invite token is unknown or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The invite token is unknown or expired.","code":"invite_expired"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Sign in with the email this invite was sent to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Sign in with the email this invite was sent to.","code":"wrong_account"}}}},"409":{"description":"Leave or delete your current team first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Leave or delete your current team first.","code":"already_on_a_team"}}}}}}},"/api/teams/members/{id}/remove":{"post":{"tags":["Teams"],"summary":"Remove a team member","description":"Owner or admin. Only the owner can remove an admin. The owner cannot be removed.","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Removed."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/teams/members/{id}/role":{"post":{"tags":["Teams"],"summary":"Change a member's role","description":"Owner only.","security":[{"cookieAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TeamRoleUpdate"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"const":true}}}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/teams/leave":{"post":{"tags":["Teams"],"summary":"Leave your team","description":"The owner cannot leave; delete the team, or promote someone else first.","security":[{"cookieAuth":[]}],"responses":{"204":{"description":"Left."},"400":{"description":"You are not on a team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You are not on a team.","code":"not_on_a_team"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Delete the team instead, or promote someone else first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Delete the team instead, or promote someone else first.","code":"owner_cannot_leave"}}}}}}},"/api/hooks":{"post":{"tags":["Webhooks"],"summary":"Register a webhook","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountWebhookCreate"}}}},"responses":{"201":{"description":"Created. secret is shown only here.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreateResponse"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}},"get":{"tags":["Webhooks"],"summary":"List webhooks","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"Webhooks for this account or team.","content":{"application/json":{"schema":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}}}}},"/api/hooks/{id}":{"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted."},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/hooks/{id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test delivery","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Result of the synthetic delivery attempt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResponse"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}}},"/api/hooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List delivery attempts","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size, up to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Latest deliveries, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/Delivery"}}}}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}}}}},"/api/hooks/{id}/deliveries/{delivery_id}/redeliver":{"post":{"tags":["Webhooks"],"summary":"Redeliver one delivery now","security":[{"cookieAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The delivery after the retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}}},"401":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"403":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"404":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"429":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}}}},"/api/health":{"get":{"tags":["Health"],"summary":"Service health","description":"Cached for up to 7 minutes. 503 when any check fails.","security":[],"responses":{"200":{"description":"Healthy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"503":{"description":"One or more checks failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}}},"components":{"securitySchemes":{"cookieAuth":{"type":"apiKey","in":"cookie","name":"__Host-polly_session","description":"Session cookie set by signing in at /login with a passkey or an email link. Required for /api/me/* and /api/teams/*; works everywhere else too."},"apiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"pk_...","description":"Authorization: Bearer pk_<key>. Create one at /developers or POST /api/me/api-keys (session-only). A key with scope 'read' can only make GET/HEAD/OPTIONS requests; any write with it is refused with 403 read_only_key. Not accepted on /api/me/* or /api/teams/* (session-only)."}},"schemas":{"Error":{"type":"object","description":"Every non-2xx JSON response from the API takes this shape.","required":["error","code"],"properties":{"error":{"type":"string","description":"Human-readable message."},"code":{"type":"string","description":"Stable machine-readable code, e.g. 'quota_reached'."}}},"Stamp":{"type":"object","required":["stamp_id","hash","algo","label","public","hidden","status","signature","slot","block_time","created_at","explorer_url","certificate_url","short_url","file","transfer"],"properties":{"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"algo":{"type":"string","example":"sha256","description":"Always sha256 today."},"label":{"type":["string","null"],"maxLength":80},"public":{"type":"boolean","description":"True only if requested AND a log handle was already set; otherwise silently kept false."},"hidden":{"type":"boolean","description":"Hidden from GET /api/stamps/mine unless include_hidden=1. Set with PATCH."},"taken_down":{"type":"boolean","description":"Moderation removed it from every public read. Owners are told rather than left to find it gone. Not settable."},"team":{"type":"boolean","description":"True when a team keeps this record rather than the person who made it. The maker is unchanged either way and still gets the attribution on the certificate; this only decides who holds it, and therefore who still has it when somebody leaves. Set with `scope` on create, moved with `scope` on PATCH."},"status":{"type":"string","enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"slot":{"type":["integer","null"]},"block_time":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":["string","null"],"format":"uri","example":"https://solscan.io/tx/5x7..."},"certificate_url":{"type":["string","null"],"format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/s/5x7..."},"short_url":{"type":["string","null"],"format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/c/01J...","description":"Redirects to certificate_url once a signature exists."},"previous_stamp_id":{"type":["string","null"],"pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1","description":"The record this one replaces, when the owner marked it as a new version."},"checks":{"type":"integer","description":"How many times the certificate was opened or the fingerprint checked by others, all time."},"file":{"oneOf":[{"$ref":"#/components/schemas/KeptFile"},{"type":"null"}]},"transfer":{"oneOf":[{"$ref":"#/components/schemas/Transfer"},{"type":"null"}],"description":"The currently open handover on this record, if any. Only ever present on GET /api/stamps/mine and /api/stamps/{id}."}}},"StampCreateResponse":{"allOf":[{"$ref":"#/components/schemas/Stamp"},{"type":"object","properties":{"paid_with_credits":{"type":"boolean"},"balance_micro":{"type":["integer","null"],"description":"Remaining prepaid balance after this call. Present only when paid_with_credits is true; omitted (not null) otherwise."}}}]},"VerifyResponse":{"type":"object","description":"Response of GET /api/verify/{token}. 'found: false' is returned with HTTP 200, not 404.","required":["found","status"],"properties":{"found":{"type":"boolean"},"status":{"type":"string","enum":["not_found","pending","failed","confirmed"],"description":"'found' is true whenever there is a record at all; 'verified' is only ever true for a confirmed record. A broadcast-but-not-yet-landed record reports 'pending' here even if its internal status is 'submitted'."},"verified":{"type":"boolean","description":"A live re-check against Solana, not just a database read. Only true for a confirmed record."},"checked":{"type":"boolean","description":"Whether the ledger could be reached at all. verified false with checked false means we could not ask, which is not the ledger saying no. Show the two differently or you either cry wolf or hide a real mismatch."},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"algo":{"type":"string","example":"sha256"},"label":{"type":["string","null"]},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"slot":{"type":"integer"},"block_time":{"type":"integer","description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":["string","null"],"format":"uri"},"certificate_url":{"type":"string","format":"uri"},"file":{"oneOf":[{"$ref":"#/components/schemas/KeptFile"},{"type":"null"}],"description":"Present only once confirmed. Shown when the kept copy is public, or you are signed in as the owner."},"custody":{"type":"array","description":"Present only once confirmed. Who has held the record: recorded first, then a transferred step for each accepted handover since.","items":{"$ref":"#/components/schemas/CustodyStep"}},"cosigns":{"type":"array","description":"Present only once confirmed. Who countersigned, in order.","items":{"$ref":"#/components/schemas/SignerStep"}},"brand":{"oneOf":[{"$ref":"#/components/schemas/Brand"},{"type":"null"}]},"versions":{"type":"object","description":"Present only once confirmed. Records the owner linked as earlier or later versions of this one.","properties":{"earlier":{"type":"array","items":{"$ref":"#/components/schemas/VersionStep"}},"later":{"type":"array","items":{"$ref":"#/components/schemas/VersionStep"}}}}}},"VersionStep":{"type":"object","required":["stamp_id","label","created_at","certificate_url"],"properties":{"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"label":{"type":["string","null"]},"created_at":{"type":"integer","description":"Unix seconds."},"certificate_url":{"type":["string","null"],"format":"uri"}}},"LogPage":{"type":"object","required":["handle","stamps","next_cursor"],"properties":{"handle":{"type":"string"},"stamps":{"type":"array","items":{"$ref":"#/components/schemas/Stamp"}},"next_cursor":{"type":["string","null"]}}},"KeptFile":{"type":"object","description":"A copy of the recorded file, kept in storage at the owner's request. Re-hashed on arrival; a copy that does not match the record's fingerprint is refused.","required":["name","size","content_type","visibility","kept_at","url"],"properties":{"name":{"type":"string","maxLength":120},"size":{"type":"integer","description":"Original byte size, before any gzip compression applied on the way in."},"content_type":{"type":"string"},"visibility":{"type":"string","enum":["private","public"]},"kept_at":{"type":"integer","description":"Unix seconds."},"reveal_at":{"type":["integer","null"],"description":"A sealed note: the moment it turns public. Null for ordinary copies."},"sealed":{"type":"boolean","description":"True while a sealed note is still private."},"url":{"type":"string","format":"uri","description":"GET this to download it as an attachment."}}},"Account":{"type":"object","description":"The account behind a session. Emails appear here and nowhere public.","required":["id","email","status","email_verified_at","log_handle","credits_micro","created_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"email":{"type":"string","format":"email"},"status":{"type":"string","enum":["active","deleted"]},"email_verified_at":{"type":["integer","null"],"description":"Null on an account started from a passkey, until the address is confirmed."},"log_handle":{"type":["string","null"],"description":"The name of the public page, when one has been claimed."},"credits_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."},"created_at":{"type":"integer","description":"Unix seconds."}}},"Session":{"type":"object","description":"A signed-in browser. The one making the call is marked current and cannot sign itself out from this list.","required":["id","current","user_agent","created_at","last_seen_at","expires_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"current":{"type":"boolean"},"user_agent":{"type":"string"},"created_at":{"type":"integer","description":"Unix seconds."},"last_seen_at":{"type":"integer","description":"Unix seconds."},"expires_at":{"type":"integer","description":"Unix seconds."}}},"Passkey":{"type":"object","description":"A fingerprint, face or device PIN registered to this account. The public key itself is never returned.","required":["id","name","device_type","backed_up","created_at","last_used_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":["string","null"]},"device_type":{"type":["string","null"],"enum":["singleDevice","multiDevice",null]},"backed_up":{"type":"boolean","description":"Synced to the platform account, so it survives losing the device."},"created_at":{"type":"integer","description":"Unix seconds."},"last_used_at":{"type":["integer","null"],"description":"Unix seconds."}}},"KeptFileRow":{"type":"object","description":"One kept copy in the storage listing, with the record it belongs to.","required":["stamp_id","name","size","stored_size","visibility","sealed","kept_at","url"],"properties":{"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":"string"},"label":{"type":["string","null"]},"size":{"type":"integer","description":"Original byte size."},"stored_size":{"type":"integer","description":"What it costs against the allowance, after any compression on the way in."},"visibility":{"type":"string","enum":["private","public"]},"sealed":{"type":"boolean"},"kept_at":{"type":"integer","description":"Unix seconds."},"url":{"type":"string","format":"uri"},"certificate_url":{"type":["string","null"],"format":"uri"}}},"SignerStep":{"type":"object","required":["handle","at","signature","ledger","explorer_url","verified","checked","memo","note","asked"],"properties":{"handle":{"type":["string","null"]},"at":{"type":"integer","description":"Unix seconds."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"ledger":{"type":["string","null"],"enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"explorer_url":{"type":["string","null"],"format":"uri","description":"A countersignature is its own transaction, so it has its own line to look at."},"verified":{"type":"boolean","description":"This step re-checked against the ledger on this request: its transaction exists, and its memo matches the one rebuilt from the parties. Always false while ledger is not confirmed."},"checked":{"type":"boolean","description":"Whether the ledger could be reached at all. false with verified false means we could not ask, which is not the ledger disagreeing."},"memo":{"type":["string","null"],"description":"The exact memo this step's transaction must carry, rebuilt from the ids rather than read back from storage. Check it yourself against any Solana node."},"note":{"type":["string","null"],"description":"The signer's own words."},"asked":{"type":["string","null"],"description":"What the person asking wrote. Never merged with the signer's own words."}}},"Brand":{"type":"object","description":"What a team chose to show on its certificates.","required":["name","logo_url"],"properties":{"name":{"type":"string"},"logo_url":{"type":["string","null"],"format":"uri"}}},"Cosign":{"type":"object","description":"A request for another account to put its name to a record; signing writes its own ledger line.","required":["id","stamp_id","status","to_email","note","created_at","expires_at","signed_at","ledger","signature","link"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"status":{"type":"string","enum":["open","signed","cancelled","expired"]},"to_email":{"type":["string","null"],"format":"email"},"note":{"type":["string","null"],"maxLength":500},"created_at":{"type":"integer","description":"Unix seconds."},"expires_at":{"type":"integer","description":"Unix seconds."},"signed_at":{"type":["integer","null"],"description":"Unix seconds."},"ledger":{"type":["string","null"],"enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"link":{"type":["string","null"],"format":"uri","description":"Only in the response that created it."}}},"CosignCreate":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Omit for a link anyone signed in can use once; set it to lock the request to one address."},"note":{"type":"string","maxLength":500}}},"StampFromUrl":{"type":"object","properties":{"url":{"type":"string","maxLength":2000,"format":"uri","description":"https only."},"mode":{"default":"file","type":"string","enum":["file","page"],"description":"'file' records the bytes the address serves; 'page' records a full-page PNG of how it looks (needs Browser Rendering, else 501)."},"label":{"type":"string","maxLength":80},"keep":{"default":true,"type":"boolean","description":"Keep what was fetched as the kept copy."},"visibility":{"default":"private","type":"string","enum":["private","public"]},"public":{"type":"boolean","description":"Show the record in your public log straight away. Needs a claimed handle, and is ignored without one."},"scope":{"type":"string","enum":["team","personal"]}},"required":["url"]},"ReportCreate":{"type":"object","properties":{"url":{"type":"string","maxLength":2000},"reason":{"type":"string","enum":["impersonation","unlawful","private_information","not_mine","other"]},"detail":{"type":"string","maxLength":2000},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Only if they want an answer."},"turnstile":{"type":"string","minLength":1,"maxLength":4096}},"required":["url","reason","turnstile"]},"DemoNote":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":500}},"required":["text"]},"AccountDelete":{"type":"object","properties":{"confirm":{"type":"string","const":"DELETE","description":"Must be the literal string DELETE."}},"required":["confirm"]},"EmailChange":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"The address to move this account to."},"turnstile":{"type":"string","minLength":1,"maxLength":2048}},"required":["email"]},"MagicCode":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"code":{"type":"string","pattern":"^\\d{6}$"}},"required":["email","code"]},"StorageUpdate":{"type":"object","properties":{"blocks":{"type":"integer","minimum":0,"maximum":50,"description":"Gigabytes rented on top of the free allowance."}},"required":["blocks"]},"RecoverySignIn":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"code":{"type":"string","minLength":8,"maxLength":32,"description":"One of the ten codes, dashes optional."},"turnstile":{"type":"string","minLength":1,"maxLength":4096}},"required":["email","code","turnstile"]},"TeamBrand":{"type":"object","properties":{"brand_name":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]}}},"Transfer":{"type":"object","description":"A handover of one record to another account, by link.","required":["id","stamp_id","status","to_email","created_at","expires_at","accepted_at","ledger","signature","link"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"status":{"type":"string","enum":["open","accepted","cancelled"]},"to_email":{"type":["string","null"],"format":"email","description":"Set only if the handover is locked to one address."},"from_note":{"type":["string","null"],"description":"The sender's note on the condition of the thing being handed over."},"from_photo_url":{"type":["string","null"],"format":"uri","description":"The sender's photo, once one is attached. Public with the chain of custody."},"to_note":{"type":["string","null"],"description":"The recipient's own note, set when they accept. Null while the handover is open."},"to_photo_url":{"type":["string","null"],"format":"uri","description":"The recipient's photo, set when they accept."},"created_at":{"type":"integer","description":"Unix seconds."},"expires_at":{"type":"integer","description":"Unix seconds."},"accepted_at":{"type":["integer","null"],"description":"Unix seconds."},"ledger":{"type":["string","null"],"enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"link":{"type":["string","null"],"format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/t/...","description":"The token-bearing link. Present only right after creation and on GET /api/transfers/{token}; the token cannot be recovered afterward."}}},"DeviceSignature":{"type":"object","description":"A WebAuthn assertion made by the recipient over a challenge derived from the handover itself, so it cannot have been made for a different one. Everything needed to check it offline, against the key rather than against our say-so; the proof pack spells out the steps.","required":["credential_id","public_key","challenge","assertion","at"],"properties":{"credential_id":{"type":"string","description":"base64url. Which of the account's passkeys signed."},"public_key":{"type":"string","description":"base64url COSE key. Names its own algorithm at CBOR label 3: -7 is ES256, -8 is EdDSA."},"challenge":{"type":"string","description":"base64url sha256 of the record id, both account ids and the step id. Must equal the challenge inside clientDataJSON."},"assertion":{"type":"object","description":"The assertion as the browser returned it: id, rawId, type and a response with clientDataJSON, authenticatorData and signature."},"at":{"type":"integer","description":"Unix seconds."}}},"CustodyStep":{"type":"object","description":"One step of a chain of custody. Each step is its own transaction with its own block time, so the ledger fixes the order of the chain. The handle beside it is PollyPay's word: the ledger records that a step happened and when, not who anybody is.","required":["event","handle","at","signature","ledger","explorer_url","verified","checked","memo"],"properties":{"event":{"type":"string","enum":["recorded","transferred"]},"handle":{"type":["string","null"],"description":"The page handle of whoever held it at this step, when they have one set."},"at":{"type":"integer","description":"Unix seconds."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"ledger":{"type":["string","null"],"enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"explorer_url":{"type":["string","null"],"format":"uri","description":"Every step is its own transaction, so every step has its own line to look at."},"verified":{"type":"boolean","description":"This step re-checked against the ledger on this request: its transaction exists, and its memo matches the one rebuilt from the parties. Always false while ledger is not confirmed."},"checked":{"type":"boolean","description":"Whether the ledger could be reached at all. false with verified false means we could not ask, which is not the ledger disagreeing."},"memo":{"type":["string","null"],"description":"The exact memo this step's transaction must carry, rebuilt from the ids rather than read back from storage. Check it yourself against any Solana node."},"signed_by":{"oneOf":[{"$ref":"#/components/schemas/DeviceSignature"},{"type":"null"}],"description":"Present when the person accepting the handover signed it with their own device. Without it the ledger still fixes when the step happened, but who was involved is PollyPay's word rather than a signature."},"from_note":{"type":["string","null"],"description":"At a handover: the sender's condition note."},"from_photo_url":{"type":["string","null"],"format":"uri"},"to_note":{"type":["string","null"],"description":"The recipient's note on what arrived."},"to_photo_url":{"type":["string","null"],"format":"uri"}}},"Attestation":{"type":"object","required":["attestation_id","subject","claim","status","signature","slot","block_time","created_at","explorer_url","certificate_url","short_url"],"properties":{"attestation_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"subject":{"type":"string","maxLength":140,"description":"Anything: an email, a wallet, a URL, a name."},"claim":{"type":"string","maxLength":280},"status":{"type":"string","enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"slot":{"type":["integer","null"]},"block_time":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":["string","null"],"format":"uri","example":"https://solscan.io/tx/5x7..."},"certificate_url":{"type":["string","null"],"format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/a/5x7..."},"short_url":{"type":["string","null"],"format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/c/01J..."}}},"AttestationCreateResponse":{"allOf":[{"$ref":"#/components/schemas/Attestation"},{"type":"object","properties":{"paid_with_credits":{"type":"boolean"},"balance_micro":{"type":["integer","null"],"description":"Present only when paid_with_credits is true."}}}]},"AttestedBySignature":{"type":"object","description":"Returned when {token} parses as a signature.","required":["found","kind","status","verified","subject","claim","signature","created_at"],"properties":{"found":{"const":true},"kind":{"const":"attestation"},"status":{"type":"string","enum":["not_found","pending","failed","confirmed"],"description":"'found' is true whenever there is a record at all; 'verified' is only ever true for a confirmed record. A broadcast-but-not-yet-landed record reports 'pending' here even if its internal status is 'submitted'."},"verified":{"type":"boolean"},"subject":{"type":"string"},"claim":{"type":"string"},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"slot":{"type":"integer"},"block_time":{"type":"integer","description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":["string","null"],"format":"uri"},"certificate_url":{"type":"string","format":"uri"}}},"AttestedBySubject":{"type":"object","description":"Returned when {token} is anything other than a signature: every confirmed attestation naming that subject, newest first.","required":["kind","subject","attestations","next_cursor"],"properties":{"kind":{"const":"subject"},"subject":{"type":"string"},"attestations":{"type":"array","items":{"$ref":"#/components/schemas/Attestation"}},"next_cursor":{"type":["string","null"]}}},"NotFoundLookup":{"type":"object","required":["found","status"],"properties":{"found":{"const":false},"status":{"const":"not_found"}}},"BatchItem":{"type":"object","required":["hash","index","proof"],"properties":{"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"index":{"type":"integer","description":"Leaf index in the Merkle tree."},"proof":{"type":"array","items":{"type":"string"},"description":"Sibling hashes needed to recompute the root for this leaf."}}},"Batch":{"type":"object","required":["batch_id","root","count","status","signature","slot","block_time","created_at","explorer_url","certificate_url"],"properties":{"batch_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"count":{"type":"integer","minimum":2,"maximum":500},"status":{"type":"string","enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"slot":{"type":["integer","null"]},"block_time":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":["string","null"],"format":"uri","example":"https://solscan.io/tx/5x7..."},"certificate_url":{"type":"string","format":"uri","example":"https://polly.hadi-rizvi123.workers.dev/b/01J...","description":"Present even before the batch has confirmed, unlike a stamp or attestation certificate_url."},"items":{"type":"array","items":{"$ref":"#/components/schemas/BatchItem"},"description":"Present on create and on GET /api/batches/{id}; omitted entirely on GET /api/batches/mine."}}},"BatchCreateResponse":{"allOf":[{"$ref":"#/components/schemas/Batch"},{"type":"object","properties":{"paid_with_credits":{"type":"boolean"},"balance_micro":{"type":["integer","null"],"description":"Present only when paid_with_credits is true."}}}]},"BatchVerifyOverview":{"type":"object","required":["found","batch_id","root","count","status","signature","created_at","explorer_url","hashes"],"properties":{"found":{"const":true},"batch_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"count":{"type":"integer"},"status":{"type":"string","enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":["string","null"],"format":"uri"},"hashes":{"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"description":"Every hash this batch covers."}}},"BatchVerifyNotInBatch":{"type":"object","required":["found","status","batch_id","root"],"properties":{"found":{"const":false},"status":{"const":"not_in_batch"},"batch_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."}}},"BatchVerifyItemPending":{"type":"object","required":["found","status","verified","batch_id","hash","index","proof","root","signature","created_at"],"properties":{"found":{"const":true},"status":{"type":"string","enum":["pending","failed"]},"verified":{"const":false},"batch_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"index":{"type":"integer"},"proof":{"type":"array","items":{"type":"string"}},"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"created_at":{"type":"integer","description":"Unix seconds."}}},"BatchVerifyItemConfirmed":{"type":"object","required":["found","status","verified","batch_id","hash","index","proof","root","signature","slot","block_time","created_at","explorer_url"],"properties":{"found":{"const":true},"status":{"const":"confirmed"},"verified":{"type":"boolean","description":"True only if the Merkle proof recomputes to root AND the on-chain memo re-verifies live."},"batch_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"index":{"type":"integer"},"proof":{"type":"array","items":{"type":"string"}},"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"signature":{"type":"string","pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"slot":{"type":"integer"},"block_time":{"type":"integer","description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"explorer_url":{"type":"string","format":"uri"}}},"CreditsBalance":{"type":"object","required":["balance_micro"],"properties":{"balance_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."}}},"TopupCreateResponse":{"type":"object","required":["topup_id","amount_micro"],"properties":{"topup_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"amount_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."}}},"TopupTxResponse":{"type":"object","required":["tx_b64","last_valid_block_height"],"properties":{"tx_b64":{"type":"string","description":"Base64 unsigned transaction. Sign it with the payer keypair before submitting."},"last_valid_block_height":{"type":"integer"}}},"TopupSubmitResponse":{"type":"object","required":["signature","status","balance_micro"],"properties":{"signature":{"type":"string","pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"status":{"type":"string","enum":["confirmed","submitted"],"description":"'submitted' if it did not confirm within the request; the topup keeps polling asynchronously."},"balance_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."}}},"ApiKeyCreated":{"type":"object","description":"The full key is shown only here, once, at creation or rotation. Store it then; it cannot be recovered later.","required":["id","name","key","prefix","scope","expires_at","created_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":"string","maxLength":40},"key":{"type":"string","example":"pk_...","description":"The full secret key. Never shown again."},"prefix":{"type":"string","example":"pk_abc12345"},"scope":{"type":"string","enum":["read","write"]},"expires_at":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."}}},"ApiKeySummary":{"type":"object","required":["id","name","prefix","scope","expires_at","created_at","last_used_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":"string"},"prefix":{"type":"string"},"scope":{"type":"string","enum":["read","write"]},"expires_at":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"last_used_at":{"type":["integer","null"],"description":"Unix seconds."}}},"TeamCreateResponse":{"type":"object","required":["id","name","credits_micro"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":"string"},"credits_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."}}},"TeamMember":{"type":"object","required":["id","email","role","status","invited_at","joined_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"email":{"type":"string","format":"email"},"role":{"type":"string","enum":["owner","admin","member"]},"status":{"type":"string","enum":["invited","active"]},"invited_at":{"type":"integer","description":"Unix seconds."},"joined_at":{"type":["integer","null"],"description":"Unix seconds."}}},"TeamMineResponse":{"type":"object","required":["team","my_role","members"],"properties":{"team":{"type":["object","null"],"required":["id","name","credits_micro","log_handle"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"name":{"type":"string"},"credits_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."},"log_handle":{"type":["string","null"]}}},"my_role":{"type":["string","null"],"enum":["owner","admin","member"]},"members":{"type":"array","items":{"$ref":"#/components/schemas/TeamMember"}}}},"Profile":{"type":"object","required":["handle","display_name","bio","link"],"properties":{"handle":{"type":["string","null"],"description":"Your log handle. null until one is claimed with POST /api/me/log-handle; a page is not reachable without one."},"display_name":{"type":"string","maxLength":40},"bio":{"type":"string","maxLength":600},"link":{"type":"string","description":"An https:// URL, or empty."}}},"PostRecord":{"type":"object","description":"A record attached to a post, or listed on a public page.","required":["hash","label","created_at","certificate_url"],"properties":{"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"label":{"type":["string","null"]},"created_at":{"type":"integer","description":"Unix seconds."},"certificate_url":{"type":["string","null"],"format":"uri"}}},"Post":{"type":"object","required":["id","body","created_at","record"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"body":{"type":"string","maxLength":4000},"created_at":{"type":"integer","description":"Unix seconds."},"record":{"oneOf":[{"$ref":"#/components/schemas/PostRecord"},{"type":"null"}]}}},"PublicProfilePage":{"type":"object","description":"Everything the page at /@handle shows. The account's email is never in here.","required":["handle","display_name","bio","link","since","posts","records","statements"],"properties":{"handle":{"type":"string"},"display_name":{"type":"string"},"bio":{"type":"string"},"link":{"type":["string","null"]},"since":{"type":"integer","description":"Unix seconds."},"posts":{"type":"array","items":{"$ref":"#/components/schemas/Post"},"description":"Newest first, up to 20."},"records":{"type":"array","items":{"$ref":"#/components/schemas/PostRecord"},"description":"Confirmed public stamps, newest first, up to 10."},"statements":{"type":"array","description":"Confirmed attestations, newest first, up to 10.","items":{"type":"object","required":["subject","claim","created_at","certificate_url"],"properties":{"subject":{"type":"string"},"claim":{"type":"string"},"created_at":{"type":"integer","description":"Unix seconds."},"certificate_url":{"type":["string","null"],"format":"uri"}}}}}},"MeSummary":{"type":"object","description":"Numbers for the signed-in home page: what this account (or its team) has on the record.","required":["records","statements","bundles","this_week","balance_micro","log_handle","team","recent"],"properties":{"records":{"type":"integer","description":"Confirmed stamps."},"statements":{"type":"integer","description":"Confirmed attestations."},"bundles":{"type":"integer","description":"Confirmed batches."},"this_week":{"type":"integer","description":"Confirmed stamps in the last 7 days."},"storage":{"type":"object","description":"Kept copies: what is stored, what the limit is, and what extra room costs.","properties":{"used_bytes":{"type":"integer"},"limit_bytes":{"type":"integer"},"file_limit_bytes":{"type":"integer"},"free_bytes":{"type":"integer"},"blocks":{"type":"integer","description":"Gigabytes rented on top of the free allowance."},"block_bytes":{"type":"integer"},"block_price_micro":{"type":"integer","description":"Per gigabyte per month, in micro-dollars."},"paid_until":{"type":["integer","null"],"description":"Unix seconds."}}},"balance_micro":{"type":"integer","description":"Integer micro-units of USDC (1,000,000 = $1)."},"log_handle":{"type":["string","null"]},"team":{"type":"boolean"},"recent":{"type":"array","description":"Up to 5 most recent stamps, any status.","items":{"type":"object","required":["stamp_id","hash","label","status","created_at","certificate_url"],"properties":{"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"label":{"type":["string","null"]},"status":{"type":"string","enum":["pending","submitted","confirmed","failed"],"description":"Lifecycle of the on-chain transaction backing this record."},"created_at":{"type":"integer","description":"Unix seconds."},"certificate_url":{"type":["string","null"],"format":"uri"}}}}}},"Webhook":{"type":"object","required":["id","url","events","active","failure_count","last_delivery_at","created_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"$ref":"#/components/schemas/AccountEventName"}},"active":{"type":"boolean"},"failure_count":{"type":"integer"},"last_delivery_at":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."}}},"WebhookCreateResponse":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","description":"HMAC signing secret. Shown only here, once, at creation."}}}]},"WebhookTestResponse":{"type":"object","description":"Sends a synthetic stamp.confirmed delivery right now, bypassing the retry queue.","required":["ok"],"properties":{"ok":{"type":"boolean"},"status":{"type":["integer","null"],"description":"HTTP status the endpoint returned."},"error":{"type":"string","description":"Present only if the request itself failed to send (DNS, timeout, etc)."}}},"Delivery":{"type":"object","required":["id","event","status","attempts","response_status","error","next_attempt_at","created_at","delivered_at"],"properties":{"id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"event":{"$ref":"#/components/schemas/AccountEventName"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempts":{"type":"integer"},"response_status":{"type":["integer","null"]},"error":{"type":["string","null"]},"next_attempt_at":{"type":["integer","null"],"description":"Unix seconds."},"created_at":{"type":"integer","description":"Unix seconds."},"delivered_at":{"type":["integer","null"],"description":"Unix seconds."}}},"AccountEventName":{"type":"string","enum":["stamp.confirmed","stamp.failed","attestation.confirmed","attestation.failed","batch.confirmed","batch.failed","stamp.transferred","stamp.cosigned"]},"StampEventData":{"type":"object","required":["stamp_id","hash","label","status","signature","certificate_url","created_at"],"properties":{"stamp_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"hash":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"label":{"type":["string","null"]},"status":{"type":"string"},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"certificate_url":{"type":["string","null"],"format":"uri"},"created_at":{"type":"integer","description":"Unix seconds."}}},"AttestationEventData":{"type":"object","required":["attestation_id","subject","claim","status","signature","certificate_url","created_at"],"properties":{"attestation_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"subject":{"type":"string"},"claim":{"type":"string"},"status":{"type":"string"},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"certificate_url":{"type":["string","null"],"format":"uri"},"created_at":{"type":"integer","description":"Unix seconds."}}},"BatchEventData":{"type":"object","required":["batch_id","root","count","status","signature","certificate_url","created_at"],"properties":{"batch_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"root":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"count":{"type":"integer"},"status":{"type":"string"},"signature":{"type":["string","null"],"pattern":"^[1-9A-HJ-NP-Za-km-z]{64,90}$","description":"Base58 Solana transaction signature."},"certificate_url":{"type":["string","null"],"format":"uri"},"created_at":{"type":"integer","description":"Unix seconds."}}},"TransferEventData":{"description":"Sent for stamp.transferred, to both the sender and the recipient. Same fields as StampEventData plus who moved it.","allOf":[{"$ref":"#/components/schemas/StampEventData"},{"type":"object","required":["transfer_id","from_account_id","to_account_id"],"properties":{"transfer_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"from_account_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"},"to_account_id":{"type":"string","pattern":"^[0-9A-HJKMNP-TV-Z]{26}$","example":"01J4Z9K3R8Q2W7N6M5P4X3V2C1"}}}]},"AccountWebhookEvent":{"type":"object","description":"The POST body sent to a registered webhook URL. Alongside this body, every delivery carries three headers: X-Polly-Timestamp (unix seconds), X-Polly-Event (the event name) and X-Polly-Signature. X-Polly-Signature is 'v1=<hex>', where <hex> is HMAC-SHA256 of the string '<X-Polly-Timestamp>.<raw body>' keyed by the webhook secret. Recompute it the same way and compare; reject anything more than a few minutes old. A failed delivery retries with backoff (10s, 30s, 2m, 10m, 1h, 6h) for up to 6 attempts, then stops.","required":["id","type","created_at","data"],"properties":{"id":{"type":"string"},"type":{"$ref":"#/components/schemas/AccountEventName"},"created_at":{"type":"integer","description":"Unix seconds."},"data":{"oneOf":[{"$ref":"#/components/schemas/StampEventData"},{"$ref":"#/components/schemas/AttestationEventData"},{"$ref":"#/components/schemas/BatchEventData"},{"$ref":"#/components/schemas/TransferEventData"}]}}},"Health":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"env":{"type":"string"},"cluster":{"type":"string"},"db":{"type":"boolean"},"rpc":{"type":"boolean"},"slot":{"type":"integer"},"fee_payer":{"type":"string","pattern":"^[1-9A-HJ-NP-Za-km-z]{32,44}$","description":"Base58 Solana address."},"fee_payer_sol":{"type":"number"},"fee_account":{"type":"boolean"},"turnstile":{"type":"boolean"},"email":{"type":"string"},"at":{"type":"integer","description":"Unix seconds."}},"additionalProperties":true},"StampCreate":{"type":"object","properties":{"hash":{"type":"string","pattern":"^[0-9a-f]{64}$"},"label":{"type":"string","maxLength":80,"description":"Shown on the certificate."},"public":{"type":"boolean","description":"Adds this stamp to your log stream. Requires a log handle to already be set, or it is silently kept private."},"scope":{"type":"string","enum":["team","personal"]}},"required":["hash"]},"StampPatch":{"type":"object","properties":{"hidden":{"type":"boolean"},"public":{"type":"boolean","description":"Show it in the account or team public log, or take it back out. Needs a log handle to have any effect."},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"previous_stamp_id":{"anyOf":[{"type":"string","format":"ulid","pattern":"^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$"},{"type":"null"}],"description":"Mark this record as a new version of one of yours; null clears it."},"scope":{"type":"string","enum":["team","personal"]}}},"StampFilePatch":{"type":"object","properties":{"visibility":{"type":"string","enum":["private","public"]}},"required":["visibility"]},"FileShareCreate":{"type":"object","properties":{"hours":{"default":24,"type":"integer","minimum":1,"maximum":168,"description":"How long the link works."}}},"TransferCreate":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$","description":"Omit for a link anyone can accept; set it to lock the handover to one address."},"note":{"type":"string","maxLength":500,"description":"The condition of the item as handed over."}}},"ProfileUpdate":{"type":"object","properties":{"display_name":{"type":"string","maxLength":40},"bio":{"type":"string","maxLength":600},"link":{"anyOf":[{"type":"string","maxLength":200,"format":"uri","pattern":"^https:\\/\\/.*"},{"type":"string","const":""}],"description":"An https:// URL, or \"\" to clear it."}}},"PagePostCreate":{"type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":4000},"stamp_id":{"type":"string","maxLength":40,"description":"One of your own stamps, attached so the post can show its certificate."}},"required":["body"]},"BatchStampCreate":{"type":"object","properties":{"hashes":{"minItems":2,"maxItems":500,"type":"array","items":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"sha256, lowercase hex."},"description":"No duplicates."},"scope":{"type":"string","enum":["team","personal"]}},"required":["hashes"]},"AttestationCreate":{"type":"object","properties":{"subject":{"type":"string","minLength":1,"maxLength":140},"claim":{"type":"string","minLength":1,"maxLength":280},"scope":{"type":"string","enum":["team","personal"]}},"required":["subject","claim"]},"LogHandleClaim":{"type":"object","properties":{"handle":{"type":"string","description":"Normalized to lowercase before checking. 3-24 characters."}},"required":["handle"]},"ApiKeyCreate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40},"scope":{"default":"write","type":"string","enum":["read","write"],"description":"A 'read' key can list and verify; every POST or DELETE with it is refused with read_only_key."},"expires_in_days":{"type":"integer","minimum":1,"maximum":365,"description":"Omit for a key that never expires."}},"required":["name"]},"AccountWebhookCreate":{"type":"object","properties":{"url":{"type":"string","maxLength":200,"format":"uri","pattern":"^https:\\/\\/.*","description":"Must be https."},"events":{"minItems":1,"maxItems":8,"type":"array","items":{"$ref":"#/components/schemas/AccountEventName"}}},"required":["url","events"]},"TopupCreate":{"type":"object","properties":{"amount_micro":{"type":"integer","minimum":1000000,"maximum":100000000,"description":"$1 to $100."}},"required":["amount_micro"]},"TopupTx":{"type":"object","properties":{"payer":{"type":"string","pattern":"^[1-9A-HJ-NP-Za-km-z]{32,44}$"}},"required":["payer"]},"TopupSubmit":{"type":"object","properties":{"signed_tx_b64":{"type":"string","maxLength":8192,"pattern":"^[A-Za-z0-9+/=]+$"}},"required":["signed_tx_b64"]},"TeamCreate":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":40}},"required":["name"]},"TeamInvite":{"type":"object","properties":{"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["email"]},"TeamRoleUpdate":{"type":"object","properties":{"role":{"type":"string","enum":["admin","member"]}},"required":["role"]}},"responses":{"Unauthorized":{"description":"No session cookie and no valid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No session cookie and no valid API key.","code":"no_session"}}}},"Forbidden":{"description":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Signed in, but not allowed to do this (e.g. not a team admin, or a read-only API key on a write).","code":"forbidden"}}}},"NotFound":{"description":"No such record, or it belongs to a different account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"No such record, or it belongs to a different account.","code":"not_found"}}}},"BadRequest":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"The request body failed validation.","code":"invalid_request"}}}},"IdempotencyConflict":{"description":"That Idempotency-Key was already used with a different request body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"That Idempotency-Key was already used with a different request body.","code":"idempotency_key_reused"}}}},"TooManyRequests":{"description":"Rate- or quota-limited. Retry-After says how many seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You have used your free stamps for this hour. Add funds to keep going without waiting.","code":"quota_reached"}}}}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Any string up to 128 characters, unique per request. A retry with the same key and the same body returns the first response again with Idempotent-Replayed: true instead of writing a second record. Kept for 24 hours; not remembered for a request that failed with a 5xx.","schema":{"type":"string","maxLength":128}}}}}