{"openapi":"3.1.1","info":{"title":"anon.legal API","version":"1.1.0","description":"Create immutable referrer-shielding links without an account. Save the key once; it also authorizes private inspection. Keys belong only in Authorization, never in URLs. All API responses are uncached. No batch or idempotent creation is supported; do not automatically retry a POST after an ambiguous failure.","termsOfService":"https://anon.legal/terms"},"servers":[{"url":"https://api.anon.legal/v1"},{"url":"https://anon.legal/v1","description":"Same API on the main host."},{"url":"https://api.anon.legal/api/v1","description":"Compatibility prefix."}],"externalDocs":{"url":"https://anon.legal/api"},"security":[],"paths":{"/stats":{"get":{"operationId":"publicStatistics","summary":"Current stored links and total counted openings.","responses":{"200":{"description":"Aggregate only, cached internally for up to 10 seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicStats"}}}},"400":{"description":"Invalid input or link ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Allow":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/health":{"get":{"operationId":"health","summary":"Check application, storage and opening configuration.","responses":{"200":{"description":"Ready.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}},"400":{"description":"Invalid input or link ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Allow":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}},"head":{"operationId":"healthHead","summary":"Readiness status and headers without a body.","responses":{"200":{"description":"Ready."},"429":{"description":"Rate limited; Retry-After is set."},"503":{"description":"Not ready."}}}},"/openapi.json":{"get":{"operationId":"schema","summary":"Download this OpenAPI schema.","responses":{"200":{"description":"OpenAPI 3.1 JSON.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid input or link ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Allow":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/links":{"post":{"operationId":"createLink","summary":"Create a link and receive its key once.","description":"JSON body at most 16384 bytes. No idempotency guarantee. Password creation is limited separately. A 503 or network timeout can occur after insertion, so retrying can create another link. A password or opening allowance requires a confirmation POST. GET/HEAD never spend the allowance.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLink"}}}},"responses":{"201":{"description":"Created. Save key privately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedLink"}}}},"400":{"description":"Invalid input or link ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Allow":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/links/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,160}$"}}],"get":{"operationId":"inspectLink","summary":"Inspect a link privately without opening it.","description":"Read-only, including expired links still present in storage. Returns aggregate openings and creation time (null for legacy links). Does not spend openings, purge expiry or expose credential hashes. Deleted links, and expired links already purged by a visitor request, return 404.","security":[{"deletionKey":[]}],"responses":{"200":{"description":"Private link settings and current state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkState"}}}},"400":{"description":"Invalid input or link ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or malformed bearer key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Link not found or incorrect key; indistinguishable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Allow":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}},"head":{"operationId":"inspectLinkHead","summary":"Check access to a link without a response body.","security":[{"deletionKey":[]}],"responses":{"200":{"description":"Same status and headers as GET; no body."},"400":{"description":"Same status and headers as GET; no body."},"401":{"description":"Same status and headers as GET; no body."},"404":{"description":"Same status and headers as GET; no body."},"429":{"description":"Same status and headers as GET; no body."},"503":{"description":"Same status and headers as GET; no body."}}},"delete":{"operationId":"deleteLink","summary":"Permanently delete a link.","security":[{"deletionKey":[]}],"responses":{"204":{"description":"Deleted; empty body."},"400":{"description":"Invalid input or link ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or malformed bearer key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Link not found or incorrect key; indistinguishable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Allow":{"schema":{"type":"string"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}}},"components":{"securitySchemes":{"deletionKey":{"type":"http","scheme":"bearer","bearerFormat":"43-character base64url key","description":"Returned once at creation. Also grants read access; keep it private."}},"responses":{"RateLimited":{"description":"Request limit reached. Wait before retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"required":true,"schema":{"type":"integer","minimum":1},"description":"Seconds to wait (currently 1 or 60)."}}},"Unavailable":{"description":"Temporarily unavailable. A failed POST may already have created a link.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Optional delay in seconds."}}}},"schemas":{"PublicStats":{"type":"object","required":["links","opens","since"],"additionalProperties":false,"properties":{"links":{"type":"string","pattern":"^[0-9]+$"},"opens":{"type":"string","pattern":"^[0-9]+$"},"since":{"type":"string","format":"date-time"}}},"Health":{"type":"object","required":["ok"],"properties":{"ok":{"const":true}},"additionalProperties":false},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","enum":["invalid_request","nested_redirect","unauthorized","not_found","method_not_allowed","rate_limited","unavailable"]},"message":{"type":"string"}}},"CreateLink":{"type":"object","required":["url"],"properties":{"url":{"type":"string","minLength":1,"maxLength":8192,"description":"HTTP/HTTPS without embedded credentials. Missing protocol defaults to HTTPS; outer whitespace is trimmed. Normalized URL must also fit 8192 characters."},"format":{"type":"string","enum":["token","uuid","words"],"default":"token"},"length":{"type":"integer","minimum":8,"maximum":128,"default":16,"description":"For token: mixed needs at least 8, lowercase 10, hex 12."},"alphabet":{"type":"string","enum":["mixed","lowercase","hex"],"default":"mixed"},"word_count":{"type":"integer","enum":[6,9,12,15],"default":12,"description":"Independent BIP39 words, not a wallet seed."},"expires_in":{"type":["integer","null"],"minimum":60,"maximum":315360000,"default":null},"not_before_in":{"type":["integer","null"],"minimum":0,"maximum":31536000,"default":0,"description":"Opening delay in seconds, strictly before expiry."},"open_mode":{"type":"string","enum":["direct","page"],"default":"direct"},"password":{"anyOf":[{"type":"string","minLength":8,"maxLength":128},{"enum":["",null]}],"description":"Optional opening password, never returned."},"max_visits":{"type":["integer","null"],"minimum":1,"maximum":10000},"page_title":{"type":["string","null"],"maxLength":80},"page_note":{"type":["string","null"],"maxLength":240}}},"CreatedLink":{"type":"object","required":["id","url","expires_at","available_at","format","password_protected","max_visits","open_mode","key"],"properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,160}$"},"url":{"type":"string","format":"uri","description":"Public anon.legal link."},"expires_at":{"type":["string","null"],"format":"date-time"},"available_at":{"type":["string","null"],"format":"date-time"},"format":{"type":"string","enum":["token","uuid","words"]},"password_protected":{"type":"boolean"},"max_visits":{"type":["integer","null"],"minimum":1,"maximum":10000},"open_mode":{"type":"string","enum":["direct","page"]},"key":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$","description":"Returned only once."},"delete_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$","deprecated":true,"description":"Compatibility alias of key."},"alphabet":{"type":"string","enum":["mixed","lowercase","hex"],"description":"Only for token format."},"word_count":{"type":"integer","enum":[6,9,12,15],"description":"Only for words format."}}},"LinkState":{"type":"object","required":["id","url","expires_at","available_at","format","password_protected","max_visits","open_mode","destination","status","remaining_visits","page_title","page_note","created_at","age_seconds","visits","stats_since"],"properties":{"id":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,160}$"},"url":{"type":"string","format":"uri","description":"Public anon.legal link."},"expires_at":{"type":["string","null"],"format":"date-time"},"available_at":{"type":["string","null"],"format":"date-time"},"format":{"type":"string","enum":["token","uuid","words"]},"password_protected":{"type":"boolean"},"max_visits":{"type":["integer","null"],"minimum":1,"maximum":10000},"open_mode":{"type":"string","enum":["direct","page"]},"destination":{"type":"string","format":"uri"},"status":{"type":"string","enum":["active","scheduled","expired","exhausted"],"description":"Expiry takes precedence, then schedule, then exhausted allowance."},"remaining_visits":{"type":["integer","null"],"minimum":0,"maximum":10000},"page_title":{"type":["string","null"],"maxLength":80},"page_note":{"type":["string","null"],"maxLength":240},"created_at":{"type":["string","null"],"format":"date-time"},"age_seconds":{"type":["integer","null"],"minimum":0},"visits":{"type":"string","pattern":"^[0-9]+$","description":"Successful handoffs since stats_since, including browser previews of direct links. Not unique people or confirmed destination loads."},"stats_since":{"type":"string","format":"date-time"}},"additionalProperties":false}}}}