{"openapi":"3.1.0","info":{"title":"zpe universe API","version":"1.0.0","description":"Every app's contract, merged under the mount each app is served at. The aggregate is generated by tools/emit.mjs and its `x-registry` block records the sha256 of the file each path came from, so `tools/verify.sh` can tell a stale aggregate from a current one instead of trusting the file's date. THIS COPY IS THE PUBLIC PROJECTION of that record: an app is listed only when its lifecycle is `stable` or `beta`, and `x-public.withheld` names every app left out with the reason. The registry's own file, every contract including the unfinished ones, is `api.json` in the repository."},"servers":[{"url":"https://zpe.vc","description":"the zone every app answers on"}],"x-registry":{"generated_by":"tools/emit.mjs","counts":{"contracts":20,"operations":196},"enrolled":["zpe-booking","zpe-chat","zpe-comments","zpe-crew","zpe-feedback","zpe-files","zpe-forms","zpe-links","zpe-mind-mapper","zpe-newsletter","zpe-notes","zpe-pact","zpe-paste","zpe-receptionist","zpe-repo-access","zpe-reports","zpe-reviews","zpe-scraper","zpe-screens","zpe-stats","zpe-status","zpe-store","zpe-tv","zpe-uw","zpe-vc"],"apps":{"zpe-booking":{"document":"apis/zpe-booking.json","sha256":"9d5931e8be4376b3a6b3de640e44f8a7b81df4a470c2b5bda2ef126de0e68fca","bytes":33912,"title":"zpe booking API","version":"1.0.0","lifecycle":"beta","server":"/booking","base":"/booking/_/api/v1","paths":6,"operations":7},"zpe-comments":{"document":"apis/zpe-comments.json","sha256":"08673dafb7ad8fb25d67196609f93a4399b71fadca35a76b9ea9a31387e37dbc","bytes":30309,"title":"zpe-comments","version":"4.1.1","lifecycle":"beta","server":"/comments","base":"/comments/_/api/v1","paths":7,"operations":9},"zpe-crew":{"document":"apis/zpe-crew.json","sha256":"e8b729990a6a9fad935f06c52566f10f7e85e75e034714a42e00677e3e137519","bytes":30043,"title":"crew — the team ledger","version":"3.0.0","lifecycle":"beta","server":"/crew","base":"/crew/_/api/v1","paths":7,"operations":7},"zpe-feedback":{"document":"apis/zpe-feedback.json","sha256":"fed278a3c72c9ee70e314379ad21bc9e4686b352df7404d6a0750e9758524fa1","bytes":5017,"title":"zpe-feedback","version":"3.0.1","lifecycle":"beta","server":"/feedback","base":"/feedback/_/api/v1/threads","paths":1,"operations":1},"zpe-files":{"document":"apis/zpe-files.json","sha256":"26f28ec4228a2ca6250d215d63d6cc0e36f87455a4751d9bb57548015c8b2545","bytes":57777,"title":"zpe files","version":"5.0.0","lifecycle":"beta","server":"/files","base":"/files/_/api/v1","paths":11,"operations":15},"zpe-forms":{"document":"apis/zpe-forms.json","sha256":"e0c4776763ee8f097a777c51e081c7116ba68ef6d18a0b8a5c36ba3312c78dee","bytes":49553,"title":"zpe-forms","version":"7.0.2","lifecycle":"stable","server":"/forms","base":"/forms/_/api/v1","paths":8,"operations":12},"zpe-links":{"document":"apis/zpe-links.json","sha256":"5853a3d6f253e8f9c6cc43378a2d0c01b79682e6d596a9bc49bb99f71c426b1d","bytes":169408,"title":"Links API","version":"3.0.0","lifecycle":"stable","server":"/links","base":"/links/","paths":44,"operations":56},"zpe-mind-mapper":{"document":"apis/zpe-mind-mapper.json","sha256":"0995a44fc0d12aeb1cea410e95e04b319f3204b15551b8902112ad2977453231","bytes":90560,"title":"mind-mapper API","version":"1.0.0","lifecycle":"beta","server":"/mindmap","base":"/mindmap/","paths":20,"operations":23},"zpe-newsletter":{"document":"apis/zpe-newsletter.json","sha256":"0c9885230f717693bbb2c10f6402ea19853e6ac89140f493bac23298576926e7","bytes":9732,"title":"zpe-newsletter","version":"1.0.1","lifecycle":"stable","server":"/newsletter","base":"/newsletter/_/api/v1","paths":2,"operations":2},"zpe-notes":{"document":"apis/zpe-notes.json","sha256":"c5e2f7a9a0c24e6925d0bc9c2fdcbb04aaa1f962682a5fed34a435ecab2a74c7","bytes":69426,"title":"zpe notes API","version":"1.0.0","lifecycle":"beta","server":"/notes","base":"/notes/_/api/v1","paths":17,"operations":25},"zpe-pact":{"document":"apis/zpe-pact.json","sha256":"e055a37c148de527b1cc64d6da0a33b0dad5a68542d82fd840727b9b13271c3e","bytes":32660,"title":"pact — the contract layer","version":"2.0.1","lifecycle":"beta","server":"/pact","base":"/pact/_/api/v1/pacts","paths":5,"operations":5},"zpe-paste":{"document":"apis/zpe-paste.json","sha256":"982077205357bb93732b397ac455e55f36087ed7cc20954770bfc405f8407346","bytes":29988,"title":"zpe-paste API","version":"4.0.1","lifecycle":"beta","server":"/paste","base":"/paste/_/api/v1","paths":6,"operations":7},"zpe-receptionist":{"document":"apis/zpe-receptionist.json","sha256":"965e8021337ad055f3c7a8e1e7292803abe897343d1c122fe24664c0d7df2915","bytes":7972,"title":"zpe-receptionist","version":"4.0.1","lifecycle":"beta","server":"/receptionist","base":"/receptionist/_/api/v1/inbox","paths":2,"operations":2},"zpe-reports":{"document":"apis/zpe-reports.json","sha256":"aff7ada2a991ef6276ff107e557414622345260d980bb1632284330e447b6e0b","bytes":11404,"title":"zpe-reports","version":"1.0.1","lifecycle":"beta","server":"/reports","base":"/reports/_/api/v1/reports","paths":2,"operations":2},"zpe-scraper":{"document":"apis/zpe-scraper.json","sha256":"0851eab0d2b2a3a777bfcf26fc4befd93d9ec4997e25cd1bb5beac8ddeb2e8b6","bytes":38049,"title":"zpe-scraper","version":"5.0.0","lifecycle":"beta","server":"","base":"/_/api/v1","paths":8,"operations":10},"zpe-stats":{"document":"apis/zpe-stats.json","sha256":"fe317fef53ddae1ade416a9cf01e710752c13f107ad9870c26d9ba05f1a20e9a","bytes":53494,"title":"zpe-stats","version":"1.1.5","lifecycle":"stable","server":"/analytics","base":"/analytics/api/v1/data/runs","paths":3,"operations":4},"zpe-status":{"document":"apis/zpe-status.json","sha256":"29dd2a01a28236e9b4944c34aec79404006a7584f653dbfecec8be5b4f87c70c","bytes":6976,"title":"zpe-status","version":"3.0.1","lifecycle":"stable","server":"/","base":"//_/api/v1/universe","paths":1,"operations":1},"zpe-store":{"document":"apis/zpe-store.json","sha256":"2893f4c7db7a7f796dee762205b1b7a3a1b137e52c7b5ae85479ce3870ed1a5a","bytes":4896,"title":"zpe-store","version":"3.0.1","lifecycle":"beta","server":"/store","base":"/store/_/api/v1/orders","paths":1,"operations":1},"zpe-tv":{"document":"apis/zpe-tv.json","sha256":"7817b8b18fe58ada5c4945d97f0069097d5ccc4db837fb19444de11f41a88edc","bytes":13559,"title":"tv-cleaner","version":"1.0.1","lifecycle":"beta","server":"/tv","base":"/tv/_/api/v1","paths":4,"operations":5},"zpe-vc":{"document":"apis/zpe-vc.json","sha256":"9e6f9783bd7f2975fb927f21ca5a44788583303f1b5193eb892cbf9ad27c73f3","bytes":6566,"title":"zpe front door","version":"1.0.0","lifecycle":"stable","server":"https://zpe.vc","base":"https://zpe.vc/_/api/v1","paths":2,"operations":2}},"owners":{"//_/api/v1/universe":"zpe-status","/_/api/v1/balance":"zpe-scraper","/_/api/v1/domains":"zpe-scraper","/_/api/v1/domains/{domain}":"zpe-scraper","/_/api/v1/keys":"zpe-scraper","/_/api/v1/keys/{id}":"zpe-scraper","/_/api/v1/map":"zpe-scraper","/_/api/v1/scrape":"zpe-scraper","/_/api/v1/usage":"zpe-scraper","/analytics/api/v1/data/runs":"zpe-stats","/analytics/api/v1/data/runs/latest":"zpe-stats","/analytics/api/v1/data/runs/{run_id}":"zpe-stats","/booking/_/api/v1/bookings":"zpe-booking","/booking/_/api/v1/bookings/{id}":"zpe-booking","/booking/_/api/v1/bookings/{id}/cancel":"zpe-booking","/booking/_/api/v1/event-types":"zpe-booking","/booking/_/api/v1/event-types/{id}":"zpe-booking","/booking/_/api/v1/me":"zpe-booking","/comments/_/api/v1/bootstrap":"zpe-comments","/comments/_/api/v1/comments":"zpe-comments","/comments/_/api/v1/comments/{id}":"zpe-comments","/comments/_/api/v1/comments/{id}/report":"zpe-comments","/comments/_/api/v1/config":"zpe-comments","/comments/_/api/v1/operator/moderate":"zpe-comments","/comments/_/api/v1/operator/queue":"zpe-comments","/crew/_/api/v1/channel-events":"zpe-crew","/crew/_/api/v1/clients":"zpe-crew","/crew/_/api/v1/clients/{id}/messages":"zpe-crew","/crew/_/api/v1/events":"zpe-crew","/crew/_/api/v1/pacts/{id}/accept":"zpe-crew","/crew/_/api/v1/rooms":"zpe-crew","/crew/_/api/v1/upwork":"zpe-crew","/feedback/_/api/v1/threads":"zpe-feedback","/files/_/api/v1/copy":"zpe-files","/files/_/api/v1/counts":"zpe-files","/files/_/api/v1/delete":"zpe-files","/files/_/api/v1/download":"zpe-files","/files/_/api/v1/list":"zpe-files","/files/_/api/v1/mkdir":"zpe-files","/files/_/api/v1/rename":"zpe-files","/files/_/api/v1/search":"zpe-files","/files/_/api/v1/stat":"zpe-files","/files/_/api/v1/trash":"zpe-files","/files/_/api/v1/upload":"zpe-files","/forms/_/api/v1/console/forms":"zpe-forms","/forms/_/api/v1/console/forms/{id}":"zpe-forms","/forms/_/api/v1/console/forms/{id}/submissions":"zpe-forms","/forms/_/api/v1/console/forms/{id}/submissions/{submissionId}":"zpe-forms","/forms/_/api/v1/console/me":"zpe-forms","/forms/_/api/v1/console/settings":"zpe-forms","/forms/_/api/v1/forms/{publicId}/submissions":"zpe-forms","/forms/_/api/v1/submissions":"zpe-forms","/links/_/api/v1/bundles":"zpe-links","/links/_/api/v1/bundles/{id}":"zpe-links","/links/_/api/v1/bundles/{id}/analytics":"zpe-links","/links/_/api/v1/bundles/{id}/archive":"zpe-links","/links/_/api/v1/bundles/{id}/breakdown":"zpe-links","/links/_/api/v1/bundles/{id}/links":"zpe-links","/links/_/api/v1/bundles/{id}/links/{linkId}":"zpe-links","/links/_/api/v1/bundles/{id}/unarchive":"zpe-links","/links/_/api/v1/links":"zpe-links","/links/_/api/v1/links/{id}":"zpe-links","/links/_/api/v1/links/{id}/analytics":"zpe-links","/links/_/api/v1/links/{id}/breakdown":"zpe-links","/links/_/api/v1/links/{id}/bundles":"zpe-links","/links/_/api/v1/links/{id}/disable":"zpe-links","/links/_/api/v1/links/{id}/enable":"zpe-links","/links/_/api/v1/links/{id}/qr":"zpe-links","/links/_/api/v1/links/{id}/slugs":"zpe-links","/links/_/api/v1/links/{id}/slugs/{slug}":"zpe-links","/links/_/api/v1/links/{id}/slugs/{slug}/disable":"zpe-links","/links/_/api/v1/links/{id}/slugs/{slug}/enable":"zpe-links","/links/_/api/v1/links/{id}/timeline":"zpe-links","/links/_/api/v1/pages":"zpe-links","/links/_/api/v1/pages/{name}":"zpe-links","/links/_/api/v1/pages/{name}/blocks":"zpe-links","/links/_/api/v1/pages/{name}/blocks/{id}":"zpe-links","/links/_/api/v1/pages/{name}/publish":"zpe-links","/links/_/api/v1/pages/{name}/submissions":"zpe-links","/links/_/api/v1/pages/{name}/unpublish":"zpe-links","/links/_/api/v1/slugs/{slug}":"zpe-links","/links/new":"zpe-links","/links/new/api/form":"zpe-links","/links/new/api/submit":"zpe-links","/links/new/api/{receipt}/content":"zpe-links","/links/new/api/{receipt}/files":"zpe-links","/links/new/api/{receipt}/message":"zpe-links","/links/new/api/{receipt}/message/sign":"zpe-links","/links/new/api/{receipt}/release":"zpe-links","/links/new/r/{receipt}":"zpe-links","/links/new/r/{receipt}/claim":"zpe-links","/links/new/r/{receipt}/confirm":"zpe-links","/links/new/r/{receipt}/email":"zpe-links","/links/new/r/{receipt}/f/{id}":"zpe-links","/mindmap/_/api/v1/results":"zpe-mind-mapper","/mindmap/api/account":"zpe-mind-mapper","/mindmap/api/account/attach":"zpe-mind-mapper","/mindmap/api/account/start":"zpe-mind-mapper","/mindmap/api/groups":"zpe-mind-mapper","/mindmap/api/groups/join":"zpe-mind-mapper","/mindmap/api/groups/leave":"zpe-mind-mapper","/mindmap/api/groups/{id}":"zpe-mind-mapper","/mindmap/api/groups/{id}/dots":"zpe-mind-mapper","/mindmap/api/groups/{id}/invites":"zpe-mind-mapper","/mindmap/api/groups/{id}/pair/{principalId}":"zpe-mind-mapper","/mindmap/api/groups/{id}/share":"zpe-mind-mapper","/mindmap/api/groups/{id}/stats":"zpe-mind-mapper","/mindmap/api/groups/{id}/tests":"zpe-mind-mapper","/mindmap/api/health":"zpe-mind-mapper","/mindmap/api/me":"zpe-mind-mapper","/mindmap/api/profile":"zpe-mind-mapper","/mindmap/api/results":"zpe-mind-mapper","/mindmap/api/session":"zpe-mind-mapper","/mindmap/api/signout":"zpe-mind-mapper","/newsletter/_/api/v1/newsletter":"zpe-newsletter","/newsletter/_/api/v1/send":"zpe-newsletter","/notes/_/api/v1/attachments":"zpe-notes","/notes/_/api/v1/docs/node":"zpe-notes","/notes/_/api/v1/docs/node/{id}":"zpe-notes","/notes/_/api/v1/docs/node/{id}/rename":"zpe-notes","/notes/_/api/v1/docs/tree":"zpe-notes","/notes/_/api/v1/files/{noteId}/{fileId}":"zpe-notes","/notes/_/api/v1/notes":"zpe-notes","/notes/_/api/v1/notes/merge":"zpe-notes","/notes/_/api/v1/notes/timeline":"zpe-notes","/notes/_/api/v1/notes/{id}":"zpe-notes","/notes/_/api/v1/notes/{id}/share":"zpe-notes","/notes/_/api/v1/public/note/raw/{public_id}":"zpe-notes","/notes/_/api/v1/public/note/{public_id}":"zpe-notes","/notes/_/api/v1/search":"zpe-notes","/notes/_/api/v1/settings":"zpe-notes","/notes/_/api/v1/stats":"zpe-notes","/notes/_/api/v1/tags":"zpe-notes","/pact/_/api/v1/pacts":"zpe-pact","/pact/_/api/v1/pacts/{id}":"zpe-pact","/pact/_/api/v1/pacts/{id}/evidence":"zpe-pact","/pact/_/api/v1/pacts/{id}/invite":"zpe-pact","/pact/_/api/v1/pacts/{id}/sign":"zpe-pact","/paste/_/api/v1/guest/paste":"zpe-paste","/paste/_/api/v1/me":"zpe-paste","/paste/_/api/v1/paste":"zpe-paste","/paste/_/api/v1/paste/{id}":"zpe-paste","/paste/_/api/v1/paste/{id}/consume":"zpe-paste","/paste/_/api/v1/pastes":"zpe-paste","/receptionist/_/api/v1/inbox":"zpe-receptionist","/receptionist/_/api/v1/inbox/{id}":"zpe-receptionist","/reports/_/api/v1/reports":"zpe-reports","/reports/_/api/v1/reports/{slug}":"zpe-reports","/store/_/api/v1/orders":"zpe-store","/tv/_/api/v1/blocks":"zpe-tv","/tv/_/api/v1/brands":"zpe-tv","/tv/_/api/v1/catalog":"zpe-tv","/tv/_/api/v1/dataset":"zpe-tv","/u/{slug}":"zpe-links","/u/{slug}/card.vcf":"zpe-links","https://zpe.vc/_/api/v1/home":"zpe-vc","https://zpe.vc/_/api/v1/openapi.json":"zpe-vc"},"operations":[{"app":"zpe-booking","operationId":"listBookings","method":"get","path":"/booking/_/api/v1/bookings","summary":"List the account's bookings"},{"app":"zpe-booking","operationId":"getBooking","method":"get","path":"/booking/_/api/v1/bookings/{id}","summary":"Read one booking"},{"app":"zpe-booking","operationId":"cancelBooking","method":"post","path":"/booking/_/api/v1/bookings/{id}/cancel","summary":"Cancel a booking"},{"app":"zpe-booking","operationId":"listEventTypes","method":"get","path":"/booking/_/api/v1/event-types","summary":"List the account's meeting types"},{"app":"zpe-booking","operationId":"createEventType","method":"post","path":"/booking/_/api/v1/event-types","summary":"Create a meeting type"},{"app":"zpe-booking","operationId":"getEventType","method":"get","path":"/booking/_/api/v1/event-types/{id}","summary":"Read one meeting type"},{"app":"zpe-booking","operationId":"getMe","method":"get","path":"/booking/_/api/v1/me","summary":"Read the caller's profile and availability"},{"app":"zpe-comments","operationId":"getBootstrap","method":"get","path":"/comments/_/api/v1/bootstrap","summary":"One post's comment tree, with the settings a composer needs"},{"app":"zpe-comments","operationId":"getComments","method":"get","path":"/comments/_/api/v1/comments","summary":"One post's comment tree"},{"app":"zpe-comments","operationId":"postComments","method":"post","path":"/comments/_/api/v1/comments","summary":"Post an anonymous comment behind a Turnstile answer"},{"app":"zpe-comments","operationId":"deleteCommentsById","method":"delete","path":"/comments/_/api/v1/comments/{id}","summary":"Delete your own comment, or any comment on a site you own"},{"app":"zpe-comments","operationId":"patchCommentsById","method":"patch","path":"/comments/_/api/v1/comments/{id}","summary":"Edit your own comment inside the edit window"},{"app":"zpe-comments","operationId":"postCommentsByIdReport","method":"post","path":"/comments/_/api/v1/comments/{id}/report","summary":"Report a comment to the site's owner, once per reader"},{"app":"zpe-comments","operationId":"getConfig","method":"get","path":"/comments/_/api/v1/config","summary":"One site's comment settings, its Turnstile sitekey and the length limit"},{"app":"zpe-comments","operationId":"postOperatorModerate","method":"post","path":"/comments/_/api/v1/operator/moderate","summary":"Moderate, ban or unban with a key instead of the console"},{"app":"zpe-comments","operationId":"getOperatorQueue","method":"get","path":"/comments/_/api/v1/operator/queue","summary":"The moderation queue, with its open report counts"},{"app":"zpe-crew","operationId":"postChannelEvents","method":"post","path":"/crew/_/api/v1/channel-events","summary":"Keep a page of the channel's events in this ledger, keyed by the channel's own id. A re-post of a page writes nothing and says so."},{"app":"zpe-crew","operationId":"postClients","method":"post","path":"/crew/_/api/v1/clients","summary":"Store a client the agency pull found, or update the one already here."},{"app":"zpe-crew","operationId":"postClientsByIdMessages","method":"post","path":"/crew/_/api/v1/clients/{id}/messages","summary":"Remember what a client said, with the day its words stop being served."},{"app":"zpe-crew","operationId":"postEvents","method":"post","path":"/crew/_/api/v1/events","summary":"Push one line of the local ledger's history, under the key that makes a second push free."},{"app":"zpe-crew","operationId":"postPactsByIdAccept","method":"post","path":"/crew/_/api/v1/pacts/{id}/accept","summary":"Send the person who accepted to the contract layer's page for this pact."},{"app":"zpe-crew","operationId":"postRooms","method":"post","path":"/crew/_/api/v1/rooms","summary":"Pour one pull's rooms: the client's half into chat, his own into the ledger. A re-post writes nothing."},{"app":"zpe-crew","operationId":"postUpwork","method":"post","path":"/crew/_/api/v1/upwork","summary":"Replace one ledger's Upwork snapshot with the document the mini read. `?account=` names the ledger; absent, the key's own. A ledger the key does not own needs the door's `can_write` for that ledger's channel, granted by its owner."},{"app":"zpe-feedback","operationId":"getThreads","method":"get","path":"/feedback/_/api/v1/threads","summary":"Every thread in this account, newest first, with its board, its moderation state and its reply count"},{"app":"zpe-files","operationId":"postCopy","method":"post","path":"/files/_/api/v1/copy","summary":"Copy a file or a whole folder to another path"},{"app":"zpe-files","operationId":"postCounts","method":"post","path":"/files/_/api/v1/counts","summary":"Count the direct children of one or more folders"},{"app":"zpe-files","operationId":"deleteDelete","method":"delete","path":"/files/_/api/v1/delete","summary":"Delete a file or folder, or move it to the trash with `?soft=1`"},{"app":"zpe-files","operationId":"getDownload","method":"get","path":"/files/_/api/v1/download","summary":"Download one file's bytes"},{"app":"zpe-files","operationId":"getList","method":"get","path":"/files/_/api/v1/list","summary":"List one folder, sorted, folders first"},{"app":"zpe-files","operationId":"postMkdir","method":"post","path":"/files/_/api/v1/mkdir","summary":"Create a folder, with its parent markers"},{"app":"zpe-files","operationId":"postRename","method":"post","path":"/files/_/api/v1/rename","summary":"Rename or move a file or folder"},{"app":"zpe-files","operationId":"getSearch","method":"get","path":"/files/_/api/v1/search","summary":"Search the account's files by name"},{"app":"zpe-files","operationId":"getStat","method":"get","path":"/files/_/api/v1/stat","summary":"Read one file's metadata without its bytes"},{"app":"zpe-files","operationId":"deleteTrash","method":"delete","path":"/files/_/api/v1/trash","summary":"List the trash, restore from it, or empty it"},{"app":"zpe-files","operationId":"getTrash","method":"get","path":"/files/_/api/v1/trash","summary":"List the trash, restore from it, or empty it"},{"app":"zpe-files","operationId":"postTrash","method":"post","path":"/files/_/api/v1/trash","summary":"List the trash, restore from it, or empty it"},{"app":"zpe-files","operationId":"deleteUpload","method":"delete","path":"/files/_/api/v1/upload","summary":"Upload a file, or write and remove one raw PUT at a time"},{"app":"zpe-files","operationId":"postUpload","method":"post","path":"/files/_/api/v1/upload","summary":"Upload a file, or write and remove one raw PUT at a time"},{"app":"zpe-files","operationId":"putUpload","method":"put","path":"/files/_/api/v1/upload","summary":"Upload a file, or write and remove one raw PUT at a time"},{"app":"zpe-forms","operationId":"getConsoleForms","method":"get","path":"/forms/_/api/v1/console/forms","summary":"List this account's forms, oldest first, with a submission count and the last one's time"},{"app":"zpe-forms","operationId":"postConsoleForms","method":"post","path":"/forms/_/api/v1/console/forms","summary":"Create a form in this account; the slug comes from the name and the public id is minted here"},{"app":"zpe-forms","operationId":"deleteConsoleFormsById","method":"delete","path":"/forms/_/api/v1/console/forms/{id}","summary":"Delete a form and every submission it received"},{"app":"zpe-forms","operationId":"getConsoleFormsById","method":"get","path":"/forms/_/api/v1/console/forms/{id}","summary":"One form of this account, with its submission stats and its last 30 days"},{"app":"zpe-forms","operationId":"patchConsoleFormsById","method":"patch","path":"/forms/_/api/v1/console/forms/{id}","summary":"Rename a form or replace its question set; the slug and the public id do not move"},{"app":"zpe-forms","operationId":"getConsoleFormsByIdSubmissions","method":"get","path":"/forms/_/api/v1/console/forms/{id}/submissions","summary":"This form's submissions, newest first"},{"app":"zpe-forms","operationId":"deleteConsoleFormsByIdSubmissionsById","method":"delete","path":"/forms/_/api/v1/console/forms/{id}/submissions/{submissionId}","summary":"Delete one submission of this account's form"},{"app":"zpe-forms","operationId":"getConsoleMe","method":"get","path":"/forms/_/api/v1/console/me","summary":"Who the caller is, as the door names them"},{"app":"zpe-forms","operationId":"getConsoleSettings","method":"get","path":"/forms/_/api/v1/console/settings","summary":"What this deployment does about mail, which is not a setting a form can hold"},{"app":"zpe-forms","operationId":"optionsFormsByIdSubmissions","method":"options","path":"/forms/_/api/v1/forms/{publicId}/submissions","summary":"CORS preflight for the write path, answered before any credential"},{"app":"zpe-forms","operationId":"postFormsByIdSubmissions","method":"post","path":"/forms/_/api/v1/forms/{publicId}/submissions","summary":"Write one submission to the form that owns this public id"},{"app":"zpe-forms","operationId":"getSubmissions","method":"get","path":"/forms/_/api/v1/submissions","summary":"Read the 200 most recent submissions of every form this account owns"},{"app":"zpe-links","operationId":"getBundles","method":"get","path":"/links/_/api/v1/bundles","summary":"List bundles"},{"app":"zpe-links","operationId":"postBundles","method":"post","path":"/links/_/api/v1/bundles","summary":"Create a bundle"},{"app":"zpe-links","operationId":"deleteBundlesById","method":"delete","path":"/links/_/api/v1/bundles/{id}","summary":"Delete a bundle permanently"},{"app":"zpe-links","operationId":"getBundlesById","method":"get","path":"/links/_/api/v1/bundles/{id}","summary":"Get a bundle"},{"app":"zpe-links","operationId":"putBundlesById","method":"put","path":"/links/_/api/v1/bundles/{id}","summary":"Update a bundle"},{"app":"zpe-links","operationId":"getBundlesByIdAnalytics","method":"get","path":"/links/_/api/v1/bundles/{id}/analytics","summary":"Get bundle analytics. Defaults to all-time if no range given."},{"app":"zpe-links","operationId":"postBundlesByIdArchive","method":"post","path":"/links/_/api/v1/bundles/{id}/archive","summary":"Archive a bundle"},{"app":"zpe-links","operationId":"getBundlesByIdBreakdown","method":"get","path":"/links/_/api/v1/bundles/{id}/breakdown","summary":"Page through a bundle's countries, sources, or domains breakdown"},{"app":"zpe-links","operationId":"getBundlesByIdLinks","method":"get","path":"/links/_/api/v1/bundles/{id}/links","summary":"List links in a bundle"},{"app":"zpe-links","operationId":"postBundlesByIdLinks","method":"post","path":"/links/_/api/v1/bundles/{id}/links","summary":"Add a link to a bundle"},{"app":"zpe-links","operationId":"deleteBundlesByIdLinksByLinkId","method":"delete","path":"/links/_/api/v1/bundles/{id}/links/{linkId}","summary":"Remove a link from a bundle"},{"app":"zpe-links","operationId":"postBundlesByIdUnarchive","method":"post","path":"/links/_/api/v1/bundles/{id}/unarchive","summary":"Unarchive a bundle"},{"app":"zpe-links","operationId":"getLinks","method":"get","path":"/links/_/api/v1/links","summary":"List all short links"},{"app":"zpe-links","operationId":"postLinks","method":"post","path":"/links/_/api/v1/links","summary":"Create a short link"},{"app":"zpe-links","operationId":"deleteLinksById","method":"delete","path":"/links/_/api/v1/links/{id}","summary":"Delete a link permanently"},{"app":"zpe-links","operationId":"getLinksById","method":"get","path":"/links/_/api/v1/links/{id}","summary":"Get a link with click stats"},{"app":"zpe-links","operationId":"putLinksById","method":"put","path":"/links/_/api/v1/links/{id}","summary":"Update a link's URL, label, or expiry"},{"app":"zpe-links","operationId":"getLinksByIdAnalytics","method":"get","path":"/links/_/api/v1/links/{id}/analytics","summary":"Get analytics for a link (defaults to all-time)"},{"app":"zpe-links","operationId":"getLinksByIdBreakdown","method":"get","path":"/links/_/api/v1/links/{id}/breakdown","summary":"Page through a link's countries, sources, or domains breakdown"},{"app":"zpe-links","operationId":"getLinksByIdBundles","method":"get","path":"/links/_/api/v1/links/{id}/bundles","summary":"List bundles a link belongs to"},{"app":"zpe-links","operationId":"postLinksByIdDisable","method":"post","path":"/links/_/api/v1/links/{id}/disable","summary":"Disable a link"},{"app":"zpe-links","operationId":"postLinksByIdEnable","method":"post","path":"/links/_/api/v1/links/{id}/enable","summary":"Re-enable a disabled link"},{"app":"zpe-links","operationId":"getLinksByIdQr","method":"get","path":"/links/_/api/v1/links/{id}/qr","summary":"Get a QR code SVG for a link"},{"app":"zpe-links","operationId":"postLinksByIdSlugs","method":"post","path":"/links/_/api/v1/links/{id}/slugs","summary":"Add a custom slug to a link"},{"app":"zpe-links","operationId":"deleteLinksByIdSlugsBySlug","method":"delete","path":"/links/_/api/v1/links/{id}/slugs/{slug}","summary":"Remove a custom slug from a link"},{"app":"zpe-links","operationId":"postLinksByIdSlugsBySlugDisable","method":"post","path":"/links/_/api/v1/links/{id}/slugs/{slug}/disable","summary":"Disable a specific slug on a link"},{"app":"zpe-links","operationId":"postLinksByIdSlugsBySlugEnable","method":"post","path":"/links/_/api/v1/links/{id}/slugs/{slug}/enable","summary":"Re-enable a disabled slug on a link"},{"app":"zpe-links","operationId":"getLinksByIdTimeline","method":"get","path":"/links/_/api/v1/links/{id}/timeline","summary":"Get click timeline for a link (defaults to all-time)"},{"app":"zpe-links","operationId":"getPages","method":"get","path":"/links/_/api/v1/pages","summary":"List the pages this key can write"},{"app":"zpe-links","operationId":"getPagesByName","method":"get","path":"/links/_/api/v1/pages/{name}","summary":"Read one page, and its numbers"},{"app":"zpe-links","operationId":"putPagesByName","method":"put","path":"/links/_/api/v1/pages/{name}","summary":"Write a page's fields"},{"app":"zpe-links","operationId":"postPagesByNameBlocks","method":"post","path":"/links/_/api/v1/pages/{name}/blocks","summary":"Add a block"},{"app":"zpe-links","operationId":"deletePagesByNameBlocksById","method":"delete","path":"/links/_/api/v1/pages/{name}/blocks/{id}","summary":"Remove one block"},{"app":"zpe-links","operationId":"patchPagesByNameBlocksById","method":"patch","path":"/links/_/api/v1/pages/{name}/blocks/{id}","summary":"Change one block"},{"app":"zpe-links","operationId":"postPagesByNamePublish","method":"post","path":"/links/_/api/v1/pages/{name}/publish","summary":"Publish a page"},{"app":"zpe-links","operationId":"getPagesByNameSubmissions","method":"get","path":"/links/_/api/v1/pages/{name}/submissions","summary":"Read a page's answers"},{"app":"zpe-links","operationId":"postPagesByNameUnpublish","method":"post","path":"/links/_/api/v1/pages/{name}/unpublish","summary":"Unpublish a page"},{"app":"zpe-links","operationId":"getSlugsBySlug","method":"get","path":"/links/_/api/v1/slugs/{slug}","summary":"Look up a link by its slug"},{"app":"zpe-links","operationId":"getNew","method":"get","path":"/links/new","summary":"The intake page"},{"app":"zpe-links","operationId":"getNewApiForm","method":"get","path":"/links/new/api/form","summary":"The form was opened (the dwell anchor's row)"},{"app":"zpe-links","operationId":"postNewApiSubmit","method":"post","path":"/links/new/api/submit","summary":"One drop: the answers and the files in one body"},{"app":"zpe-links","operationId":"getNewApiByReceiptContent","method":"get","path":"/links/new/api/{receipt}/content","summary":"The assembled object (key)"},{"app":"zpe-links","operationId":"postNewApiByReceiptContent","method":"post","path":"/links/new/api/{receipt}/content","summary":"The assembler writes the object, once (key)"},{"app":"zpe-links","operationId":"postNewApiByReceiptFiles","method":"post","path":"/links/new/api/{receipt}/files","summary":"The receipt holder adds files after the drop"},{"app":"zpe-links","operationId":"getNewApiByReceiptMessage","method":"get","path":"/links/new/api/{receipt}/message","summary":"The held message, its sign-off and its attempts (key)"},{"app":"zpe-links","operationId":"postNewApiByReceiptMessage","method":"post","path":"/links/new/api/{receipt}/message","summary":"Write the held message for a row already read (key)"},{"app":"zpe-links","operationId":"postNewApiByReceiptMessageSign","method":"post","path":"/links/new/api/{receipt}/message/sign","summary":"Record a sign-off naming the body and the recipient (key)"},{"app":"zpe-links","operationId":"postNewApiByReceiptRelease","method":"post","path":"/links/new/api/{receipt}/release","summary":"The app's only send (key, and refused without a sign-off)"},{"app":"zpe-links","operationId":"getNewRByReceipt","method":"get","path":"/links/new/r/{receipt}","summary":"The page the submitter waits on"},{"app":"zpe-links","operationId":"postNewRByReceiptClaim","method":"post","path":"/links/new/r/{receipt}/claim","summary":"Keep the published page, once, for the door's principal"},{"app":"zpe-links","operationId":"getNewRByReceiptConfirm","method":"get","path":"/links/new/r/{receipt}/confirm","summary":"The correction page"},{"app":"zpe-links","operationId":"postNewRByReceiptConfirm","method":"post","path":"/links/new/r/{receipt}/confirm","summary":"The person's press, which is what confirms"},{"app":"zpe-links","operationId":"postNewRByReceiptEmail","method":"post","path":"/links/new/r/{receipt}/email","summary":"The address the page asks for when the record holds none"},{"app":"zpe-links","operationId":"getNewRByReceiptFById","method":"get","path":"/links/new/r/{receipt}/f/{id}","summary":"One stored file, streamed through the worker"},{"app":"zpe-links","operationId":"getPublishedPage","method":"get","path":"/u/{slug}","summary":"The published page"},{"app":"zpe-links","operationId":"getPublishedPageCard","method":"get","path":"/u/{slug}/card.vcf","summary":"The published page's card, as a vCard"},{"app":"zpe-mind-mapper","operationId":"getMachineResults","method":"get","path":"/mindmap/_/api/v1/results","summary":"The estate's machine address for this app: the same read as /api/results, keyed by the number."},{"app":"zpe-mind-mapper","operationId":"deleteAccount","method":"delete","path":"/mindmap/api/account","summary":"Delete what this app holds for the resolved principal, and nothing else."},{"app":"zpe-mind-mapper","operationId":"postAccountAttach","method":"post","path":"/mindmap/api/account/attach","summary":"Adopt an existing number on this device. A wrong number and an unknown one answer alike."},{"app":"zpe-mind-mapper","operationId":"postAccountStart","method":"post","path":"/mindmap/api/account/start","summary":"Mint a new anonymous number. The number is returned once and never again."},{"app":"zpe-mind-mapper","operationId":"getGroups","method":"get","path":"/mindmap/api/groups","summary":"Every group the resolved principal is a member of, with their own share level."},{"app":"zpe-mind-mapper","operationId":"postGroups","method":"post","path":"/mindmap/api/groups","summary":"Create a group. The creator is owner and a member, and an invite is returned once."},{"app":"zpe-mind-mapper","operationId":"postGroupsJoin","method":"post","path":"/mindmap/api/groups/join","summary":"Join a group by its invite code."},{"app":"zpe-mind-mapper","operationId":"postGroupsLeave","method":"post","path":"/mindmap/api/groups/leave","summary":"Leave a group the resolved principal is a member of."},{"app":"zpe-mind-mapper","operationId":"getGroupsById","method":"get","path":"/mindmap/api/groups/{id}","summary":"A group's detail: membership, the caller's share level, their own per-test switches."},{"app":"zpe-mind-mapper","operationId":"putGroupsByIdDots","method":"put","path":"/mindmap/api/groups/{id}/dots","summary":"Show the caller's own value as a dot in this group's profile, or stop showing it."},{"app":"zpe-mind-mapper","operationId":"postGroupsByIdInvites","method":"post","path":"/mindmap/api/groups/{id}/invites","summary":"The owner mints another invite. The code is returned once, here."},{"app":"zpe-mind-mapper","operationId":"getGroupsByIdPairByPrincipalId","method":"get","path":"/mindmap/api/groups/{id}/pair/{principalId}","summary":"Pairwise divergence with one other member."},{"app":"zpe-mind-mapper","operationId":"putGroupsByIdShare","method":"put","path":"/mindmap/api/groups/{id}/share","summary":"Set the caller's OWN share level in this group."},{"app":"zpe-mind-mapper","operationId":"getGroupsByIdStats","method":"get","path":"/mindmap/api/groups/{id}/stats","summary":"The group profile: a median per dimension, with n, and the dots members chose to show."},{"app":"zpe-mind-mapper","operationId":"putGroupsByIdTests","method":"put","path":"/mindmap/api/groups/{id}/tests","summary":"Set the caller's OWN per-test exclusions in this group."},{"app":"zpe-mind-mapper","operationId":"getHealth","method":"get","path":"/mindmap/api/health","summary":"Liveness, and whether the gate, the database, the assets and the human check are wired."},{"app":"zpe-mind-mapper","operationId":"getMindMapperMe","method":"get","path":"/mindmap/api/me","summary":"Resolve the presented credential to one principal, and summarise what this app holds for it."},{"app":"zpe-mind-mapper","operationId":"getProfile","method":"get","path":"/mindmap/api/profile","summary":"This app's profile row for the resolved principal."},{"app":"zpe-mind-mapper","operationId":"putProfile","method":"put","path":"/mindmap/api/profile","summary":"Upsert the resolved principal's profile row."},{"app":"zpe-mind-mapper","operationId":"getResults","method":"get","path":"/mindmap/api/results","summary":"Every stored result for the resolved principal."},{"app":"zpe-mind-mapper","operationId":"putResults","method":"put","path":"/mindmap/api/results","summary":"Upsert results, scoped to the resolved principal."},{"app":"zpe-mind-mapper","operationId":"getSession","method":"get","path":"/mindmap/api/session","summary":"Is there a live fleet sign-in on this request? The passkey door, read."},{"app":"zpe-mind-mapper","operationId":"postSignout","method":"post","path":"/mindmap/api/signout","summary":"End the door's session, carrying the request's cookies to the door and the cleared ones back."},{"app":"zpe-newsletter","operationId":"postNewsletter","method":"post","path":"/newsletter/_/api/v1/newsletter","summary":"Create this account's one newsletter list, or change its settings"},{"app":"zpe-newsletter","operationId":"postSend","method":"post","path":"/newsletter/_/api/v1/send","summary":"Queue a campaign to the list, or send one test to a single address"},{"app":"zpe-notes","operationId":"listAttachments","method":"get","path":"/notes/_/api/v1/attachments","summary":"Every image, video and file the account has"},{"app":"zpe-notes","operationId":"createDocNode","method":"post","path":"/notes/_/api/v1/docs/node","summary":"Create a doc file or folder"},{"app":"zpe-notes","operationId":"deleteDocNode","method":"delete","path":"/notes/_/api/v1/docs/node/{id}","summary":"Delete a doc node and its subtree"},{"app":"zpe-notes","operationId":"getDocNode","method":"get","path":"/notes/_/api/v1/docs/node/{id}","summary":"Read one doc node"},{"app":"zpe-notes","operationId":"moveDocNode","method":"patch","path":"/notes/_/api/v1/docs/node/{id}","summary":"Move a doc node under another folder"},{"app":"zpe-notes","operationId":"updateDocNode","method":"put","path":"/notes/_/api/v1/docs/node/{id}","summary":"Save a doc node's body"},{"app":"zpe-notes","operationId":"renameDocNode","method":"post","path":"/notes/_/api/v1/docs/node/{id}/rename","summary":"Rename a doc node"},{"app":"zpe-notes","operationId":"docsTree","method":"get","path":"/notes/_/api/v1/docs/tree","summary":"The account's docs tree"},{"app":"zpe-notes","operationId":"downloadAttachment","method":"get","path":"/notes/_/api/v1/files/{noteId}/{fileId}","summary":"Download one attachment"},{"app":"zpe-notes","operationId":"listNotes","method":"get","path":"/notes/_/api/v1/notes","summary":"List the account's notes"},{"app":"zpe-notes","operationId":"createNote","method":"post","path":"/notes/_/api/v1/notes","summary":"Create a note, with attachments"},{"app":"zpe-notes","operationId":"mergeNotes","method":"post","path":"/notes/_/api/v1/notes/merge","summary":"Merge one note into another"},{"app":"zpe-notes","operationId":"notesTimeline","method":"get","path":"/notes/_/api/v1/notes/timeline","summary":"Counts of notes by year, month and day"},{"app":"zpe-notes","operationId":"deleteNote","method":"delete","path":"/notes/_/api/v1/notes/{id}","summary":"Delete a note and its attachments"},{"app":"zpe-notes","operationId":"getNote","method":"get","path":"/notes/_/api/v1/notes/{id}","summary":"Read one note, for the editor"},{"app":"zpe-notes","operationId":"editNote","method":"put","path":"/notes/_/api/v1/notes/{id}","summary":"Edit a note, its attachments and its mood"},{"app":"zpe-notes","operationId":"revokeNoteShare","method":"delete","path":"/notes/_/api/v1/notes/{id}/share","summary":"Revoke a note's public link"},{"app":"zpe-notes","operationId":"shareNote","method":"post","path":"/notes/_/api/v1/notes/{id}/share","summary":"Share a note to a public address"},{"app":"zpe-notes","operationId":"getPublicNoteRaw","method":"get","path":"/notes/_/api/v1/public/note/raw/{public_id}","summary":"Read a shared note's text, as plain text"},{"app":"zpe-notes","operationId":"getPublicNote","method":"get","path":"/notes/_/api/v1/public/note/{public_id}","summary":"Read a shared note by its public id"},{"app":"zpe-notes","operationId":"searchNotes","method":"get","path":"/notes/_/api/v1/search","summary":"Search the account's notes"},{"app":"zpe-notes","operationId":"getSettings","method":"get","path":"/notes/_/api/v1/settings","summary":"Read the account's settings document"},{"app":"zpe-notes","operationId":"putSettings","method":"put","path":"/notes/_/api/v1/settings","summary":"Replace the account's settings document"},{"app":"zpe-notes","operationId":"noteStats","method":"get","path":"/notes/_/api/v1/stats","summary":"Note, tag and age counts for the account"},{"app":"zpe-notes","operationId":"listTags","method":"get","path":"/notes/_/api/v1/tags","summary":"The account's tags, most used first"},{"app":"zpe-pact","operationId":"postPacts","method":"post","path":"/pact/_/api/v1/pacts","summary":"Write a pact and freeze its first version, once per idempotency key."},{"app":"zpe-pact","operationId":"getPactsById","method":"get","path":"/pact/_/api/v1/pacts/{id}","summary":"One pact at its current version: the terms, the parties, and that version's obligations."},{"app":"zpe-pact","operationId":"getPactsByIdEvidence","method":"get","path":"/pact/_/api/v1/pacts/{id}/evidence","summary":"Everything a stranger needs to check one pact, and the sentences that say what it does not prove."},{"app":"zpe-pact","operationId":"postPactsByIdInvite","method":"post","path":"/pact/_/api/v1/pacts/{id}/invite","summary":"Mint one link that lets the person it is sent to read this pact and sign it, with no account."},{"app":"zpe-pact","operationId":"postPactsByIdSign","method":"post","path":"/pact/_/api/v1/pacts/{id}/sign","summary":"Sign the version you were shown, as the party you are."},{"app":"zpe-paste","operationId":"createGuestPaste","method":"post","path":"/paste/_/api/v1/guest/paste","summary":"POST /_/api/v1/guest/paste"},{"app":"zpe-paste","operationId":"getPasteMe","method":"get","path":"/paste/_/api/v1/me","summary":"GET /_/api/v1/me"},{"app":"zpe-paste","operationId":"postPaste","method":"post","path":"/paste/_/api/v1/paste","summary":"POST /_/api/v1/paste"},{"app":"zpe-paste","operationId":"deletePasteById","method":"delete","path":"/paste/_/api/v1/paste/{id}","summary":"DELETE /_/api/v1/paste/:id"},{"app":"zpe-paste","operationId":"getPasteById","method":"get","path":"/paste/_/api/v1/paste/{id}","summary":"GET /_/api/v1/paste/:id"},{"app":"zpe-paste","operationId":"postPasteByIdConsume","method":"post","path":"/paste/_/api/v1/paste/{id}/consume","summary":"POST /_/api/v1/paste/:id/consume"},{"app":"zpe-paste","operationId":"getPastes","method":"get","path":"/paste/_/api/v1/pastes","summary":"GET /_/api/v1/pastes"},{"app":"zpe-receptionist","operationId":"getInbox","method":"get","path":"/receptionist/_/api/v1/inbox","summary":"This account's conversations, newest first."},{"app":"zpe-receptionist","operationId":"getInboxById","method":"get","path":"/receptionist/_/api/v1/inbox/{id}","summary":"One conversation and its messages."},{"app":"zpe-reports","operationId":"getReportIndex","method":"get","path":"/reports/_/api/v1/reports","summary":"Every report this caller may read, as a keyset page"},{"app":"zpe-reports","operationId":"getReportBySlug","method":"get","path":"/reports/_/api/v1/reports/{slug}","summary":"One report's build facts, to the caller that owns the slug"},{"app":"zpe-scraper","operationId":"getBalance","method":"get","path":"/_/api/v1/balance","summary":"GET /_/api/v1/balance"},{"app":"zpe-scraper","operationId":"getDomains","method":"get","path":"/_/api/v1/domains","summary":"GET /_/api/v1/domains"},{"app":"zpe-scraper","operationId":"postDomains","method":"post","path":"/_/api/v1/domains","summary":"POST /_/api/v1/domains"},{"app":"zpe-scraper","operationId":"deleteDomainsById","method":"delete","path":"/_/api/v1/domains/{domain}","summary":"DELETE /_/api/v1/domains/:domain"},{"app":"zpe-scraper","operationId":"getKeys","method":"get","path":"/_/api/v1/keys","summary":"GET /_/api/v1/keys"},{"app":"zpe-scraper","operationId":"postKeys","method":"post","path":"/_/api/v1/keys","summary":"POST /_/api/v1/keys"},{"app":"zpe-scraper","operationId":"deleteKeysById","method":"delete","path":"/_/api/v1/keys/{id}","summary":"DELETE /_/api/v1/keys/:id"},{"app":"zpe-scraper","operationId":"postMap","method":"post","path":"/_/api/v1/map","summary":"POST /_/api/v1/map"},{"app":"zpe-scraper","operationId":"postScrape","method":"post","path":"/_/api/v1/scrape","summary":"POST /_/api/v1/scrape"},{"app":"zpe-scraper","operationId":"getUsage","method":"get","path":"/_/api/v1/usage","summary":"GET /_/api/v1/usage"},{"app":"zpe-stats","operationId":"listCensusRuns","method":"get","path":"/analytics/api/v1/data/runs","summary":"The census history, newest run first"},{"app":"zpe-stats","operationId":"postCensusRun","method":"post","path":"/analytics/api/v1/data/runs","summary":"Record a census run the instrument measured"},{"app":"zpe-stats","operationId":"getLatestCensusRun","method":"get","path":"/analytics/api/v1/data/runs/latest","summary":"The newest census run, and the stores it read"},{"app":"zpe-stats","operationId":"getCensusRun","method":"get","path":"/analytics/api/v1/data/runs/{run_id}","summary":"One census run by its id, and the stores it read"},{"app":"zpe-status","operationId":"getUniverse","method":"get","path":"/_/api/v1/universe","summary":"Every app the registry declares, joined to this account's monitor and its last check"},{"app":"zpe-store","operationId":"getOrders","method":"get","path":"/store/_/api/v1/orders","summary":"GET /_/api/v1/orders"},{"app":"zpe-tv","operationId":"getBlocks","method":"get","path":"/tv/_/api/v1/blocks","summary":"A blocklist for one brand, in the dialect a blocker reads."},{"app":"zpe-tv","operationId":"getBrands","method":"get","path":"/tv/_/api/v1/brands","summary":"The platform profiles, including whether the on-device opt-out was found to work."},{"app":"zpe-tv","operationId":"getCatalog","method":"get","path":"/tv/_/api/v1/catalog","summary":"Every rule with its class, confidence, sources and evidence, plus the artifact sha."},{"app":"zpe-tv","operationId":"getDataset","method":"get","path":"/tv/_/api/v1/dataset","summary":"The anonymised aggregates, floored so no single submission can publish a host."},{"app":"zpe-tv","operationId":"postDataset","method":"post","path":"/tv/_/api/v1/dataset","summary":"Submit one anonymous observation: a rotating pseudonym, the platform, and hostnames with bucketed counts."},{"app":"zpe-vc","operationId":"getHome","method":"get","path":"/_/api/v1/home","summary":"The home page model"},{"app":"zpe-vc","operationId":"getHomeContract","method":"get","path":"/_/api/v1/openapi.json","summary":"This document"}]},"paths":{"/_/api/v1/universe":{"get":{"operationId":"getUniverse","summary":"Every app the registry declares, joined to this account's monitor and its last check","tags":["universe"],"parameters":[{"name":"lifecycle","in":"query","description":"Only rows whose declared lifecycle is this one (`live`, `wip`, `plan`, …), as the registry spells it.","required":false,"schema":{"type":"string"}},{"name":"access","in":"query","description":"Only rows whose declared access is this one (a public app, a gated one, the owner's own).","required":false,"schema":{"type":"string"}},{"name":"kind","in":"query","description":"Only rows of this kind, as the registry declares it (a worker, a page, a service).","required":false,"schema":{"type":"string"}},{"name":"category","in":"query","description":"Only rows carrying this category in their list of categories.","required":false,"schema":{"type":"string"}},{"name":"license","in":"query","description":"Only rows whose declared licence is this one.","required":false,"schema":{"type":"string"}},{"name":"check","in":"query","description":"Only rows whose last check answered this word (`up`, `down`, `none`), which is the word the cell shows.","required":false,"schema":{"type":"string","enum":["up","down","none"]}}],"responses":{"200":{"description":"Every app the registry declares, as rows, joined to this account's monitor and the last check of each, narrowed by the filters the query names.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential may not read this board: the key is not granted this app, or the board was claimed by another account and it is not shared.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so the request could not be checked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://status.zpe.vc"}]},"/_/api/v1/balance":{"get":{"operationId":"getBalance","summary":"GET /_/api/v1/balance","tags":["balance"],"responses":{"200":{"description":"The credit this account holds, in milliseconds of browser time, and whether it is live or only observed.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/domains":{"get":{"operationId":"getDomains","summary":"GET /_/api/v1/domains","tags":["domains"],"responses":{"200":{"description":"Every domain this account has claimed, with the state of each claim and the TXT record it was proved with.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"post":{"operationId":"postDomains","summary":"POST /_/api/v1/domains","tags":["domains"],"requestBody":{"required":true,"description":"The hostname to claim, so its robots.txt rules can be overridden for this account.","content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"A bare hostname like `example.com`. It is normalized to lower case, and a scheme or a path is refused."}}}}}},"responses":{"200":{"description":"The claim exists: proved, already proved, or waiting for the TXT record the answer names.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body could not be read, or `domain` is not a bare hostname like example.com.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/domains/{domain}":{"delete":{"operationId":"deleteDomainsById","summary":"DELETE /_/api/v1/domains/:domain","tags":["domains"],"parameters":[{"name":"domain","in":"path","description":"The bare hostname whose claim is dropped, as `GET /v1/domains` answered it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The claim is dropped, so the site's robots.txt rules apply to this account again.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"That is not a domain this route can read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No claim for that domain belongs to this account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/keys":{"get":{"operationId":"getKeys","summary":"GET /_/api/v1/keys","tags":["keys"],"responses":{"200":{"description":"Every key this account has minted, with its label, when it was created and last used, and whether it is revoked.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"post":{"operationId":"postKeys","summary":"POST /_/api/v1/keys","tags":["keys"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"A retry handle: a second POST carrying the same value answers `200` with the first call's key and label, `secret: null`, and mints nothing.","required":false,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":false,"description":"An optional label for the key.","content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","description":"A name for the key, shown in `GET /_/api/v1/keys`. A live key with the same label is refused rather than duplicated."}}}}}},"responses":{"200":{"description":"A replay: this `Idempotency-Key` was already answered. The same key id and label the first call returned, `secret: null`, and nothing minted.","content":{"application/json":{"schema":{"type":"object"}}}},"201":{"description":"The key was minted. The secret is in this answer and nowhere else: it is stored as a keyed digest.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"A live key with that label already exists, the `Idempotency-Key` was empty or longer than 200 characters, or the key could not be minted for a reason the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Keys cannot be minted here: this deployment has no token pepper or no store, or the door could not check the credential.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/keys/{id}":{"delete":{"operationId":"deleteKeysById","summary":"DELETE /_/api/v1/keys/:id","tags":["keys"],"parameters":[{"name":"id","in":"path","description":"The key's id, as `GET /v1/keys` answered it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The key is revoked. A second call for the same id answers `404`: there is no live key left to revoke.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No live key with that id belongs to this account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/map":{"post":{"operationId":"postMap","summary":"POST /_/api/v1/map","tags":["map"],"requestBody":{"required":true,"description":"The address to read links from.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"The absolute address whose links are listed. Answered from one guarded fetch, with no browser."}}}}}},"responses":{"200":{"description":"The links found at that address, filtered by the request, with the metered time the listing cost.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`url` is missing or is not a string, or the address was refused before any fetch: a blocked host, a blocked address, a scheme other than http or https, or credentials in the URL.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Payment required: this account holds no credit, or its balance cannot cover the time a listing reserves before it starts.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The page could not be read: the address did not resolve, the redirect chain was too long, or the body exceeded the size cap.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/scrape":{"post":{"operationId":"postScrape","summary":"POST /_/api/v1/scrape","tags":["scrape"],"requestBody":{"required":true,"description":"The page to read, and which readings of it to return.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"The absolute address to scrape. The SSRF guard reads it before any browser opens, so a refused address costs nothing."},"formats":{"type":"array","minItems":1,"items":{"type":"string","enum":["markdown","html","rawHtml","links","screenshot","screenshot@fullPage"]},"description":"Which readings to return. Defaults to markdown, html, rawHtml and links; duplicates are dropped."},"onlyMainContent":{"type":"boolean","description":"Drop navigation, footers and other page chrome from the text. Defaults to true."},"waitFor":{"type":"integer","minimum":0,"maximum":30000,"description":"Milliseconds to wait after the page loads before reading it. Defaults to 0."},"timeout":{"type":"integer","minimum":1000,"description":"The job's ceiling in milliseconds. Defaults to 60000 or the deployment's MAX_JOB_MS, whichever is lower."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra request headers for the target, at most 20. Host and hop-by-hop headers are refused."}}}}}},"responses":{"200":{"description":"The page, read into the formats asked for, with the browser time it cost and how that time was measured.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The request could not be read, or the address was refused before any fetch: a blocked host, a blocked address, a scheme other than http or https, or credentials in the URL.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"402":{"description":"Payment required: this account holds no credit, or its balance cannot cover the browser time a scrape reserves before it starts.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The page could not be read: the address did not resolve, the redirect chain was too long, the body exceeded the size cap, or the driver reported an error.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, or this account already has its browser slots open. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The browser the scrape opened did not answer, so nothing could be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"504":{"description":"The address did not answer in the time the request allowed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/_/api/v1/usage":{"get":{"operationId":"getUsage","summary":"GET /_/api/v1/usage","tags":["usage"],"responses":{"200":{"description":"This account's twenty most recent scrapes, newest first, with the address, time and charge of each.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing, or the door did not recognise it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This key may not do that: it is granted a different app, its scope does not cover this verb, or the request was refused by the site's own robots.txt.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling. Retry after the period the answer names.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer: the door could not check the credential, the ledger binding a metered call needs is absent, or the browser the scrape needs is not bound here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"servers":[{"url":"https://scrape.zpe.vc"}]},"/analytics/api/v1/data/runs":{"get":{"operationId":"listCensusRuns","summary":"The census history, newest run first","description":"One row per run, newest first, with each run's totals and no stores: the history is what answers whether the census is current, and `/runs/latest` is what answers what it found. `limit` and `cursor` page the runs, and `next` is the cursor for the following page.","tags":["census"],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many runs to return. Absent, the app answers its own default.","schema":{"type":"integer","minimum":1,"maximum":200,"default":20}},{"name":"cursor","in":"query","required":false,"description":"The `next` value from a previous answer: a position in the run history, newest first.","schema":{"type":"string"}}],"responses":{"200":{"description":"The runs this page carries, newest first, and the cursor for the next page. `next` is `null` on the last page, and the app answers an empty `runs` list when no run has been recorded.","content":{"application/json":{"schema":{"type":"object","required":["runs","next"],"properties":{"runs":{"type":"array","items":{"$ref":"#/components/schemas/CensusRun"}},"next":{"type":["string","null"],"description":"The cursor for the next page of runs, or `null` when this answer carried the last of them."}}}}}},"400":{"description":"`limit` is not a number in range, or `cursor` is not a cursor this app wrote: nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential, or one the app could not verify: the history is not public, so nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A real credential that may not read this surface, or the door named no account for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering, so the history is not an answer about the estate. The problem's `detail` says what it was doing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`zpe-stats-db` could not be read, so the history could not be listed. The problem's `detail` names the store that refused it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"serviceToken":[]},{"doorSession":[]}]},"post":{"operationId":"postCensusRun","summary":"Record a census run the instrument measured","description":"The instrument posts one run per measurement: the run's own `run_id`, when it ran, which instrument version and commit read the estate, and one row per store. A run is KEYED by `run_id`, which is the hash of what was measured, so the same run posted twice answers `200` with the row the first call wrote and makes no second row. A store the instrument could not read is posted as a row whose `state` is `error`, with the reason in `error`; that is a fact about the store, not a refusal of this call.","tags":["census"],"requestBody":{"required":true,"description":"The run, as the instrument measured it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CensusRunReport"}}}},"responses":{"200":{"description":"The run was already recorded: `run_id` matched a row, so this answers that row and writes no second one. The body is the run the first call stored, not the one in this request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CensusRun"}}}},"201":{"description":"The run was new and is now recorded, with one row per store it carried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CensusRun"}}}},"400":{"description":"The body is not a census run: `run_id`, `ran_at`, `instrument_version`, `instrument_commit`, or a store row is missing or malformed. The problem's `detail` names the store whose row it was, so the instrument that posted it knows which read to look at.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No service token, or one that is not live: nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A real credential that may not write the census: a person's session and an account key may read it, and only the service token records a run.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering, and whether the run was recorded is NOT answered by this response. The problem's `detail` says what it was doing, so a caller can post the same `run_id` again and be told.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`zpe-stats-db` could not be reached, so nothing was written and this run is not recorded. The problem's `detail` names the store that refused the write.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"serviceToken":[]}]}},"/analytics/api/v1/data/runs/latest":{"get":{"operationId":"getLatestCensusRun","summary":"The newest census run, and the stores it read","description":"The run with the greatest `ran_at`, its totals, and one row per store. `limit` and `cursor` page the STORES inside that run, not the runs, because a census of the estate is larger than one answer: the first answer carries the run and the first `limit` stores, and `next` is the cursor for the rest of them.","tags":["census"],"parameters":[{"name":"limit","in":"query","required":false,"description":"How many stores to return. Absent, the app answers its own default.","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `next` value from a previous answer for the same run. It is a position in that run's stores and means nothing in another run.","schema":{"type":"string"}}],"responses":{"200":{"description":"The newest run, with its totals and the stores this page carries. `next` is the cursor for the following page of stores, and is `null` on the last one.","content":{"application/json":{"schema":{"type":"object","required":["run","stores","findings","next"],"properties":{"run":{"$ref":"#/components/schemas/CensusRun"},"stores":{"type":"array","items":{"$ref":"#/components/schemas/CensusStore"}},"findings":{"type":["array","null"],"description":"Every store the walk could not settle, named rather than counted: a name no database in the account answers to, two stores resolving to one database, a count that could not be read or did not match the read. They belong to the RUN, not to the page, so this array is the whole run's findings whatever page of stores answered. `null` means the instrument did not report findings at all — every run recorded before 1.1.3 — which is not the same answer as the empty array a run that looked and found none gives.","items":{"$ref":"#/components/schemas/CensusFinding"}},"next":{"type":["string","null"],"description":"The cursor for the next page of stores, or `null` when this answer carried the last of them."}}}}}},"400":{"description":"`limit` is not a number in range, or `cursor` is not a cursor this app wrote: nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential, or one the app could not verify: the census is not public, so nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A real credential that may not read this surface, or the door named no account for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No run is recorded yet, so there is no newest one: the census has not been posted to this deployment.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering, so nothing here describes the estate. The problem's `detail` says what it was doing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`zpe-stats-db` could not be read, so the newest run could not be named. The problem's `detail` names the store that refused it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"serviceToken":[]},{"doorSession":[]}]}},"/analytics/api/v1/data/runs/{run_id}":{"get":{"operationId":"getCensusRun","summary":"One census run by its id, and the stores it read","description":"The run with that `run_id`, its totals, and one row per store — the SAME answer shape `/runs/latest` gives while that run is the newest. `/latest` moves as soon as a newer run is posted, so this is the only address a run keeps once it stops being the newest: a reader comparing tonight's declarations with the previous run's reads the previous run here instead of keeping a copy of it. `limit` and `cursor` page the STORES inside this run, exactly as they do on `/latest`, and a cursor carries the run it belongs to: a cursor from another run is a `400`, because it would otherwise be a position in a list this answer does not contain. An id this deployment does not hold is a `404` problem document naming the id it asked for.","tags":["census"],"parameters":[{"name":"run_id","in":"path","required":true,"description":"The run's own id: the hash the instrument computed over what it measured, which is also the id `/runs` lists and the one the POST answered. It is the primary key, so the id a caller holds is the id this route answers.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many stores to return. Absent, the app answers its own default.","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}},{"name":"cursor","in":"query","required":false,"description":"The `next` value from a previous answer for the SAME run. It is a position in that run's stores and means nothing in another run.","schema":{"type":"string"}}],"responses":{"200":{"description":"The run, with its totals and the stores this page carries. The shape is `/runs/latest`'s, for the run that was asked for rather than the newest one. `next` is the cursor for the following page of stores, and is `null` on the last one.","content":{"application/json":{"schema":{"type":"object","required":["run","stores","findings","next"],"properties":{"run":{"$ref":"#/components/schemas/CensusRun"},"stores":{"type":"array","items":{"$ref":"#/components/schemas/CensusStore"}},"findings":{"type":["array","null"],"description":"Every store the walk could not settle, named rather than counted. They belong to the RUN, not to the page, so this array is the whole run's findings whatever page of stores answered. `null` means the instrument did not report findings at all — every run recorded before 1.1.3 — which is not the same answer as the empty array a run that looked and found none gives.","items":{"$ref":"#/components/schemas/CensusFinding"}},"next":{"type":["string","null"],"description":"The cursor for the next page of stores, or `null` when this answer carried the last of them."}}}}}},"400":{"description":"`limit` is not a number in range, `cursor` is not a cursor this app wrote, or the cursor belongs to a different run: nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential, or one the app could not verify: the census is not public, so nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A real credential that may not read this surface, or the door named no account for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No run with that `run_id` is recorded in `zpe-stats-db`: the id is not one this deployment holds, or the run has been pruned by the store's retention. The problem's `detail` names the id it asked for.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering, so nothing here describes the estate. The problem's `detail` says what it was doing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"`zpe-stats-db` could not be read, so the run's stores could not be named. The problem's `detail` names the store that refused it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"serviceToken":[]},{"doorSession":[]}]}},"/booking/_/api/v1/bookings":{"get":{"operationId":"listBookings","summary":"List the account's bookings","tags":["bookings"],"responses":{"200":{"description":"One page, soonest first. The next cursor is in `x-next-cursor`.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}}}},"headers":{"x-next-cursor":{"description":"Pass back as `cursor` for the next page. Absent on the last page, so its absence IS the end.","schema":{"type":"string"}}}},"400":{"description":"The cursor is not one this listing issued, or `status` is not a known status.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"name":"limit","in":"query","required":false,"description":"Rows to return, 1–100. Defaults to 25.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","required":false,"description":"The `x-next-cursor` value from the previous page. The walk is a keyset, so a row written mid-walk cannot make it repeat or skip a row.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Narrow the walk to one status. Page on the same key either way.","schema":{"type":"string","enum":["confirmed","cancelled"]}}]}},"/booking/_/api/v1/bookings/{id}":{"get":{"operationId":"getBooking","summary":"Read one booking","tags":["bookings"],"responses":{"200":{"description":"The booking.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"This account has no booking with that id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The row's id.","schema":{"type":"string"}}]}},"/booking/_/api/v1/bookings/{id}/cancel":{"post":{"operationId":"cancelBooking","summary":"Cancel a booking","tags":["bookings"],"responses":{"200":{"description":"Cancelled. The row's status moved and its slot locks are gone.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"This account has no booking with that id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"That booking is already cancelled.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The row's id.","schema":{"type":"string"}}]}},"/booking/_/api/v1/event-types":{"get":{"operationId":"listEventTypes","summary":"List the account's meeting types","tags":["event-types"],"responses":{"200":{"description":"One page, newest first. The next cursor is in `x-next-cursor`.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EventType"}}}},"headers":{"x-next-cursor":{"description":"Pass back as `cursor` for the next page. Absent on the last page, so its absence IS the end.","schema":{"type":"string"}}}},"400":{"description":"The cursor is not one this listing issued.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"name":"limit","in":"query","required":false,"description":"Rows to return, 1–100. Defaults to 25.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},{"name":"cursor","in":"query","required":false,"description":"The `x-next-cursor` value from the previous page. The walk is a keyset, so a row written mid-walk cannot make it repeat or skip a row.","schema":{"type":"string"}}]},"post":{"operationId":"createEventType","summary":"Create a meeting type","tags":["event-types"],"responses":{"201":{"description":"Created. The row the store wrote, read back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventType"}}}},"400":{"description":"The body is not a JSON object, or `locationType` is not one of the four.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"That `Idempotency-Key` was already used for a different operation.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The row was read and could not be created: a missing title or link, or a duration off the five-minute grid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Names ONE create intent. A retry with the same key replays the first answer instead of making a second row; the same key on another operation is a 409.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The meeting. Field names are camelCase; the console's own form names are accepted as aliases.","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"What the meeting is called on the guest's page."},"slug":{"type":"string","description":"The second half of the booking link. Unique per host."},"description":{"type":"string"},"durationMinutes":{"type":"integer","default":30,"description":"5–720, in steps of 5: the slot grid is five minutes."},"bufferBeforeMinutes":{"type":"integer","default":0},"bufferAfterMinutes":{"type":"integer","default":0},"minNoticeMinutes":{"type":"integer","default":0},"maxHorizonDays":{"type":"integer","default":60},"locationType":{"type":"string","enum":["google_meet","custom_link","phone","in_person"],"default":"custom_link"},"locationValue":{"type":"string"}},"required":["title","slug"],"additionalProperties":true}}}}}},"/booking/_/api/v1/event-types/{id}":{"get":{"operationId":"getEventType","summary":"Read one meeting type","tags":["event-types"],"responses":{"200":{"description":"The meeting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventType"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"This account has no meeting type with that id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The row's id.","schema":{"type":"string"}}]}},"/booking/_/api/v1/me":{"get":{"operationId":"getMe","summary":"Read the caller's profile and availability","tags":["account"],"responses":{"200":{"description":"The profile and the hours a guest would be offered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"No credential, or one the door will not honour: no session cookie and no key, or a key that is malformed, unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential verified and may not do this: a read-scoped key on a write, or a key not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"This account has no profile row yet: the console creates the first one.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its window's ceiling. `Retry-After` says when the next call may be made.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so no account could be named. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/comments/_/api/v1/bootstrap":{"get":{"operationId":"getBootstrap","summary":"One post's comment tree, with the settings a composer needs","tags":["bootstrap"],"parameters":[{"name":"site","in":"query","description":"The site's slug — its address under this app's mount.","required":true,"schema":{"type":"string"}},{"name":"slug","in":"query","description":"The post's slug, as the site's own URLs spell it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"One post's comment tree, with the settings and limits a composer needs.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`slug` is missing or malformed, so the post cannot be addressed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No site answers to that `site` slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/comments/_/api/v1/comments":{"get":{"operationId":"getComments","summary":"One post's comment tree","tags":["comments"],"parameters":[{"name":"site","in":"query","description":"The site's slug — its address under this app's mount.","required":true,"schema":{"type":"string"}},{"name":"slug","in":"query","description":"The post's slug, as the site's own URLs spell it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"One post's comment tree.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`slug` is missing or malformed, so the post cannot be addressed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No site answers to that `site` slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"postComments","summary":"Post an anonymous comment behind a Turnstile answer","tags":["comments"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"A value that names this attempt. Send the same one when retrying and the answer is the row the first call wrote (`200`) instead of a second row; present and empty is refused.","required":false,"schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Nothing new was written: this `Idempotency-Key` already made a comment here, and this is the row the first call wrote.","content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"type":"object","required":["id","parent_id","status","created_at","body_html","author","depth"],"properties":{"id":{"type":"string"},"parent_id":{"type":["string","null"]},"status":{"type":"string","enum":["approved","pending","spam","deleted"]},"created_at":{"type":"integer"},"body_html":{"type":"string"},"author":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"depth":{"type":"integer"}}}}}}}},"201":{"description":"The comment was written. `status` is `approved`, or `pending` when the site holds first comments or the blocklist matched; `depth` is 1 for a top-level comment.","content":{"application/json":{"schema":{"type":"object","required":["comment"],"properties":{"comment":{"type":"object","required":["id","parent_id","status","created_at","body_html","author","depth"],"properties":{"id":{"type":"string"},"parent_id":{"type":["string","null"]},"status":{"type":"string","enum":["approved","pending","spam","deleted"]},"created_at":{"type":"integer"},"body_html":{"type":"string"},"author":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"depth":{"type":"integer"}}}}}}}},"400":{"description":"The body is not one JSON object, `site` or `slug` is missing or malformed, the name or the text is refused, the honeypot field is filled, the Turnstile answer was rejected, or `Idempotency-Key` was sent empty.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, comments are disabled on this site, or this address is banned.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No site answers to that `site` slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The request is not `application/json`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This address posted too often; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"This deployment holds no Turnstile secret, so no write can be checked.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/comments/_/api/v1/comments/{id}":{"patch":{"operationId":"patchCommentsById","summary":"Edit your own comment inside the edit window","tags":["comments"],"parameters":[{"name":"id","in":"path","description":"The comment's id, as the tree answered it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The comment's text was replaced; `body_html` and `edited_at` carry the new row.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body is not one JSON object, or the text is refused — empty, over the length limit, or otherwise unacceptable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or this site's edit window is closed or has passed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No comment with that id, or it is not this visitor's to edit. A deleted or spam-marked row answers the same, so a caller learns nothing about a comment it does not own.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The request is not `application/json`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"deleteCommentsById","summary":"Delete your own comment, or any comment on a site you own","tags":["comments"],"parameters":[{"name":"id","in":"path","description":"The comment's id, as the tree answered it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The comment was deleted: by its author, or by the account that owns its site.","content":{"application/json":{"schema":{"type":"object"}}}},"403":{"description":"The request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No comment with that id, or it is neither this visitor's own nor on a site the caller owns.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/comments/_/api/v1/comments/{id}/report":{"post":{"operationId":"postCommentsByIdReport","summary":"Report a comment to the site's owner, once per reader","tags":["comments"],"parameters":[{"name":"id","in":"path","description":"The id of the comment being reported.","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A value that names this attempt. Send the same one when retrying and the answer is the row the first call wrote (`200`) instead of a second row; present and empty is refused.","required":false,"schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Nothing new was written: this reader already reported this comment, or this `Idempotency-Key` already filed one. `duplicate` is `true`.","content":{"application/json":{"schema":{"type":"object","required":["reported","duplicate"],"properties":{"reported":{"type":"boolean"},"duplicate":{"type":"boolean"}}}}}},"201":{"description":"The report was filed and is open for the site's owner.","content":{"application/json":{"schema":{"type":"object","required":["reported","duplicate"],"properties":{"reported":{"type":"boolean"},"duplicate":{"type":"boolean"}}}}}},"400":{"description":"The body is not one JSON object, `reason` is missing, or `Idempotency-Key` was sent empty.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No comment answers to that id in any account this app serves.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The request is not `application/json`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/comments/_/api/v1/config":{"get":{"operationId":"getConfig","summary":"One site's comment settings, its Turnstile sitekey and the length limit","tags":["config"],"parameters":[{"name":"site","in":"query","description":"The site's slug — its address under this app's mount.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"This site's comment settings, its Turnstile site key, and its length and edit-window limits.","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No site answers to that `site` slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/comments/_/api/v1/operator/moderate":{"post":{"operationId":"postOperatorModerate","summary":"Moderate, ban or unban with a key instead of the console","tags":["operator"],"responses":{"200":{"description":"The moderation action was applied, and `status` is the comment's new state.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body is not one JSON object, names no action or an unknown one, or is missing the `comment_id` or `user_id` its action needs.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key and no session: this caller is not signed in at the door.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or the key is revoked, not granted this app, or scoped to an account that may not moderate here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No comment or commenter answers to that id in this account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The request is not `application/json`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"The key has spent its request ceiling; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door or this deployment's key store did not answer, so the key could not be checked — not a verdict on the key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/comments/_/api/v1/operator/queue":{"get":{"operationId":"getOperatorQueue","summary":"The moderation queue, with its open report counts","tags":["operator"],"responses":{"200":{"description":"The moderation queue, with each comment's open report count.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No key and no session: this caller is not signed in at the door.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is revoked, is not granted this app, or belongs to an account the door will not scope here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"The key has spent its request ceiling; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door or this deployment's key store did not answer, so the key could not be checked — not a verdict on the key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/crew/_/api/v1/channel-events":{"post":{"operationId":"postChannelEvents","summary":"Keep a page of the channel's events in this ledger, keyed by the channel's own id. A re-post of a page writes nothing and says so.","tags":["ingest"],"parameters":[{"name":"account","in":"query","description":"The ledger the events are kept in, when it is not the key's own. The door is asked for `can_write` on `crew-channel:<account>`; nothing is granted by this parameter.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The page was poured. `written` rows are new, `replayed` events were already here (a retry writes nothing), and `cursor` is the highest channel id in the page — the `since` to send next, or null for an empty page.","content":{"application/json":{"schema":{"type":"object","required":["written","replayed","cursor"],"properties":{"written":{"type":"integer"},"replayed":{"type":"integer"},"cursor":{"type":["integer","null"]}}}}}},"400":{"description":"The body is not one JSON object with an `events` array, or an event in it was refused: `event`, `id`, `kind`, `subject_ref`, `at` or `payload`. Nothing was written, in this ledger or any other.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key. The mirror is written by the machine on his desk, and a cookie names a person.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key's account does not hold this ledger's owner seat, and the ledger's owner has not granted it `can_write` on this ledger's channel. Nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body carries more than 200 events. That is the channel's own page size, so a larger body is a consumer that joined pages rather than one that read one.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The ledger named by `?account=` could not be checked at the door, or the store refused the write. The sentence names which event could not be written; a retry costs nothing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/crew/_/api/v1/clients":{"post":{"operationId":"postClients","summary":"Store a client the agency pull found, or update the one already here.","tags":["ingest"],"responses":{"200":{"description":"A client the ledger already held: its name, contract reference, milestone and `synced_at` are rewritten, and `created_at` stays the minute the first sighting wrote. `conversationId` is written only when the body names it (null detaches); absent leaves the attached conversation alone.","content":{"application/json":{"schema":{"type":"object","required":["client"],"properties":{"client":{"type":"object"}}}}}},"201":{"description":"A client this ledger did not hold. The agency's own id is the key, so the row a second pull sends lands on this one rather than beside it.","content":{"application/json":{"schema":{"type":"object","required":["client"],"properties":{"client":{"type":"object"}}}}}},"400":{"description":"The body names no id, no name, or no contract reference — the three `clients.store()` refuses rather than fills in.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key. The ingest is a machine call on the ledger's account; a browser session names a person, and a person is not a client's pull.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key's account does not hold this ledger's owner seat. The agency clause licenses this material to the account that authenticated, and a key that could write into another account is the redistribution D11 refuses.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/crew/_/api/v1/clients/{id}/messages":{"post":{"operationId":"postClientsByIdMessages","summary":"Remember what a client said, with the day its words stop being served.","tags":["ingest"],"parameters":[{"name":"id","in":"path","description":"the client the words are about, as `client.id` spells it","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"The post's own retry handle. The unique index on (account_id, idempotency_key) is what makes a second post free: a repeat answers 200 with the first call's row and writes nothing. Absent, every post appends, which is the behaviour a caller with no key had before the column existed.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"That `Idempotency-Key` was already here: nothing was written, and the row is the one the first call answered with — a repeat key whose words moved is still not a second line.","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"object"}}}}}},"201":{"description":"The body as it was written: the minute it was said, and the minute its words stop being served.","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"object"}}}}}},"400":{"description":"No client in this ledger holds that id, the body has no words, the day it names is outside `(the minute it was said, thirty days after it]`, or a body with no day of its own names no open item (`item`, `unknown-item`, `closed`) — each one the module's own refusal word.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key. A body is written for the ledger's account, and a cookie names a person.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key's account does not hold this ledger's owner seat, so there is no client list here for the words to hang on.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The item the words name was confirmed by the other side, so there is no open delivery for them to live on.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/crew/_/api/v1/events":{"post":{"operationId":"postEvents","summary":"Push one line of the local ledger's history, under the key that makes a second push free.","tags":["ingest"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"The push's own key, and the same fact as the body's `idempotencyKey`. The header wins when both are present. The unique index on (account_id, idempotency_key) is what makes the second push free.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"That key was already here: nothing was written, and the row keeps the fields the first push sent — a repeat key whose fields moved is still not a second line of history.","content":{"application/json":{"schema":{"type":"object","required":["event"],"properties":{"event":{"type":"object"}}}}}},"201":{"description":"A line this ledger did not hold. The caller's `idempotencyKey` is the local row's own uid, so a second push of the same row lands here rather than beside it.","content":{"application/json":{"schema":{"type":"object","required":["event"],"properties":{"event":{"type":"object"}}}}}},"400":{"description":"A body this ingest refuses: no `idempotencyKey`, no kind, no subject, no artifact, an `at` that is not whole epoch milliseconds, a minute before 1970, or one more than five minutes ahead of this app's clock.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key. The push is a machine call on the ledger's account, and a cookie names a person rather than a collector.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key's account does not hold this ledger's owner seat, so there is no history here for the push to write into.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/crew/_/api/v1/pacts/{id}/accept":{"post":{"operationId":"postPactsByIdAccept","summary":"Send the person who accepted to the contract layer's page for this pact.","tags":["pacts"],"parameters":[{"name":"id","in":"path","description":"the pact to accept, as `pact.id` spells it","required":true,"schema":{"type":"string"}}],"responses":{"303":{"description":"The contract layer's page for the same pact id. The signature is the service's to record — its version, its evidence and its tier — so this route writes nothing and answers the address a person signs at."},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/crew/_/api/v1/rooms":{"post":{"operationId":"postRooms","summary":"Pour one pull's rooms: the client's half into chat, his own into the ledger. A re-post writes nothing.","tags":["ingest"],"responses":{"200":{"description":"Every room the body named, with the conversation each one is about and the count written and skipped per half.","content":{"application/json":{"schema":{"type":"object","required":["rooms"],"properties":{"rooms":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"The body is not one object with a `rooms` array, or a room or a line failed its own check: the refusal names the part (`room`, `ref`, `side`, `body`, `at`, `duplicate`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key. The pull is a machine call, and a browser session names a person.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key's account does not hold this ledger's owner seat, so the rooms of another ledger are not its to write.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Chat refused one room's client half (no key, a refused grant, too long, or no answer). The owner's half still landed, and the body carries every room's counts so a retry costs nothing.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/crew/_/api/v1/upwork":{"post":{"operationId":"postUpwork","summary":"Replace one ledger's Upwork snapshot with the document the mini read. `?account=` names the ledger; absent, the key's own. A ledger the key does not own needs the door's `can_write` for that ledger's channel, granted by its owner.","tags":["ingest"],"parameters":[{"name":"account","in":"query","description":"The ledger the snapshot is written into, when it is not the key's own account. The door is asked for `can_write` on `crew-channel:<account>`; nothing is granted by this parameter.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The ledger held a snapshot and this replaced it. `updated_at` is this post's minute — a re-push moves the page's stamp and nothing else — and nothing appends.","content":{"application/json":{"schema":{"type":"object","required":["channel"],"properties":{"channel":{"type":"object"}}}}}},"201":{"description":"The ledger held no snapshot and now holds this one: the body as the mini sent it, and the minute crew received it.","content":{"application/json":{"schema":{"type":"object","required":["channel"],"properties":{"channel":{"type":"object"}}}}}},"400":{"description":"The body is not one JSON object, or it carries no `proposals` array: the envelope is the one thing this route judges, and the content is the producer's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key. The mirror is written by the machine on his desk, and a cookie names a person.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key's account does not hold this ledger's owner seat, and the ledger's owner has not granted it `can_write` on this ledger's channel. The door's `why` is in the sentence; nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is over 256 KB. A proposal list past that is not the channel.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The ledger named by `?account=` could not be checked at the door (no `DOOR_INTERNAL_TOKEN`, or the door did not answer). Nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/feedback/_/api/v1/threads":{"get":{"operationId":"getThreads","summary":"Every thread in this account, newest first, with its board, its moderation state and its reply count","tags":["threads"],"responses":{"200":{"description":"Every thread in this account, newest first, with its board, its moderation state and its reply count.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The key is missing, malformed, unknown, revoked, expired, or names another account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"That key is not granted this app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its request ceiling for the current minute; retry after the break.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The key could not be checked, or this deployment could not read its store, so nothing was answered.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/copy":{"post":{"operationId":"postCopy","summary":"Copy a file or a whole folder to another path","tags":["copy"],"parameters":[{"name":"from","in":"query","description":"The file or folder to copy. Send it here or as `from` in a JSON body.","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","description":"Where the copy goes. Send it here or as `to` in a JSON body.","required":false,"schema":{"type":"string"}},{"name":"overwrite","in":"query","description":"Replace the destination when it already exists. Default false.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"The file was copied. `from` still exists: a copy does not move it.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`from` or `to` is missing, is not a usable key, names the internal prefix, is the same as the other, or sits inside it; or the JSON body could not be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`from` names nothing in this account's prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`to` is already there and `overwrite` was not asked for.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/counts":{"post":{"operationId":"postCounts","summary":"Count the direct children of one or more folders","tags":["counts"],"responses":{"200":{"description":"One count per folder, keyed by path. A folder that could not be counted is absent rather than zero, so a missing key means `unknown`, not `empty`.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The JSON body could not be read, so there were no folders to count.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/delete":{"delete":{"operationId":"deleteDelete","summary":"Delete a file or folder, or move it to the trash with `?soft=1`","tags":["delete"],"parameters":[{"name":"path","in":"query","description":"The file or folder to delete, relative to the account's prefix.","required":true,"schema":{"type":"string"}},{"name":"soft","in":"query","description":"Move it to the trash instead of deleting it.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"It is gone, or — with `soft` — it moved to the trash and this is the `trashKey` a restore takes.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`path` is missing or is not a usable key, or names the internal prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`path` names nothing in this account's prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/download":{"get":{"operationId":"getDownload","summary":"Download one file's bytes","tags":["download"],"parameters":[{"name":"path","in":"query","description":"The object key to read, relative to the account's prefix.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The file's bytes, with its type, length and `ETag`.","content":{"application/json":{"schema":{"type":"object"}}}},"206":{"description":"Part of the file's bytes, when the request carried a `Range` header.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`path` is missing or is not a usable key, or names a folder: a folder has no bytes to send.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`path` names nothing in this account's prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/list":{"get":{"operationId":"getList","summary":"List one folder, sorted, folders first","tags":["list"],"parameters":[{"name":"path","in":"query","description":"The folder to list, relative to the account's prefix. The root when absent.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"How many entries to answer. The whole folder when absent.","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000}},{"name":"cursor","in":"query","description":"The `nextCursor` a previous page answered, to read the next one.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The folder's entries, folders first, and a `nextCursor` when there is another page.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`path` is not a usable key, names a file rather than a folder, or `limit` is outside 1-1000.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`path` names no folder in this account's prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/mkdir":{"post":{"operationId":"postMkdir","summary":"Create a folder, with its parent markers","tags":["mkdir"],"parameters":[{"name":"path","in":"query","description":"The folder to create, relative to the account's prefix.","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A value that names this attempt. Send the same one when retrying and the answer is what the first call wrote (`200`) instead of a second object; present and empty is refused.","required":false,"schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Nothing new was written: this `Idempotency-Key` already created that folder, or the folder was already there. `created` is `false` and `existed` says what was found.","content":{"application/json":{"schema":{"type":"object","required":["key","created"],"properties":{"key":{"type":"string"},"created":{"type":"boolean"},"existed":{"type":"string","enum":["directory","prefix"]}}}}}},"201":{"description":"The folder was created, with its parent markers.","content":{"application/json":{"schema":{"type":"object","required":["key","created"],"properties":{"key":{"type":"string"},"created":{"type":"boolean"}}}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"A file of that name is already there, so a folder cannot take the address.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/rename":{"post":{"operationId":"postRename","summary":"Rename or move a file or folder","tags":["rename"],"parameters":[{"name":"from","in":"query","description":"The file or folder to move. Send it here or as `from` in a JSON body.","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","description":"Its new path. Send it here or as `to` in a JSON body.","required":false,"schema":{"type":"string"}},{"name":"overwrite","in":"query","description":"Replace the destination when it already exists. Default false.","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"description":"It moved, and `from` no longer exists.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`from` or `to` is missing or is not a usable key, is the same as the other or sits inside it, names the internal prefix, or `overwrite` was asked for on a folder move; or the JSON body could not be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`from` names nothing in this account's prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"`to` is already there, and either `overwrite` was not asked for or the destination is a folder.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/search":{"get":{"operationId":"getSearch","summary":"Search the account's files by name","tags":["search"],"parameters":[{"name":"q","in":"query","description":"The text to find in a file's name. An empty one answers nothing rather than everything.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"How many hits to answer. Default 100.","required":false,"schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"prefix","in":"query","description":"Only search under this folder, relative to the account's prefix.","required":false,"schema":{"type":"string"}},{"name":"match","in":"query","description":"`name` matches a file's whole name instead of a substring of it, for a caller that knows the name it wants.","required":false,"schema":{"type":"string","enum":["name"]}},{"name":"cursor","in":"query","description":"The `nextCursor` a previous page answered, to read the next one.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The names that matched, and a `nextCursor` when the scan stopped with more to read. An empty `q` answers an empty page rather than every file.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/stat":{"get":{"operationId":"getStat","summary":"Read one file's metadata without its bytes","tags":["stat"],"parameters":[{"name":"path","in":"query","description":"The object key to read the metadata of, relative to the account's prefix.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"One file's or folder's metadata, without its bytes. A folder answers `kind: directory`.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`path` is missing or is not a usable key, or names the internal prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"`path` names nothing in this account's prefix.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/trash":{"delete":{"operationId":"deleteTrash","summary":"List the trash, restore from it, or empty it","tags":["trash"],"responses":{"204":{"description":"The named `trashKeys` — or everything, with `all` — were removed from the trash and their bytes with them. Nothing to send back."},"400":{"description":"The JSON body could not be read, so there were no entries to remove.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"get":{"operationId":"getTrash","summary":"List the trash, restore from it, or empty it","tags":["trash"],"responses":{"200":{"description":"Everything in the trash, including entries whose retention has just run out and been purged.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"post":{"operationId":"postTrash","summary":"List the trash, restore from it, or empty it","tags":["trash"],"parameters":[{"name":"action","in":"query","description":"`restore` restores the keys named in the body. Absent, the body's keys are soft-deleted instead.","required":false,"schema":{"type":"string","enum":["restore"]}}],"responses":{"200":{"description":"`action=restore` answers one result per `trashKey` — restored, or why it could not be. Without it, the body's `keys` moved to the trash and this is each one's id.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The JSON body could not be read, or — without `action=restore` — carried no `keys` to move.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/files/_/api/v1/upload":{"delete":{"operationId":"deleteUpload","summary":"Upload a file, or write and remove one raw PUT at a time","tags":["upload"],"parameters":[{"name":"path","in":"query","description":"The file key the abandoned upload was for.","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"query","description":"The multipart upload to abandon.","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"The multipart upload was abandoned and its parts are gone. An `uploadId` that was never live is treated as already abandoned, so this answer is idempotent."},"400":{"description":"`path` or `uploadId` is missing or is not a usable key.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"post":{"operationId":"postUpload","summary":"Upload a file, or write and remove one raw PUT at a time","tags":["upload"],"parameters":[{"name":"path","in":"query","description":"The folder to write into — or, with `uploads`, the file key the multipart upload is for.","required":false,"schema":{"type":"string"}},{"name":"uploads","in":"query","description":"Start a multipart upload and answer its `uploadId`. Send it with no value.","required":false,"schema":{"type":"boolean"}},{"name":"uploadId","in":"query","description":"Complete the multipart upload this id names, with the parts in a JSON body.","required":false,"schema":{"type":"string"}},{"name":"overwrite","in":"query","description":"Replace a file of the same name. Default false.","required":false,"schema":{"type":"boolean"}},{"name":"Idempotency-Key","in":"header","description":"A value that names this attempt. Send the same one when retrying and the answer is what the first call wrote (`200`) instead of a second object; present and empty is refused.","required":false,"schema":{"type":"string","maxLength":200}}],"responses":{"200":{"description":"Nothing new was written: this `Idempotency-Key` already answered here, and this is what the first call wrote — the same key, name, size and `uploadId`.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"path":{"type":"string"},"overwritten":{"type":"boolean"},"uploadId":{"type":"string"}}}}}},"201":{"description":"The file was written, or — with `uploads` — a multipart upload was started and this is its `uploadId`.","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"size":{"type":"integer"},"path":{"type":"string"},"overwritten":{"type":"boolean"},"uploadId":{"type":"string"}}}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The destination is a folder, so it cannot be replaced by this file.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than this deployment accepts in one request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"put":{"operationId":"putUpload","summary":"Upload a file, or write and remove one raw PUT at a time","tags":["upload"],"parameters":[{"name":"path","in":"query","description":"The file key this part belongs to.","required":true,"schema":{"type":"string"}},{"name":"uploadId","in":"query","description":"The multipart upload this part belongs to.","required":true,"schema":{"type":"string"}},{"name":"partNumber","in":"query","description":"Which part this body is, counted from 1.","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"The part was written, and this is its `partNumber` and `ETag` for the completing call.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"`path`, `uploadId` or `partNumber` is missing or is not usable, the request carried no body, or the upload refused the part.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that: the key's scope does not cover this method, or the door's grant does not name this app","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests: this key has spent its request ceiling, and `Retry-After` says when to come back","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"A service this app needs did not answer — the door could not be reached, so this request was not checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/console/forms":{"get":{"operationId":"getConsoleForms","summary":"List this account's forms, oldest first, with a submission count and the last one's time","tags":["forms"],"responses":{"200":{"description":"This account's forms, oldest first, each with its submission count and the time of its last one.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"post":{"operationId":"postConsoleForms","summary":"Create a form in this account; the slug comes from the name and the public id is minted here","tags":["forms"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"A value that names this attempt. Send the same one when retrying and the answer is the form the first call wrote (`200`) instead of `a form named … already exists`; present and empty is refused.","required":false,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"The form's name. Its slug is derived once, here, and never moves."}}}}}},"responses":{"200":{"description":"Nothing new was written: this `Idempotency-Key` already created a form in this account, and this is the row the first call wrote.","content":{"application/json":{"schema":{"type":"object","required":["success","form"],"properties":{"success":{"type":"boolean"},"form":{"type":"object","required":["id","name","public_id","created_at","updated_at"],"properties":{"id":{"type":"string","description":"The form's slug, unique inside the account."},"name":{"type":"string"},"public_id":{"type":"string","description":"The unguessable id a stranger's page is addressed by."},"created_at":{"type":"integer"},"updated_at":{"type":"integer"}}}}}}}},"201":{"description":"The form was created in this account. Its slug comes from the name.","content":{"application/json":{"schema":{"type":"object","required":["success","form"],"properties":{"success":{"type":"boolean"},"form":{"type":"object","required":["id","name","public_id","created_at","updated_at"],"properties":{"id":{"type":"string","description":"The form's slug, unique inside the account."},"name":{"type":"string"},"public_id":{"type":"string","description":"The unguessable id a stranger's page is addressed by."},"created_at":{"type":"integer"},"updated_at":{"type":"integer"}}}}}}}},"400":{"description":"The body names no form, its name has no letter or digit, the name is one of this app's own addresses, a form with that name already exists in this account, or `Idempotency-Key` was sent empty or over 200 characters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/console/forms/{id}":{"get":{"operationId":"getConsoleFormsById","summary":"One form of this account, with its submission stats and its last 30 days","tags":["forms"],"parameters":[{"name":"id","in":"path","description":"The form's slug, unique within this account.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The form, its question set (`null` when it inherits the three defaults), its submission stats and its last 30 days.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The path carries a malformed percent escape, so the slug cannot be read and no form can be addressed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No form in this account answers to that slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"patch":{"operationId":"patchConsoleFormsById","summary":"Rename a form or replace its question set; the slug and the public id do not move","tags":["forms"],"parameters":[{"name":"id","in":"path","description":"The form's slug, unique within this account.","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The new name. A form's address is fixed at creation."},"fields":{"type":["array","null"],"description":"The form's own questions, in the order the page asks them, or null to inherit the three defaults. Replaces the whole set.","items":{"type":"object","required":["key","label","kind","required"],"properties":{"key":{"type":"string","description":"What a submission is keyed by: a letter, then letters, digits or underscores."},"label":{"type":"string","description":"The question in the owner's words, up to 80 characters."},"kind":{"type":"string","enum":["text","email","url","textarea"]},"required":{"type":"boolean"}}}}}}}}},"responses":{"200":{"description":"The form was renamed or its question set replaced; the row published is the one now stored.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body is not JSON, is not an object, carries neither a name nor a field set, or names a field set this app will not store.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No form in this account answers to that slug, so nothing was changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than this deployment's `MAX_SUBMISSION_BYTES`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is neither `application/json` nor `application/x-www-form-urlencoded`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"operationId":"deleteConsoleFormsById","summary":"Delete a form and every submission it received","tags":["forms"],"parameters":[{"name":"id","in":"path","description":"The form's slug, unique within this account.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The form and every submission it received were deleted; `deleted` counts the rows.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The path carries a malformed percent escape, so the slug cannot be read and no form can be addressed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No form in this account answers to that slug, so nothing was deleted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/console/forms/{id}/submissions":{"get":{"operationId":"getConsoleFormsByIdSubmissions","summary":"This form's submissions, newest first","tags":["submissions"],"parameters":[{"name":"id","in":"path","description":"The form's slug, unique within this account.","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"How many rows to return; the deployment caps it.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"This form's submissions, newest first, capped by `limit` and by this deployment's own ceiling.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The path carries a malformed percent escape, so the slug cannot be read and no form can be addressed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No form in this account answers to that slug.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/console/forms/{id}/submissions/{submissionId}":{"delete":{"operationId":"deleteConsoleFormsByIdSubmissionsById","summary":"Delete one submission of this account's form","tags":["submissions"],"parameters":[{"name":"id","in":"path","description":"The form's slug, unique within this account.","required":true,"schema":{"type":"string"}},{"name":"submissionId","in":"path","description":"The submission's id.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The submission was deleted; `deleted` counts the row.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"A path segment carries a malformed percent escape, so the form or the submission cannot be addressed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No submission in this account's form answers to that id, so nothing was deleted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/console/me":{"get":{"operationId":"getConsoleMe","summary":"Who the caller is, as the door names them","tags":["account"],"responses":{"200":{"description":"The account the door named for this caller, and the email the session carries (`null` for a key, which names no person).","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/console/settings":{"get":{"operationId":"getConsoleSettings","summary":"What this deployment does about mail, which is not a setting a form can hold","tags":["account"],"responses":{"200":{"description":"What this deployment does about mail for submissions: nothing is sent, so `settings` is null and `managed` is the estate.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/forms/_/api/v1/forms/{publicId}/submissions":{"post":{"operationId":"postFormsByIdSubmissions","summary":"Write one submission to the form that owns this public id","tags":["submissions"],"parameters":[{"name":"publicId","in":"path","description":"The form's public id, the one its `/f/<publicId>` link carries.","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A value that names this attempt. Send the same one when retrying and the answer is the submission the first call wrote (`200`) instead of a second row; present and empty is refused. The form's own page cannot set a header, so it carries the same value in a body field of the same name.","required":false,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"description":"The stranger's answers, as an object keyed by the keys the form's own questions declare. A key the form never declared is refused (`422`), so read the form's page for the set. `Idempotency-Key` may ride here instead of the header, because the page's own form cannot set one.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"properties":{"Idempotency-Key":{"type":"string","maxLength":200,"description":"The page's copy of the retry handle: the same value the `Idempotency-Key` header carries. Lifted out of the body before the questions are judged, never stored as an answer."}}}},"application/x-www-form-urlencoded":{"schema":{"type":"object","additionalProperties":true,"properties":{"Idempotency-Key":{"type":"string","maxLength":200,"description":"The page's copy of the retry handle: the same value the `Idempotency-Key` header carries. Lifted out of the body before the questions are judged, never stored as an answer."}}}},"multipart/form-data":{"schema":{"type":"object","additionalProperties":true,"properties":{"Idempotency-Key":{"type":"string","maxLength":200,"description":"The page's copy of the retry handle: the same value the `Idempotency-Key` header carries. Lifted out of the body before the questions are judged, never stored as an answer."}}}}}},"responses":{"200":{"description":"Nothing new was written: this `Idempotency-Key` already wrote a submission to this form, and this is the id of the row the first call wrote.","content":{"application/json":{"schema":{"type":"object","required":["success","id"],"properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"The submission's id inside the account that owns the form. Not a credential."}}}}}},"201":{"description":"The submission was written to the account that owns this form. The body names its id.","content":{"application/json":{"schema":{"type":"object","required":["success","id"],"properties":{"success":{"type":"boolean"},"id":{"type":"string","description":"The submission's id inside the account that owns the form. Not a credential."}}}}}},"303":{"description":"The caller posted the page's own form rather than asking for JSON, so the answer is the page it came from (or `?redirect=`, or the form's page with `sent=1`). No body."},"400":{"description":"The body is not JSON, or is not an object, or `Idempotency-Key` was sent empty or longer than 200 characters.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"That public id names no form on this deployment.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is over this deployment's `MAX_SUBMISSION_BYTES`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not `application/json`, `application/x-www-form-urlencoded` or `multipart/form-data`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"A question the form declares was left unanswered, a value is too long or is not the shape its key asks for, nothing at all was filled in, or the body carries a key the form never declared. Nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"options":{"operationId":"optionsFormsByIdSubmissions","summary":"CORS preflight for the write path, answered before any credential","tags":["submissions"],"parameters":[{"name":"publicId","in":"path","description":"The form's public id, the one its `/f/<publicId>` link carries.","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"No content: the preflight's policy is in the headers (`access-control-allow-origin`, `-methods`, `-headers`, `-max-age`) and the answer carries no body. Answered before any credential is read."},"500":{"description":"The deployment failed while answering. The wrapper answers this for any matched route whose handler throws, so it is published rather than left for a caller to discover.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The database schema could not be created, so the request was refused before it reached the handler. Nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/forms/_/api/v1/submissions":{"get":{"operationId":"getSubmissions","summary":"Read the 200 most recent submissions of every form this account owns","tags":["submissions"],"responses":{"200":{"description":"Every form this account owns, and its 200 most recent submissions, newest first, each named by the form it came from.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No key and no session: the caller is not signed in at the door, or the key it sent is unknown, revoked or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This credential opens no account here, this key is not granted this app, or — on a write — the request came from another origin.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, or this deployment could not create its schema, so nothing was read and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/links/_/api/v1/bundles":{"get":{"tags":["bundles"],"summary":"List bundles","description":"Every bundle on the deployment, not only the caller's. Reads are open across owners.","parameters":[{"schema":{"type":"string","enum":["1","true","only","all"],"description":"true|1|only -> archived only. all -> include archived. Omit -> active only."},"required":false,"description":"true|1|only -> archived only. all -> include archived. Omit -> active only.","name":"archived","in":"query"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/BundleWithSummary"}}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getBundles"},"post":{"tags":["bundles"],"summary":"Create a bundle","description":"The identity behind the API key becomes the bundle's owner and is the only one who can later change it.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBundleBody"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bundle"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postBundles"}},"/links/_/api/v1/bundles/{id}":{"get":{"tags":["bundles"],"summary":"Get a bundle","description":"Open to every authenticated key, whoever owns the bundle.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleWithSummary"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getBundlesById"},"put":{"tags":["bundles"],"summary":"Update a bundle","description":"Owner only. Refused with 403 for any other identity.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBundleBody"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bundle"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"putBundlesById"},"delete":{"tags":["bundles"],"summary":"Delete a bundle permanently","description":"Owner only. Unlike a link, a bundle carries no zero-click rule: a bundle is a grouping, not an address, so deleting one drops the memberships and leaves every member link and its click history in place. Archive instead when you only want it out of the default listing.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}},"required":["deleted"]}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"deleteBundlesById"}},"/links/_/api/v1/bundles/{id}/analytics":{"get":{"tags":["bundles"],"summary":"Get bundle analytics. Defaults to all-time if no range given.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClickStats"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getBundlesByIdAnalytics"}},"/links/_/api/v1/bundles/{id}/archive":{"post":{"tags":["bundles"],"summary":"Archive a bundle","description":"Owner only. Hides the bundle from the default listing without deleting anything. Reversible through unarchive.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Archived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bundle"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postBundlesByIdArchive"}},"/links/_/api/v1/bundles/{id}/breakdown":{"get":{"tags":["bundles"],"summary":"Page through a bundle's countries, sources, or domains breakdown","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["countries","referrers","referrer_hosts"],"description":"Which panel to page through: countries, referrers (sources), or referrer_hosts (domains)."},"required":true,"description":"Which panel to page through: countries, referrers (sources), or referrer_hosts (domains).","name":"dimension","in":"query"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range. Defaults to all-time."},"required":false,"description":"Time range. Defaults to all-time.","name":"range","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"description":"Row offset into the ranked list. Defaults to 0."},"required":false,"description":"Row offset into the ranked list. Defaults to 0.","name":"offset","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"Page size, 1-100. Defaults to 10."},"required":false,"description":"Page size, 1-100. Defaults to 10.","name":"limit","in":"query"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BreakdownPage"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getBundlesByIdBreakdown"}},"/links/_/api/v1/bundles/{id}/links":{"get":{"tags":["bundles"],"summary":"List links in a bundle","description":"Open to every authenticated key, whoever owns the bundle or its links.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getBundlesByIdLinks"},"post":{"tags":["bundles"],"summary":"Add a link to a bundle","description":"Open to any authenticated caller with the `create` scope: adding requires owning neither the bundle nor the link. Idempotent. Removing the link again is owner-gated.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddBundleLinkBody"}}}},"responses":{"200":{"description":"Added.","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"boolean"}},"required":["added"]}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postBundlesByIdLinks"}},"/links/_/api/v1/bundles/{id}/links/{linkId}":{"delete":{"tags":["bundles"],"summary":"Remove a link from a bundle","description":"Bundle owner only, whoever owns the link. Owning the link does not grant removal from someone else's bundle. The link itself is not deleted.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"integer","exclusiveMinimum":0,"example":17},"required":true,"name":"linkId","in":"path"}],"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{"removed":{"type":"boolean"}},"required":["removed"]}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"deleteBundlesByIdLinksByLinkId"}},"/links/_/api/v1/bundles/{id}/unarchive":{"post":{"tags":["bundles"],"summary":"Unarchive a bundle","description":"Owner only. Returns an archived bundle to the default listing.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Unarchived.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Bundle"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postBundlesByIdUnarchive"}},"/links/_/api/v1/links":{"post":{"tags":["links"],"summary":"Create a short link","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkBody"}}}},"responses":{"200":{"description":"Existing link returned (duplicate URL).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postLinks"},"get":{"tags":["links"],"summary":"List all short links","parameters":[{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinks"}},"/links/_/api/v1/links/{id}":{"get":{"tags":["links"],"summary":"Get a link with click stats","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinksById"},"put":{"tags":["links"],"summary":"Update a link's URL, label, or expiry","description":"Owner only. The identity behind the API key must be the link's `created_by`, otherwise the call is refused with 403.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLinkBody"}}}},"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"putLinksById"},"delete":{"tags":["links"],"summary":"Delete a link permanently","description":"Owner only, and only while the link has recorded zero clicks. Once any click exists the link is permanent and this call returns 400; disable it instead. Deleting removes the link, its slugs, and frees those slugs for reuse by anyone.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"type":"boolean"}},"required":["deleted"]}}}},"400":{"description":"Refused. The link has recorded at least one click, so it cannot be deleted: `Cannot delete a link with clicks, disable it instead`. A clicked short link is already in circulation, in email, in print and in QR codes, and deleting it frees the slug for reuse. Call `POST /links/{id}/disable` instead, which stops the redirect and keeps the history. The lifetime count is unfiltered, so a single bot click is enough. Also returned for a malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"deleteLinksById"}},"/links/_/api/v1/links/{id}/analytics":{"get":{"tags":["links","analytics"],"summary":"Get analytics for a link (defaults to all-time)","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClickStats"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinksByIdAnalytics"}},"/links/_/api/v1/links/{id}/breakdown":{"get":{"tags":["links","analytics"],"summary":"Page through a link's countries, sources, or domains breakdown","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["countries","referrers","referrer_hosts"],"description":"Which panel to page through: countries, referrers (sources), or referrer_hosts (domains)."},"required":true,"description":"Which panel to page through: countries, referrers (sources), or referrer_hosts (domains).","name":"dimension","in":"query"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range. Defaults to all-time."},"required":false,"description":"Time range. Defaults to all-time.","name":"range","in":"query"},{"schema":{"type":["integer","null"],"minimum":0,"description":"Row offset into the ranked list. Defaults to 0."},"required":false,"description":"Row offset into the ranked list. Defaults to 0.","name":"offset","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"description":"Page size, 1-100. Defaults to 10."},"required":false,"description":"Page size, 1-100. Defaults to 10.","name":"limit","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BreakdownPage"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinksByIdBreakdown"}},"/links/_/api/v1/links/{id}/bundles":{"get":{"tags":["links","bundles"],"summary":"List bundles a link belongs to","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"OK.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Bundle"}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinksByIdBundles"}},"/links/_/api/v1/links/{id}/disable":{"post":{"tags":["links"],"summary":"Disable a link","description":"Owner only. Stops every slug on the link from redirecting while keeping the link and its click history. This is the correct operation for a link that is already in circulation, and the only one available once the link has recorded a click.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postLinksByIdDisable"}},"/links/_/api/v1/links/{id}/enable":{"post":{"tags":["links"],"summary":"Re-enable a disabled link","description":"Owner only. Clears `expires_at` outright, including an expiry set at create or update, so the link resolves again and no longer expires.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postLinksByIdEnable"}},"/links/_/api/v1/links/{id}/qr":{"get":{"tags":["qr"],"summary":"Get a QR code SVG for a link","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","description":"Optional specific slug. Defaults to the link's primary slug."},"required":false,"description":"Optional specific slug. Defaults to the link's primary slug.","name":"slug","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":2048,"description":"QR pixel dimensions (square). Integer 1-2048; defaults to 220."},"required":false,"description":"QR pixel dimensions (square). Integer 1-2048; defaults to 220.","name":"size","in":"query"}],"responses":{"200":{"description":"SVG image","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinksByIdQr"}},"/links/_/api/v1/links/{id}/slugs":{"post":{"tags":["slugs"],"summary":"Add a custom slug to a link","description":"Open to any authenticated caller with the `create` scope, including on a link owned by someone else. Adding a slug creates an additional address for the existing destination; it cannot change, disable or delete anything. Removing or disabling that slug afterwards is owner-gated.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSlugBody"}}}},"responses":{"201":{"description":"Slug added.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Slug"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postLinksByIdSlugs"}},"/links/_/api/v1/links/{id}/slugs/{slug}":{"delete":{"tags":["slugs"],"summary":"Remove a custom slug from a link","description":"Owner of the parent link only, custom slugs only, and only while the slug has recorded zero clicks. Once any click exists the slug is permanent and this call returns 400; disable it instead.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"Removed.","content":{"application/json":{"schema":{"type":"object","properties":{"removed":{"type":"boolean"}},"required":["removed"]}}}},"400":{"description":"Refused. Either the slug has recorded at least one click (`Cannot remove a slug with clicks, disable it instead`), or it is the link's system-generated slug, which can never be removed. For a clicked custom slug, call `POST /links/{id}/slugs/{slug}/disable` instead: it stops that slug resolving and keeps its click history. For the system-generated slug, disable the whole link with `POST /links/{id}/disable`. Also returned for a malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"deleteLinksByIdSlugsBySlug"}},"/links/_/api/v1/links/{id}/slugs/{slug}/disable":{"post":{"tags":["slugs"],"summary":"Disable a specific slug on a link","description":"Owner of the parent link only. Custom slugs only: the system-generated slug cannot be disabled. The slug stops resolving and keeps its click history.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"Disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Slug"}}}},"400":{"description":"Refused. The system-generated slug cannot be disabled, only custom slugs can. Disable the whole link instead. Also returned for a malformed request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postLinksByIdSlugsBySlugDisable"}},"/links/_/api/v1/links/{id}/slugs/{slug}/enable":{"post":{"tags":["slugs"],"summary":"Re-enable a disabled slug on a link","description":"Owner of the parent link only.","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"Enabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Slug"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postLinksByIdSlugsBySlugEnable"}},"/links/_/api/v1/links/{id}/timeline":{"get":{"tags":["links","analytics"],"summary":"Get click timeline for a link (defaults to all-time)","parameters":[{"schema":{"type":"integer","exclusiveMinimum":0,"example":42},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["24h","7d","30d","90d","1y","all"],"description":"Time range for analytics. Defaults are documented per endpoint."},"required":false,"description":"Time range for analytics. Defaults are documented per endpoint.","name":"range","in":"query"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TimelineData"}}}},"400":{"description":"Validation error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getLinksByIdTimeline"}},"/links/_/api/v1/pages":{"get":{"tags":["Pages"],"summary":"List the pages this key can write","description":"Every name the key's owner holds that carries a page, with its blocks. An account holds at most five pages, so this is one page of results and takes no paging parameters.","responses":{"200":{"description":"The owner's pages.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Page"}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getPages"}},"/links/_/api/v1/pages/{name}":{"get":{"tags":["Pages"],"summary":"Read one page, and its numbers","description":"The page's fields and every block, in the order a visitor would see them, plus the report the editor draws: views, taps per block, the answers each question holds, the acts the page performs, and the countries and hosts the visits came from. Both doors read the report from one grouped read, so the JSON and the tab cannot disagree. Bots are stored and excluded from every count.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"}],"responses":{"200":{"description":"The page, with its report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageWithReport"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getPagesByName"},"put":{"tags":["Pages"],"summary":"Write a page's fields","description":"The one line, the longer note, the address and the look. The first save creates the page row, which is what publishing requires.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"headline":{"type":"string","maxLength":120},"bio":{"type":"string","maxLength":280},"email":{"type":["string","null"]},"theme":{"type":"string","enum":["ink","paper","midnight","forest","cocoa","sand","mono","rosewater","neon"]},"accent":{"type":"string","enum":["ember","lime","mint","sky","violet","rose","gold","slate"]}}}}}},"responses":{"200":{"description":"The page as written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"putPagesByName"}},"/links/_/api/v1/pages/{name}/blocks":{"post":{"tags":["Pages"],"summary":"Add a block","description":"A page holds 12 blocks. The first ask is exclusive: adding a block with is_first clears whichever one held it.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["link","app","form","image","text"]},"title":{"type":"string"},"subtitle":{"type":"string"},"href":{"type":"string"},"app_id":{"type":"string"},"field":{"type":"string","enum":["email","text","tel"]},"label":{"type":"string"},"consent":{"type":"string"},"r2_key":{"type":"string"},"alt":{"type":"string"},"body":{"type":"string","maxLength":1200},"is_first":{"type":"boolean"},"visible":{"type":"boolean"}}}}}},"responses":{"201":{"description":"The block as written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageBlock"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postPagesByNameBlocks"}},"/links/_/api/v1/pages/{name}/blocks/{id}":{"patch":{"tags":["Pages"],"summary":"Change one block","description":"Only the keys sent are written. The kind cannot change. Hiding the first ask clears it, because an ask nobody can see is not an ask.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"},{"schema":{"type":["integer","null"]},"required":false,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["link","app","form","image","text"]},"title":{"type":"string"},"subtitle":{"type":"string"},"href":{"type":"string"},"app_id":{"type":"string"},"field":{"type":"string","enum":["email","text","tel"]},"label":{"type":"string"},"consent":{"type":"string"},"r2_key":{"type":"string"},"alt":{"type":"string"},"body":{"type":"string","maxLength":1200},"is_first":{"type":"boolean"},"visible":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The block as written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageBlock"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"patchPagesByNameBlocksById"},"delete":{"tags":["Pages"],"summary":"Remove one block","description":"The taps a block earned are kept: the report says what happened, and a removed block does not un-happen it.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"},{"schema":{"type":["integer","null"]},"required":false,"name":"id","in":"path"}],"responses":{"204":{"description":"Removed."},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"deletePagesByNameBlocksById"}},"/links/_/api/v1/pages/{name}/publish":{"post":{"tags":["Pages"],"summary":"Publish a page","description":"The name starts answering at its address with no destination beside it. Refused when the page was never written or has nothing visible.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"}],"responses":{"200":{"description":"The name now answers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageState"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postPagesByNamePublish"}},"/links/_/api/v1/pages/{name}/submissions":{"get":{"tags":["Pages"],"summary":"Read a page's answers","description":"Every answer the page's questions collected in the last 90 days, newest first, each with the block it answers and that block's title as it reads now. The window and the bot rule are the report's own, so a block's `answers` figure and this list count the same rows. An answer whose block was since removed is still listed, with a null title: the answer outlives its question by design.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"}],"responses":{"200":{"description":"The answers in the window, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageSubmissions"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getPagesByNameSubmissions"}},"/links/_/api/v1/pages/{name}/unpublish":{"post":{"tags":["Pages"],"summary":"Unpublish a page","description":"The address stops answering and the page is left whole, so publishing again brings the same page back.","parameters":[{"schema":{"type":"string"},"required":true,"name":"name","in":"path"}],"responses":{"200":{"description":"The name stopped answering.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageState"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key's scope does not allow this verb.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such page for this key's owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The name is retired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The request is well formed and the page cannot honour it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"postPagesByNameUnpublish"}},"/links/_/api/v1/slugs/{slug}":{"get":{"tags":["slugs"],"summary":"Look up a link by its slug","description":"Open to every authenticated key, whoever owns the link. Reads are not owner-scoped.","parameters":[{"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$"},"required":true,"name":"slug","in":"path"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Scope insufficient.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Slug not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"operationId":"getSlugsBySlug"}},"/links/new":{"get":{"operationId":"getNew","summary":"The intake page","description":"**Public.** The form a stranger opens at `zpe.vc/links/new`: the markup, the caps and the script that posts the drop. No key and no session — this is the one address a person who has never heard of the door is sent to. Any other method on this path is the intake's own 404 page, not a second operation.","tags":["intake"],"x-audience":"public","responses":{"200":{"description":"The intake page.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/links/new/api/form":{"get":{"operationId":"getNewApiForm","summary":"The form was opened (the dwell anchor's row)","description":"**Public.** The page calls this once when the form first paints. The call writes the dwell anchor's row (a salted hash of the caller, and the first time it was seen) and answers the caps the page shows: the dwell floor, the per-hour send cap and the text cap. It reads nothing back and refuses nothing.","tags":["intake"],"x-audience":"public","responses":{"200":{"description":"The caps, and when the anchor was first seen.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/links/new/api/submit":{"post":{"operationId":"postNewApiSubmit","summary":"One drop: the answers and the files in one body","description":"**Public.** The only write a stranger makes. The browser posts the form (multipart) and a script posts JSON; both reach the same fields and the same file walk. A browser is answered `303` to its receipt page; a script is answered `201` with `{receipt, status}`. A replayed drop under the dedupe window answers `201` with the SAME receipt and writes no second row. Every refusal (`413` cap, `429` per-address, `422` fields) is the app's `{ok:false, code, errors}` body.","tags":["intake"],"x-audience":"public","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object"}},"application/json":{"schema":{"type":"object"}}}},"responses":{"201":{"description":"The drop was stored. The body carries the receipt and the build's status.","content":{"application/json":{"schema":{"type":"object"}}}},"303":{"description":"A browser's drop: the receipt page, which is where the person waits.","headers":{"location":{"schema":{"type":"string"}}}},"413":{"description":"The body, one file, or the store's whole submission cap was exceeded.","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"A field was missing or over its limit; the body names which.","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"The caller's address is over the per-hour send cap.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/links/new/api/{receipt}/content":{"get":{"operationId":"getNewApiByReceiptContent","summary":"The assembled object (key)","description":"**Key.** The runner reads what the assembler wrote: the extracted content, the build's status and when it moved. Refused with `401` without an `Authorization: Bearer` account key granted `zpe-links`, and with `403` when the grant is read-only.","tags":["intake"],"x-audience":"key","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"responses":{"200":{"description":"The assembled object and the build's status.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No `Authorization` header, or one the door refuses. The path needs the app's own account key.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"403":{"description":"The key is valid and its grant does not allow this verb.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"404":{"description":"No build carries that receipt.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}},"post":{"operationId":"postNewApiByReceiptContent","summary":"The assembler writes the object, once (key)","description":"**Key.** The one write that puts the assembled object on the build. It is written once: a second write to a build that already holds content is refused rather than merged.","tags":["intake"],"x-audience":"key","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Written. The body carries the build's new status.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No `Authorization` header, or one the door refuses. The path needs the app's own account key.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"403":{"description":"The key is valid and its grant does not allow this verb.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"404":{"description":"No build carries that receipt.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"409":{"description":"The build is past the state where content can be written.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"413":{"description":"The object was over the record's byte cap.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}}},"/links/new/api/{receipt}/files":{"post":{"operationId":"postNewApiByReceiptFiles","summary":"The receipt holder adds files after the drop","description":"**Public, with the receipt as the credential.** The receipt page's file picker posts here. The receipt names the build and nothing else authorises the write: a person holding the eight characters is the person the drop belongs to. A file already stored under the same digest is a duplicate and is not written twice.","tags":["intake"],"x-audience":"public","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"What was stored, what was a duplicate, and what was skipped and why.","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No build carries that receipt.","content":{"application/json":{"schema":{"type":"object"}}}},"413":{"description":"The file, the part count, or the submission's byte cap was exceeded.","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"No file in the batch could be read.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/links/new/api/{receipt}/message":{"get":{"operationId":"getNewApiByReceiptMessage","summary":"The held message, its sign-off and its attempts (key)","description":"**Key.** Reads the draft that is held for this build: the subject and body, the recipient, the latest sign-off and every send attempt so far. A caller that only wants to see the words can stop here — nothing is sent by a read.","tags":["intake"],"x-audience":"key","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"responses":{"200":{"description":"The held message, its sign-off and its attempts.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No `Authorization` header, or one the door refuses. The path needs the app's own account key.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"403":{"description":"The key is valid and its grant does not allow this verb.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"404":{"description":"This build holds no message yet.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}},"post":{"operationId":"postNewApiByReceiptMessage","summary":"Write the held message for a row already read (key)","description":"**Key.** Writes the draft the sender will read. A second post to a build that already holds a message is refused: the draft is written once, and a correction is a new build rather than a quiet rewrite of words somebody may have signed.","tags":["intake"],"x-audience":"key","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"The message the build now holds.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No `Authorization` header, or one the door refuses. The path needs the app's own account key.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"403":{"description":"The key is valid and its grant does not allow this verb.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"404":{"description":"No build carries that receipt.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"409":{"description":"The build already holds a message, or its recipient is not settled.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"422":{"description":"The message was missing a field the composer needs.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}}},"/links/new/api/{receipt}/message/sign":{"post":{"operationId":"postNewApiByReceiptMessageSign","summary":"Record a sign-off naming the body and the recipient (key)","description":"**Key.** Records that a person read this exact draft, addressed to this exact recipient: the sign-off names the draft's hash, so a body that moved after the signature no longer matches it. `release` refuses without one.","tags":["intake"],"x-audience":"key","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"The sign-off that is now the newest for this message.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The sign-off named nobody as the reader.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"401":{"description":"No `Authorization` header, or one the door refuses. The path needs the app's own account key.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"403":{"description":"The key is valid and its grant does not allow this verb.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"404":{"description":"This build holds no message to sign.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"409":{"description":"The message has no recipient, so there is nothing to sign for.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}}},"/links/new/api/{receipt}/release":{"post":{"operationId":"postNewApiByReceiptRelease","summary":"The app's only send (key, and refused without a sign-off)","description":"**Key.** The one operation in this app that sends mail. It refuses unless the held message carries a sign-off whose draft hash still matches, so a caller cannot post words nobody read. The refusal codes are the contract `tools/mail-release.mjs` turns into exit codes — `409` is 'nobody signed this', not 'the mail service refused it'.","tags":["intake"],"x-audience":"key","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"responses":{"200":{"description":"The send was attempted; the body carries its outcome.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No `Authorization` header, or one the door refuses. The path needs the app's own account key.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"403":{"description":"The key is valid and its grant does not allow this verb.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"404":{"description":"This build holds no message to send.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"409":{"description":"The message is unsigned, or its sign-off names a body that has since moved.","content":{"application/problem+json":{"schema":{"type":"object"}}}},"503":{"description":"The mail service or the store could not be reached; nothing was sent.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}}},"/links/new/r/{receipt}":{"get":{"operationId":"getNewRByReceipt","summary":"The page the submitter waits on","description":"**Public, with the receipt as the credential.** The receipt page: what was received, the files, the held message's state, the address box when the record holds no address, and the confirm link. A receipt nobody holds answers the same page at `404`, so the answer never says whether a receipt exists.","tags":["intake"],"x-audience":"public","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"responses":{"200":{"description":"The receipt page.","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"The same page, saying it is not on this server.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/links/new/r/{receipt}/claim":{"post":{"operationId":"postNewRByReceiptClaim","summary":"Keep the published page, once, for the door's principal","description":"**Door.** Unreachable without the door's session: the receipt opens the record, and only a signed-in principal can become its owner — answered `401` with no session, and `503` when the door itself cannot be reached (which is not a refusal, because the person may well be signed in). A browser is sent back to the receipt page with `303`; a script is told `{ok:true, receipt, owner_principal}`. Only a published row nobody has kept can be claimed, once.","tags":["intake"],"x-audience":"door","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"responses":{"200":{"description":"The row now belongs to the caller's principal.","content":{"application/json":{"schema":{"type":"object"}}}},"303":{"description":"A browser's claim: back to the receipt page.","headers":{"location":{"schema":{"type":"string"}}}},"401":{"description":"No door session.","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"No build carries that receipt.","content":{"application/json":{"schema":{"type":"object"}}}},"409":{"description":"The row is not published, or somebody already owns it.","content":{"application/json":{"schema":{"type":"object"}}}},"503":{"description":"The door could not be reached, so the claim was not decided.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/links/new/r/{receipt}/confirm":{"get":{"operationId":"getNewRByReceiptConfirm","summary":"The correction page","description":"**Public, with the receipt as the credential.** The page that lets the person fix a field before the press. It is a read: nothing is confirmed until the POST below.","tags":["intake"],"x-audience":"public","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"responses":{"200":{"description":"The correction page.","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"No build carries that receipt.","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The build is closed; the page says so.","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"operationId":"postNewRByReceiptConfirm","summary":"The person's press, which is what confirms","description":"**Public, with the receipt as the credential.** The only act that moves a build to `confirmed`: the page's press, with the corrected fields. It answers `303` back to the receipt page, which is where the published address appears.","tags":["intake"],"x-audience":"public","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}},"application/json":{"schema":{"type":"object"}}}},"responses":{"303":{"description":"Confirmed; back to the receipt page.","headers":{"location":{"schema":{"type":"string"}}}},"404":{"description":"No build carries that receipt.","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The build is closed, or somebody pressed first.","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"A corrected field was refused; the page says which.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/links/new/r/{receipt}/email":{"post":{"operationId":"postNewRByReceiptEmail","summary":"The address the page asks for when the record holds none","description":"**Public, with the receipt as the credential.** The person says where their link should go. This recomposes the held draft and writes nothing anybody can send: the message still waits for a read and a sign-off. A browser is sent back to the receipt page with `303`.","tags":["intake"],"x-audience":"public","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}}],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}},"application/json":{"schema":{"type":"object"}}}},"responses":{"303":{"description":"The address was recorded; back to the receipt page.","headers":{"location":{"schema":{"type":"string"}}}},"404":{"description":"No build carries that receipt.","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The message is already signed, or the row is past the ask.","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"That is not an address the app can write to.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/links/new/r/{receipt}/f/{id}":{"get":{"operationId":"getNewRByReceiptFById","summary":"One stored file, streamed through the worker","description":"**Public, with the receipt as the credential.** The bytes of one file on this build, streamed from R2 through the worker so the bucket is never public. The receipt and the file id must belong together: a file id from somebody else's build is not a row and answers `404`.","tags":["intake"],"x-audience":"public","parameters":[{"name":"receipt","in":"path","required":true,"description":"The eight-character receipt the submit answered with. It is the only key a stranger holds.","schema":{"type":"string","minLength":4,"maxLength":16,"pattern":"^[A-Za-z0-9]{4,16}$"}},{"name":"id","in":"path","required":true,"description":"The file row's UUID, as the receipt page links it.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The file's own bytes, at the type the row stored.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"That file is not on this server.","content":{"application/problem+json":{"schema":{"type":"object"}}}}}}},"/mindmap/_/api/v1/results":{"get":{"operationId":"getMachineResults","summary":"The estate's machine address for this app: the same read as /api/results, keyed by the number.","description":"`<mount>/_/api/v1/<tail>` is the address a caller with no browser uses. The guard is the same resolver the browser routes use, and its verdict is final: no principal reads nothing here. A request that presented no credential gets the estate's RFC 9457 `unauthenticated` document; one that presented a number which resolves to nobody gets the app's uniform 401 `unknown_account`, so unknown, revoked and claimed numbers stay indistinguishable.\n\nThe method is checked BEFORE the credential, so a caller learns the method without being asked for a key. Any other path under `/_/` is a `not-found` problem document, never the SPA shell.","tags":["machine"],"responses":{"200":{"description":"The caller's own rows, keyed by test id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultSet"}}}},"401":{"description":"One of two documents, by whether a credential was presented at all. No credential: the estate's problem document. A credential that resolves to nobody: the app's uniform `unknown_account`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}},"application/json":{"schema":{"$ref":"#/components/schemas/AppError"}}}},"405":{"description":"A method this path does not answer. The `allow` header names GET.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/mindmap/api/account":{"delete":{"operationId":"deleteAccount","summary":"Delete what this app holds for the resolved principal, and nothing else.","description":"The only destructive route in the app, and its scope is narrow: groups the principal owns (ownership passes to the earliest-joined remaining member, or the group goes), invite codes they minted, their membership rows, their results and their profile. The central identity database is NOT touched: deleting a principal there is the gate's business, not this app's. Idempotent, so a second call reports zero deletions.","tags":["account"],"responses":{"200":{"description":"What went, counted. `gate` is always `untouched`. On a deployment with no `DB` bound the same 200 carries `{error: \"not_configured\"}` instead.","content":{"application/json":{"schema":{"type":"object","required":["ok","deleted","gate"],"properties":{"ok":{"type":"boolean"},"deleted":{"type":"object","required":["results","profile","memberships","invites","groupsDeleted","groupsTransferred"],"properties":{"results":{"type":"integer","minimum":0},"profile":{"type":"integer","minimum":0},"memberships":{"type":"integer","minimum":0},"invites":{"type":"integer","minimum":0},"groupsDeleted":{"type":"integer","minimum":0},"groupsTransferred":{"type":"integer","minimum":0}}},"gate":{"type":"string","const":"untouched"},"error":{"type":"string","description":"Present only on a deployment with no DB bound."}}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}}}}},"/mindmap/api/account/attach":{"post":{"operationId":"postAccountAttach","summary":"Adopt an existing number on this device. A wrong number and an unknown one answer alike.","description":"The one route that takes a number in a BODY rather than a header. A number that does not normalise, an unknown number, a revoked number and a claimed one all answer the same 401 `unknown_account`, because telling them apart is what would let a stranger probe the space.","tags":["account"],"security":[],"requestBody":{"required":true,"description":"The number to adopt.","content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"type":"string","description":"Sixteen digits, with or without the dashes."}}}}}},"responses":{"200":{"description":"The principal that number resolves to, and its last four.","content":{"application/json":{"schema":{"type":"object","required":["principal"],"properties":{"principal":{"type":"object","required":["principalId","last4"],"properties":{"principalId":{"type":"string"},"last4":{"type":["string","null"]}}}}}}}},"400":{"description":"The body is not JSON, is larger than 1 MB, or carries the wrong shape. `bad_request` for a body that will not parse, `body_too_large` past 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/account/start":{"post":{"operationId":"postAccountStart","summary":"Mint a new anonymous number. The number is returned once and never again.","description":"This route is retired as the way a person gets a number (DECISIONS.md D-005: a number is minted at the door), but the worker still answers it, and this contract describes what it answers rather than what the app recommends. The number is returned ONCE: the identity database stores only a peppered hash, so this response is the only copy that will exist. Rate limited by counting rows minted in the last hour; no IP, no fingerprint, no identifier is stored. When `TURNSTILE_SECRET` is bound the call needs a `turnstileToken` in the body or the maintenance key in `x-mm-mint-key`, and the check FAILS CLOSED: a siteverify outage refuses the mint rather than opening it.","tags":["account"],"security":[],"parameters":[{"name":"x-mm-mint-key","in":"header","required":false,"description":"The maintenance key, for scripts that cannot solve a Turnstile widget. A worker secret; nothing else uses it.","schema":{"type":"string"}}],"requestBody":{"required":false,"description":"Optional. A missing or unparseable body is read as `{}`, so the refusal is the human check, not a 400.","content":{"application/json":{"schema":{"type":"object","properties":{"turnstileToken":{"type":"string","description":"The widget's token, verified against Cloudflare's siteverify."}}}}}},"responses":{"201":{"description":"The only copy of the number that will ever exist.","content":{"application/json":{"schema":{"type":"object","required":["number","last4","principalId","created","note"],"properties":{"number":{"type":"string","description":"Sixteen digits as four groups of four, e.g. 0000-0000-0000-0000. A bearer secret."},"last4":{"type":"string","description":"The last four digits, for the app's card."},"principalId":{"type":"string"},"created":{"type":"boolean","description":"True when this call made the principal, false when it resolved one that already existed."},"note":{"type":"string"}}}}}},"403":{"description":"The human check refused, so nothing was created. `reason` is the check's own word.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"human_check_required","reason":"missing_token"}}}}},"429":{"description":"The hourly mint cap was reached. The cap is counted from rows, never from an address.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"rate_limited"}}}}},"503":{"description":"The gate is not wired on this deployment (`DOOR_INTERNAL_URL` or the `DOOR_INTERNAL_TOKEN` secret unbound).","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_configured: gate"}}}}}}}},"/mindmap/api/groups":{"post":{"operationId":"postGroups","summary":"Create a group. The creator is owner and a member, and an invite is returned once.","description":"The creator joins at share level `none`, so creating a group shares nothing by itself. The invite code is returned in this response and stored only as a hash, so it cannot be handed back a second time: only its last four are readable later.","tags":["groups"],"requestBody":{"required":true,"description":"The group's name.","content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Trimmed and capped at 80 characters."}}}}}},"responses":{"200":{"description":"The group, and the one-time invite.","content":{"application/json":{"schema":{"type":"object","required":["group","invite"],"properties":{"group":{"$ref":"#/components/schemas/Group"},"invite":{"$ref":"#/components/schemas/Invite"}}}}}},"400":{"description":"The name is missing or blank.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"name_required"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}},"get":{"operationId":"getGroups","summary":"Every group the resolved principal is a member of, with their own share level.","description":"Newest group first. `memberCount` counts members, never results.","tags":["groups"],"responses":{"200":{"description":"The caller's groups.","content":{"application/json":{"schema":{"type":"object","required":["groups"],"properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/GroupMembership"}}}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}}}}},"/mindmap/api/groups/join":{"post":{"operationId":"postGroupsJoin","summary":"Join a group by its invite code.","description":"An unknown, expired, exhausted and foreign code all answer the same 404 `invalid_code`: the four causes are indistinguishable, so a code cannot be probed. A member who presents a live code again answers `alreadyMember: true` rather than an error.","tags":["groups"],"requestBody":{"required":true,"description":"The invite code, as the person was given it.","content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string"}}}}}},"responses":{"200":{"description":"The group, whether the caller was already in it, and the code's last four.","content":{"application/json":{"schema":{"type":"object","required":["group","alreadyMember","inviteLast4"],"properties":{"group":{"$ref":"#/components/schemas/Group"},"alreadyMember":{"type":"boolean"},"inviteLast4":{"type":"string"}}}}}},"400":{"description":"The body is not JSON, is larger than 1 MB, or carries the wrong shape. `bad_request` for a body that will not parse, `body_too_large` past 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"The code is unknown, expired, exhausted or belongs to another account.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"invalid_code"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/groups/leave":{"post":{"operationId":"postGroupsLeave","summary":"Leave a group the resolved principal is a member of.","description":"An unknown group and a group the caller is not in answer the same 404 `not_found`.","tags":["groups"],"requestBody":{"required":true,"description":"The group to leave.","content":{"application/json":{"schema":{"type":"object","required":["groupId"],"properties":{"groupId":{"type":"string"}}}}}},"responses":{"200":{"description":"The membership row is gone.","content":{"application/json":{"schema":{"type":"object","required":["ok","left"],"properties":{"ok":{"type":"boolean","const":true},"left":{"type":"string"}}}}}},"400":{"description":"The body is not JSON, is larger than 1 MB, or carries the wrong shape. `bad_request` for a body that will not parse, `body_too_large` past 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not a member of it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/groups/{id}":{"get":{"operationId":"getGroupsById","summary":"A group's detail: membership, the caller's share level, their own per-test switches.","description":"Membership and each member's sharing state are visible to any member; the invite metadata is the owner's alone (`invites` is null for everybody else). No member's results are ever included here. `shareableTests` is what the CALLER could switch on, not what anybody else holds.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The group as this caller may read it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupDetail"}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not a member of it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}}}}},"/mindmap/api/groups/{id}/dots":{"put":{"operationId":"putGroupsByIdDots","summary":"Show the caller's own value as a dot in this group's profile, or stop showing it.","description":"The choice is the caller's alone. A reader always sees their own value; this switch decides whether OTHER members do. `show_dots`, `showDots` and `show` are all read, and anything not true is stored as false.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Whether to show the caller's own values as dots.","content":{"application/json":{"schema":{"type":"object","required":["show_dots"],"properties":{"show_dots":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The switch as stored.","content":{"application/json":{"schema":{"type":"object","required":["ok","groupId","showDots"],"properties":{"ok":{"type":"boolean","const":true},"groupId":{"type":"string"},"showDots":{"type":"boolean"}}}}}},"400":{"description":"The body is not JSON, is larger than 1 MB, or carries the wrong shape. `bad_request` for a body that will not parse, `body_too_large` past 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not a member of it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/groups/{id}/invites":{"post":{"operationId":"postGroupsByIdInvites","summary":"The owner mints another invite. The code is returned once, here.","description":"The owner's alone: a member who is not the owner, and a group that does not exist, answer the same 404 `not_found`. The code is stored only as a sha256, so this response is the only copy that will exist; later reads show its last four, its uses and its expiry.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}}],"requestBody":{"required":false,"description":"Optional. A missing body is read as `{}`.","content":{"application/json":{"schema":{"type":"object","properties":{"maxUses":{"type":"integer","minimum":1,"description":"How many joins the code allows. Defaults to 25."},"expiresAt":{"type":"string","format":"date-time","description":"When the code stops working. Absent means it does not expire."},"principalId":{"type":"string","description":"Address the code to one principal."}}}}}},"responses":{"200":{"description":"The one-time code, and what it allows.","content":{"application/json":{"schema":{"type":"object","required":["invite"],"properties":{"invite":{"$ref":"#/components/schemas/Invite"}}}}}},"400":{"description":"The body is not JSON, is larger than 1 MB, or carries the wrong shape. `bad_request` for a body that will not parse, `body_too_large` past 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not its owner.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/groups/{id}/pair/{principalId}":{"get":{"operationId":"getGroupsByIdPairByPrincipalId","summary":"Pairwise divergence with one other member.","description":"Refused uniformly unless BOTH principals are members of this group AND sharing at `pair`: an unknown member, a non-member, a member at `group` and a request naming the caller themselves all answer the same 404 `pair_unavailable`. Only dimensions both members hold a value for are returned; each side's per-test exclusions are applied before the read.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}},{"name":"principalId","in":"path","required":true,"description":"The other member's principal id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The dimensions both share, with the absolute difference and who is higher.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PairStats"}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"Both principals are not members sharing at `pair`, or the request names the caller.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"pair_unavailable"}}}}}}}},"/mindmap/api/groups/{id}/share":{"put":{"operationId":"putGroupsByIdShare","summary":"Set the caller's OWN share level in this group.","description":"A member can only change their own level; there is no field for somebody else's. `group` puts their values into the group profile, `pair` additionally allows a pairwise read with another member who is also at `pair`, and `none` shares nothing.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The caller's new level. `shareLevel` is accepted as a spelling.","content":{"application/json":{"schema":{"type":"object","required":["share_level"],"properties":{"share_level":{"$ref":"#/components/schemas/ShareLevel"}}}}}},"responses":{"200":{"description":"The level as stored.","content":{"application/json":{"schema":{"type":"object","required":["ok","groupId","shareLevel"],"properties":{"ok":{"type":"boolean","const":true},"groupId":{"type":"string"},"shareLevel":{"$ref":"#/components/schemas/ShareLevel"}}}}}},"400":{"description":"The level is not one of `none`, `group`, `pair`.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_share_level"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not a member of it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/groups/{id}/stats":{"get":{"operationId":"getGroupsByIdStats","summary":"The group profile: a median per dimension, with n, and the dots members chose to show.","description":"Median, never mean. A dimension with fewer than `minSharers` contributors answers `status: \"insufficient\"` with its n and no median, range or spread: the gap is named and nothing is invented. Members who are not sharing at `group` or `pair` appear in `absent` with their level, never as a zero.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}}],"responses":{"200":{"description":"The group's profile as this caller may read it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupStats"}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not a member of it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}}}}},"/mindmap/api/groups/{id}/tests":{"put":{"operationId":"putGroupsByIdTests","summary":"Set the caller's OWN per-test exclusions in this group.","description":"An excluded test is filtered in the query before anything is loaded, so its values cannot reach a dimension, a median or a payload: it lowers n rather than contributing a zero. A test id the caller holds no result for is refused (`unknown_test`) rather than stored.","tags":["groups"],"parameters":[{"name":"id","in":"path","required":true,"description":"The group id, a UUID.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The exclusions. An empty array shares every test the caller holds.","content":{"application/json":{"schema":{"type":"object","required":["excluded"],"properties":{"excluded":{"type":"array","items":{"type":"string"},"description":"The test ids to switch OFF for this group."}}}}}},"responses":{"200":{"description":"The exclusions as stored.","content":{"application/json":{"schema":{"type":"object","required":["ok","groupId","excludedTests"],"properties":{"ok":{"type":"boolean","const":true},"groupId":{"type":"string"},"excludedTests":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"`excluded` is not an array, or names a test the caller holds no result for.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_excluded_tests"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"404":{"description":"No such group, or the caller is not a member of it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"not_found"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/health":{"get":{"operationId":"getHealth","summary":"Liveness, and whether the gate, the database, the assets and the human check are wired.","description":"The one route a monitor can call without a credential. `gateConfigured` is false when the door's address (`DOOR_INTERNAL_URL`) or its bearer secret (`DOOR_INTERNAL_TOKEN`) is unbound, which is the difference between a worker that refuses everybody and one that is running. This Worker holds no identity database of its own; it asks the door. `humanCheck` names whether the Turnstile secret is bound: an unarmed production mint is visible here rather than silent.","tags":["account"],"security":[],"responses":{"200":{"description":"The worker is up, and each boolean says whether that binding is present.","content":{"application/json":{"schema":{"type":"object","required":["ok","gateConfigured","dbConfigured","assetsBound","humanCheck"],"properties":{"ok":{"type":"boolean","const":true},"gateConfigured":{"type":"boolean"},"dbConfigured":{"type":"boolean"},"assetsBound":{"type":"boolean"},"humanCheck":{"type":"boolean"}}}}}}}}},"/mindmap/api/me":{"get":{"operationId":"getMindMapperMe","summary":"Resolve the presented credential to one principal, and summarise what this app holds for it.","description":"Null is a clean answer here, not an error: a request with no credential and one whose credential resolves to nobody both answer `principal: null`, so this route never tells a stranger whether a number exists. `via` is the resolver's own word for which credential answered (`account` for a number, `identity` for a door session).","tags":["account"],"security":[],"responses":{"200":{"description":"The principal, or null; with the counts and the profile's update time when there is one.","content":{"application/json":{"schema":{"type":"object","required":["principal","via","results","profile"],"properties":{"principal":{"oneOf":[{"$ref":"#/components/schemas/Principal"},{"type":"null"}],"description":"Null when no credential resolved."},"via":{"type":["string","null"],"enum":["account","identity",null]},"results":{"type":"object","required":["count","lastUpdatedAt"],"properties":{"count":{"type":"integer","minimum":0},"lastUpdatedAt":{"type":["string","null"],"format":"date-time"}}},"profile":{"oneOf":[{"type":"object","required":["updatedAt"],"properties":{"updatedAt":{"type":"string","format":"date-time"}}},{"type":"null"}]}}}}}}}}},"/mindmap/api/profile":{"get":{"operationId":"getProfile","summary":"This app's profile row for the resolved principal.","description":"`null` when the principal has never saved one, which is not an error.","tags":["profile"],"responses":{"200":{"description":"The profile, or null.","content":{"application/json":{"schema":{"type":"object","required":["profile"],"properties":{"profile":{"oneOf":[{"$ref":"#/components/schemas/Profile"},{"type":"null"}]}}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}}}},"put":{"operationId":"putProfile","summary":"Upsert the resolved principal's profile row.","description":"Every field is trimmed and capped at 200 characters; an empty or non-string field is stored as null rather than refused, so a person can clear a field by sending nothing in it.","tags":["profile"],"requestBody":{"required":true,"description":"The fields to store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileWrite"}}}},"responses":{"200":{"description":"The row as stored.","content":{"application/json":{"schema":{"type":"object","required":["ok","profile"],"properties":{"ok":{"type":"boolean","const":true},"profile":{"$ref":"#/components/schemas/Profile"}}}}}},"400":{"description":"The body is not JSON, is larger than 1 MB, or carries the wrong shape. `bad_request` for a body that will not parse, `body_too_large` past 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/results":{"get":{"operationId":"getResults","summary":"Every stored result for the resolved principal.","description":"Scoped to the principal the credential resolves to; there is no parameter that names somebody else. `results` is a map keyed by test id, and each payload is whatever the app saved, so this contract does not shape it.","tags":["results"],"responses":{"200":{"description":"The caller's own rows, keyed by test id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultSet"}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}}}},"put":{"operationId":"putResults","summary":"Upsert results, scoped to the resolved principal.","description":"A per-test upsert: a key present in `results` replaces that row's payload, a key absent is left alone (this is not a replace-all). A test id is truncated to 64 characters and a payload larger than 1 MB is skipped rather than refused, so one oversized value does not cost the caller the rest of the write. The answer is the same shape a read would give.","tags":["results"],"requestBody":{"required":true,"description":"The rows to write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultSetWrite"}}}},"responses":{"200":{"description":"The write landed; the counts are what the store holds now.","content":{"application/json":{"schema":{"type":"object","required":["ok","count","lastUpdatedAt"],"properties":{"ok":{"type":"boolean","const":true},"count":{"type":"integer","minimum":0},"lastUpdatedAt":{"type":["string","null"],"format":"date-time"}}}}}},"400":{"description":"`results` is missing, or is not an object, or the body is not JSON.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"bad_request"}}}}},"401":{"description":"No credential, or one that resolves to nobody. A wrong number, an unknown number and a revoked number all answer this same body, so a stranger cannot probe the number space.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"unknown_account"}}}}},"413":{"description":"The request body is larger than 1000000 bytes.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/AppError"}],"example":{"error":"body_too_large"}}}}}}}},"/mindmap/api/session":{"get":{"operationId":"getSession","summary":"Is there a live fleet sign-in on this request? The passkey door, read.","description":"Answers before a principal exists, so a person signed in at the door with a passkey and no number on the device is not shown Sign In. `linked` is the second half of the truth: signed in at the door but no principal in the gate yet. No cookie, an unreachable door and a refused session are one answer, signed out. Nothing here creates or changes a row.","tags":["account"],"security":[],"responses":{"200":{"description":"Signed in or not, and whether the gate already holds a principal for that person.","content":{"application/json":{"schema":{"type":"object","required":["signedIn","linked","name"],"properties":{"signedIn":{"type":"boolean"},"linked":{"type":"boolean"},"name":{"type":["string","null"],"description":"The door's own name for the person. No email, nothing else."}}}}}}}}},"/mindmap/api/signout":{"post":{"operationId":"postSignout","summary":"End the door's session, carrying the request's cookies to the door and the cleared ones back.","description":"One gate, so the session is the door's to end and this app only carries the act. A door that does not answer is named in the reply (`door: \"did_not_answer\"`) rather than hidden: a browser whose cookie was not cleared still looks signed in on the next load, and a silent success would be a lie. GET on this path is not a route and answers the router's 404.","tags":["account"],"security":[],"responses":{"200":{"description":"Whether the door answered. `set-cookie` carries the door's own clearing headers.","content":{"application/json":{"schema":{"type":"object","required":["ok","door"],"properties":{"ok":{"type":"boolean"},"door":{"type":"string","enum":["ended","did_not_answer"]}}}}}}}}},"/newsletter/_/api/v1/newsletter":{"post":{"operationId":"postNewsletter","summary":"Create this account's one newsletter list, or change its settings","tags":["newsletter"],"responses":{"200":{"description":"The account's list existed and its settings are now what this request asked for; no new list was created.","content":{"application/json":{"schema":{"type":"object"}}}},"201":{"description":"The account had no list and this request created it, at the `slug` it named.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The request named no usable list: the title, the from name, the from address, or the public address (1-40 characters of a-z, 0-9 and inner hyphens) is missing or malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No session cookie and no key, so there is nobody whose list this could be.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential was real and does not open this operation: the door named no account, or the key does not carry this route's grant or scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"That public address already belongs to another account's list; one slug exists once across the estate.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its request ceiling for the moment; retry once the wait the answer names has passed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door (auth.zpe.vc) did not answer, so the caller could not be named and nothing was written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/newsletter/_/api/v1/send":{"post":{"operationId":"postSend","summary":"Queue a campaign to the list, or send one test to a single address","tags":["campaigns"],"responses":{"200":{"description":"The campaign was queued for delivery, or — with `mode: test` — the one test email was sent to the address that was asked for.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Nothing was queued: no list exists yet, the subject or the HTML is empty, the test address is malformed, or this deployment has no sender wired (`EMAIL_API_URL`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No session cookie and no key, so there is nobody whose campaign this could be.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential was real and does not open this operation: the door named no account, or the key does not carry this route's grant or scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its request ceiling for the moment; retry once the wait the answer names has passed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door (auth.zpe.vc) did not answer, so the caller could not be named and nothing was queued.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/notes/_/api/v1/attachments":{"get":{"operationId":"listAttachments","summary":"Every image, video and file the account has","tags":["files"],"responses":{"200":{"description":"One page, newest first, drawn from the notes' own `pics`, `videos` and `files` columns.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentPage"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"page","in":"query","description":"1-based page of 20.","schema":{"type":"integer","minimum":1,"default":1}}]}},"/notes/_/api/v1/docs/node":{"post":{"operationId":"createDocNode","summary":"Create a doc file or folder","tags":["docs"],"responses":{"201":{"description":"The node as stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocNode"}}}},"400":{"description":"Not a JSON body, no title, or a type that is neither `file` nor `folder`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","description":"Names one intent. A retry carrying the same key gets the first answer instead of a second row; the same key on a different operation is a 409.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"description":"A new node. A file starts with `# <title>` as its body.","content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["file","folder"]},"title":{"type":"string"},"parent_id":{"type":["string","null"],"description":"Absent or null puts the node at the root."}},"required":["type","title"]}}}}}},"/notes/_/api/v1/docs/node/{id}":{"get":{"operationId":"getDocNode","summary":"Read one doc node","tags":["docs"],"responses":{"200":{"description":"The node and its body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DocNode"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The node is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}]},"put":{"operationId":"updateDocNode","summary":"Save a doc node's body","tags":["docs"],"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string"}},"required":["success","id"]}}}},"400":{"description":"No `content` string in the body.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The node is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The body. An empty string is a body, not a delete.","content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string"}},"required":["content"]}}}}},"delete":{"operationId":"deleteDocNode","summary":"Delete a doc node and its subtree","tags":["docs"],"responses":{"200":{"description":"Deleted, naming every id the delete took with it.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"deletedIds":{"type":"array","items":{"type":"string"}}},"required":["success","deletedIds"]}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The node is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}]},"patch":{"operationId":"moveDocNode","summary":"Move a doc node under another folder","tags":["docs"],"responses":{"200":{"description":"Moved, or already where the request asked.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success","message"]}}}},"400":{"description":"Not a JSON body, or a destination that is this node's own descendant or another account's folder.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The node is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The destination folder, or null for the root.","content":{"application/json":{"schema":{"type":"object","properties":{"new_parent_id":{"type":["string","null"],"description":"Null moves the node to the root."}}}}}}}},"/notes/_/api/v1/docs/node/{id}/rename":{"post":{"operationId":"renameDocNode","summary":"Rename a doc node","tags":["docs"],"responses":{"200":{"description":"Renamed.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"new_title":{"type":"string"}},"required":["success","new_title"]}}}},"400":{"description":"No title in the body, or a blank one.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The node is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The new title; a blank one is refused.","content":{"application/json":{"schema":{"type":"object","properties":{"new_title":{"type":"string"}},"required":["new_title"]}}}}}},"/notes/_/api/v1/docs/tree":{"get":{"operationId":"docsTree","summary":"The account's docs tree","tags":["docs"],"responses":{"200":{"description":"One page of the tree, one level per parent.","headers":{"x-next-cursor":{"description":"Present when another page exists: the `cursor` to send for it. Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DocTree"}}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"limit","in":"query","description":"How many rows this page carries. Default 100 for tags, 200 for the docs tree; the most a page returns is 500.","schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"cursor","in":"query","description":"The `x-next-cursor` value from the previous page. A keyset cursor, not an offset, so a write during the walk neither repeats nor skips a row.","schema":{"type":"string"}}]}},"/notes/_/api/v1/files/{noteId}/{fileId}":{"get":{"operationId":"downloadAttachment","summary":"Download one attachment","tags":["files"],"responses":{"200":{"description":"The object, with the content type it was stored under. Text-like files are served as `text/plain` so a stored page cannot run in the app's origin.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The note id is not a whole number.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The note is not this account's, or the object is not in storage.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer, or this deployment has no R2 bucket bound.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"noteId","in":"path","required":true,"description":"The note the attachment belongs to. Checked against the account before storage is read.","schema":{"type":"integer"}},{"name":"fileId","in":"path","required":true,"description":"The attachment's id, as the note's `files` array names it.","schema":{"type":"string"}},{"name":"preview","in":"query","description":"`true` asks the browser to render rather than download.","schema":{"type":"boolean"}}]}},"/notes/_/api/v1/notes":{"get":{"operationId":"listNotes","summary":"List the account's notes","tags":["notes"],"responses":{"200":{"description":"One page of notes, newest first with pinned rows on top.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePage"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer, so this app cannot say whose rows these are.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"page","in":"query","description":"1-based page of ten notes. A page below one is read as one.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"tag","in":"query","description":"Only notes carrying this tag, lowercased by the writer.","schema":{"type":"string"}},{"name":"favorites","in":"query","description":"`true` limits the answer to favorited notes.","schema":{"type":"boolean"}},{"name":"archived","in":"query","description":"`true` lists archived notes instead of live ones.","schema":{"type":"boolean"}},{"name":"startTimestamp","in":"query","description":"Unix seconds. Used together with `endTimestamp`; alone it is ignored.","schema":{"type":"integer"}},{"name":"endTimestamp","in":"query","description":"Unix seconds, exclusive. Used together with `startTimestamp`.","schema":{"type":"integer"}}]},"post":{"operationId":"createNote","summary":"Create a note, with attachments","tags":["notes"],"responses":{"201":{"description":"The note as stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"400":{"description":"No text and no file, and no multipart body at all.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer, or this deployment has no R2 bucket bound so the attachment cannot be stored.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","description":"Names one intent. A retry carrying the same key gets the first answer instead of a second row; the same key on a different operation is a 409.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"description":"`content`, plus zero or more `file` parts. A note with no text and no file is refused with 400 rather than stored empty.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"content":{"type":"string"},"file":{"type":"array","items":{"type":"string","format":"binary"},"description":"Stored under the account's prefix. Image parts are not stored: upstream put images in the note's markdown instead."}}}}}}}},"/notes/_/api/v1/notes/merge":{"post":{"operationId":"mergeNotes","summary":"Merge one note into another","tags":["notes"],"responses":{"200":{"description":"The target, after the merge.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"400":{"description":"Not a JSON body, the two ids are equal, or one is not a whole number.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"One or both notes are not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"description":"The two notes to merge, and whether to draw a rule between them.","content":{"application/json":{"schema":{"type":"object","properties":{"sourceNoteId":{"type":"integer","description":"The note that is absorbed and removed."},"targetNoteId":{"type":"integer","description":"The note that survives and grows."},"addSeparator":{"type":"boolean","description":"`true` joins the two bodies with a horizontal rule."}},"required":["sourceNoteId","targetNoteId"]}}}}}},"/notes/_/api/v1/notes/timeline":{"get":{"operationId":"notesTimeline","summary":"Counts of notes by year, month and day","tags":["notes"],"responses":{"200":{"description":"A nested count tree, over the account's 5000 most recent notes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Timeline"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"timezone","in":"query","description":"An IANA name. An unrecognised one falls back to UTC rather than failing.","schema":{"type":"string","default":"UTC"}}]}},"/notes/_/api/v1/notes/{id}":{"get":{"operationId":"getNote","summary":"Read one note, for the editor","tags":["notes"],"responses":{"200":{"description":"The note, its attachments and its tags.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteWithTags"}}}},"400":{"description":"The id is not a whole number.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The note is not this account's, and for this caller it does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}]},"put":{"operationId":"editNote","summary":"Edit a note, its attachments and its mood","tags":["notes"],"responses":{"200":{"description":"The note after the edit, plus its tags. Or `{success, noteDeleted}` when the edit emptied it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteWithTags"}}}},"400":{"description":"The id is not a whole number, or the body is not multipart.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The note is not this account's, and for this caller it does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer, or this deployment has no R2 bucket bound.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"`content`, `filesToDelete` (a JSON list of attachment ids), `file` parts, `update_timestamp`, and the three mood fields `isPinned`, `isFavorited`, `is_archived`. An edit that empties the note of text and files deletes it, which is upstream's rule.","content":{"multipart/form-data":{"schema":{"type":"object","properties":{"content":{"type":"string"},"filesToDelete":{"type":"string","description":"A JSON array of attachment ids, as multipart parts are strings."},"file":{"type":"array","items":{"type":"string","format":"binary"}},"update_timestamp":{"type":"string","enum":["true","false"],"description":"`false` keeps the old `updated_at`."},"isPinned":{"type":"string","enum":["true","false"]},"isFavorited":{"type":"string","enum":["true","false"]},"is_archived":{"type":"string","enum":["true","false"]}}}}}}},"delete":{"operationId":"deleteNote","summary":"Delete a note and its attachments","tags":["notes"],"responses":{"204":{"description":"Deleted. Nothing in the body."},"400":{"description":"The id is not a whole number.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The note is not this account's, and for this caller it does not exist.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}]}},"/notes/_/api/v1/notes/{id}/share":{"post":{"operationId":"shareNote","summary":"Share a note to a public address","tags":["sharing"],"responses":{"200":{"description":"The link: the same one a second share returns, and no shorter a life than before.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareLink"}}}},"400":{"description":"The id is not a whole number.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The note is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Optional; a share with no body gets the default hour.","content":{"application/json":{"schema":{"type":"object","properties":{"expirationTtl":{"type":"integer","description":"Seconds the link lives. Defaults to 3600; zero means it never lapses."}}}}}}},"delete":{"operationId":"revokeNoteShare","summary":"Revoke a note's public link","tags":["sharing"],"responses":{"200":{"description":"Revoked, whether or not a link was live.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"required":["success"]}}}},"400":{"description":"The id is not a whole number.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The note is not this account's.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The note's id, or the doc node's uuid.","schema":{"type":"string"}}]}},"/notes/_/api/v1/public/note/raw/{public_id}":{"get":{"operationId":"getPublicNoteRaw","summary":"Read a shared note's text, as plain text","tags":["public"],"responses":{"200":{"description":"The note's text, exactly as written.","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"No live share answers to this id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"public_id","in":"path","required":true,"description":"The uuid the share link carries.","schema":{"type":"string"}}],"security":[]}},"/notes/_/api/v1/public/note/{public_id}":{"get":{"operationId":"getPublicNote","summary":"Read a shared note by its public id","tags":["public"],"responses":{"200":{"description":"The shared note's text. Its attachments are not shared.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicNote"}}}},"404":{"description":"No live share answers to this id: it was never shared, it was revoked, or it lapsed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"public_id","in":"path","required":true,"description":"The uuid the share link carries. The address IS the capability: no principal is asked for.","schema":{"type":"string"}}],"security":[]}},"/notes/_/api/v1/search":{"get":{"operationId":"searchNotes","summary":"Search the account's notes","tags":["notes"],"responses":{"200":{"description":"One page of matching notes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePage"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"q","in":"query","description":"A prefix match over the note text. Under two characters the answer is empty rather than a full scan; empty falls back to the listing.","schema":{"type":"string"}},{"name":"page","in":"query","description":"1-based page of ten notes. A page below one is read as one.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"tag","in":"query","description":"Only notes carrying this tag, lowercased by the writer.","schema":{"type":"string"}},{"name":"favorites","in":"query","description":"`true` limits the answer to favorited notes.","schema":{"type":"boolean"}},{"name":"startTimestamp","in":"query","description":"Unix seconds. Used together with `endTimestamp`; alone it is ignored.","schema":{"type":"integer"}},{"name":"endTimestamp","in":"query","description":"Unix seconds, exclusive. Used together with `startTimestamp`.","schema":{"type":"integer"}}]}},"/notes/_/api/v1/settings":{"get":{"operationId":"getSettings","summary":"Read the account's settings document","tags":["settings"],"responses":{"200":{"description":"The stored document, or the defaults when the account has never saved one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Settings"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"putSettings","summary":"Replace the account's settings document","tags":["settings"],"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"400":{"description":"The body is not a JSON object.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"description":"The settings document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"The whole document. It is stored as sent, so a partial send drops the keys it omits."}}}}}},"/notes/_/api/v1/stats":{"get":{"operationId":"noteStats","summary":"Note, tag and age counts for the account","tags":["notes"],"responses":{"200":{"description":"Three counts over the account's own rows.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Stats"}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/notes/_/api/v1/tags":{"get":{"operationId":"listTags","summary":"The account's tags, most used first","tags":["notes"],"responses":{"200":{"description":"One page of the tags this account has used, most used first, then by name.","headers":{"x-next-cursor":{"description":"Present when another page exists: the `cursor` to send for it. Absent on the last page.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TagUsage"}}}}},"401":{"description":"No door session, or a session the door could not name.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The door did not answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"parameters":[{"name":"limit","in":"query","description":"How many rows this page carries. Default 100 for tags, 200 for the docs tree; the most a page returns is 500.","schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"cursor","in":"query","description":"The `x-next-cursor` value from the previous page. A keyset cursor, not an offset, so a write during the walk neither repeats nor skips a row.","schema":{"type":"string"}}]}},"/pact/_/api/v1/pacts":{"post":{"operationId":"postPacts","summary":"Write a pact and freeze its first version, once per idempotency key.","tags":["pacts"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"The caller's own retry handle. The same key written twice returns the pact the first call made and writes nothing; the body's `idempotencyKey` is the same field for a client that cannot set a header.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"The terms, the parties, and the id the pact is written under. The account and the creator come from the credential, never from the body.","content":{"application/json":{"schema":{"type":"object","required":["kind","bodyMd","parties"],"properties":{"pactId":{"type":"string","pattern":"^[a-z0-9][a-z0-9._:-]{0,127}$","description":"The id this pact is written under, for a consumer republishing a pact it already holds. Omitted, the service mints a uuid. It is the id every later call names, and the last segment of the address a link signs at."},"idempotencyKey":{"type":"string","minLength":1,"description":"The same retry handle as the `Idempotency-Key` header, for a client that cannot set one. The header wins when both are sent. Absent is a new pact, and the same key twice replays the first answer."},"kind":{"type":"string","minLength":1,"description":"The consumer's own word for what sort of agreement this is. Stored as given, never interpreted."},"bodyMd":{"type":"string","minLength":1,"description":"The terms, as markdown, frozen as version 1 in the same write as the header."},"profileRef":{"type":"string","description":"The jurisdiction and form profile, or `none`. Defaults to `none`."},"timeZone":{"type":"string","description":"The IANA zone this pact's dates are read in, such as `Asia/Bangkok`. Defaults to `UTC`."},"parties":{"type":"array","minItems":1,"description":"The people and companies on this pact. At least one row must carry the `counterparty` role: a promise with nobody on the other side of it is not an agreement, and that is the seat a link is sent to.","items":{"type":"object","required":["role","displayName"],"properties":{"role":{"type":"string","enum":["issuer","counterparty","arbiter","witness","notary","custodian"],"description":"Which side of this pact the party is on. A witness or a notary is a role here and not yet a mechanism."},"partyId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,63}$","description":"This pact's own name for the party, which an obligation's `obligorPartyId` cites. Defaults to the role, then `role_2` for a second party of the same role."},"principalId":{"type":["string","null"],"description":"The door's principal, when this party has an account. Absent or null is a counterparty named before they join."},"displayName":{"type":"string","minLength":1,"description":"The only name a document can print for someone who has not signed in."},"capacity":{"type":"string","enum":["person","company"],"description":"How a document prints the party. Defaults to `person`."}}}},"obligations":{"type":"array","description":"What each party promised. Absent is a pact with no obligations, which is a legal pact.","items":{"type":"object","required":["obligorPartyId","action"],"properties":{"obligationId":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,63}$","description":"This version's own name for the obligation. Defaults to `ob_<position>`, counting from 1."},"obligorPartyId":{"type":"string","description":"The `partyId` of the party that owes it, not a principal."},"action":{"type":"string","minLength":1,"description":"The thing promised."},"dueAt":{"type":"integer","description":"Epoch milliseconds, after 1970. Required when `amountCents` is sent: an amount with no date is a debt nobody can be late on."},"amountCents":{"type":"integer","minimum":0,"description":"Money in minor units. Sent with `currency` and `dueAt`, and never with one of them alone."},"currency":{"type":"string","pattern":"^[A-Za-z]{3}$","description":"The three-letter code, stored upper-case. Sent with `amountCents` and never without it."},"condition":{"type":["string","null"],"description":"What makes it due, for an obligation dated by an event rather than a day."},"evidenceRequired":{"type":["string","null"],"description":"What the obligor must show for it to read as done."}}}}}}}}},"responses":{"200":{"description":"A replay: this account already held a pact written under this idempotency key, and it is answered as it stands. Nothing was written.","content":{"application/json":{"schema":{"type":"object","required":["created","pact","version","parties","obligations"],"properties":{"created":{"type":"boolean","enum":[false]},"pact":{"type":"object"},"version":{"type":"object"},"parties":{"type":"array","items":{"type":"object"}},"obligations":{"type":"array","items":{"type":"object"}}}}}}},"201":{"description":"A pact this account did not hold. Its version 1 is frozen in the same batch as the header, so a 201 always names text that exists.","content":{"application/json":{"schema":{"type":"object","required":["created","pact","version","parties","obligations"],"properties":{"created":{"type":"boolean","enum":[true]},"pact":{"type":"object"},"version":{"type":"object"},"parties":{"type":"array","items":{"type":"object"}},"obligations":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"The request is missing a field a pact cannot be written without: the terms, a counterparty, a non-empty due date on a money obligation, or an id this service serves. The sentence names which.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Nobody was named: no session cookie and no key. A pact with no account is a row no one owns, so nothing is written.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/pact/_/api/v1/pacts/{id}":{"get":{"operationId":"getPactsById","summary":"One pact at its current version: the terms, the parties, and that version's obligations.","tags":["pacts"],"parameters":[{"name":"id","in":"path","description":"the pact to read, as `pact.id` spells it","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The pact as it now stands. `current_version` is derived from the highest version present, so it is the text a reader would sign next.","content":{"application/json":{"schema":{"type":"object","required":["current_version","pact","version","parties","obligations"],"properties":{"current_version":{"type":"integer","minimum":1},"pact":{"type":"object"},"version":{"type":"object"},"parties":{"type":"array","items":{"type":"object"}},"obligations":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Nobody was named, so there is no account whose pacts could be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The door named a person it could not place in an account, or this key is not granted this app. Nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No pact in this account holds that id. Another account's pact reads the same way on purpose: a pact this account cannot read is a pact it must not learn exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so the credential could not be checked and nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/pact/_/api/v1/pacts/{id}/evidence":{"get":{"operationId":"getPactsByIdEvidence","summary":"Everything a stranger needs to check one pact, and the sentences that say what it does not prove.","tags":["evidence"],"parameters":[{"name":"id","in":"path","description":"the pact to prove, as `pact.id` spells it","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Every version with its hash, the parties, the obligations, the signatures with their tiers, the renditions with their digests, the one history, and the tier the weakest current signature supports.","content":{"application/json":{"schema":{"type":"object","required":["schema","pact","current_version","versions","signatures","renditions","events","tier","claims"],"properties":{"schema":{"type":"string","enum":["pact.evidence/1"]},"pact":{"type":"object"},"current_version":{"type":"integer","minimum":1},"versions":{"type":"array","items":{"type":"object"}},"signatures":{"type":"array","items":{"type":"object"}},"renditions":{"type":"array","items":{"type":"object"}},"events":{"type":"array","items":{"type":"object"}},"tier":{"type":"string","enum":["L0","L1","L2","L3","L4","L5"]},"claims":{"type":"object"}}}}}},"401":{"description":"Nobody was named, so there is no account whose bundle could be read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The door named a person it could not place in an account, or this key is not granted this app. Nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No pact in this account holds that id. An empty bundle would read as \"nothing was agreed\", which is a worse answer than \"not here\".","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its ceiling of 120 requests in the current minute; `retry-after` says when to try again.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so the credential could not be checked and nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/pact/_/api/v1/pacts/{id}/invite":{"post":{"operationId":"postPactsByIdInvite","summary":"Mint one link that lets the person it is sent to read this pact and sign it, with no account.","tags":["pacts","links"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"description":"The seat the link signs. No body at all is the common call, and it means the counterparty's seat.","content":{"application/json":{"schema":{"type":"object","properties":{"partyId":{"type":"string","description":"The `partyId` of the seat this link signs, when the pact carries more than one. Omitted, the counterparty's seat is used. The issuer's seat is refused: a link signs the side that did not write the terms."}}}}}},"responses":{"201":{"description":"A link naming the seat it signs. The token in it exists in this response and nowhere else: it is stored only as a sha256, so a link cannot be re-read or re-sent later.","content":{"application/json":{"schema":{"type":"object","required":["url","partyId","expiresAt"],"properties":{"url":{"type":"string","description":"The address to send. It carries the token."},"partyId":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No pact with that id for this caller, or no seat a link may sign: the issuer's seat is refused, and a pact with no counterparty has nobody to send a link to.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/pact/_/api/v1/pacts/{id}/sign":{"post":{"operationId":"postPactsByIdSign","summary":"Sign the version you were shown, as the party you are.","tags":["signatures"],"parameters":[{"name":"id","in":"path","description":"the pact to sign, as `pact.id` spells it","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"One signature row and its evidence record. The row is keyed by pact, version and party, so a second mark from one party on one version is a 409 rather than a second row.","content":{"application/json":{"schema":{"type":"object","required":["signature","evidence"],"properties":{"signature":{"type":"object"},"evidence":{"type":"object"}}}}}},"400":{"description":"The request does not carry what a signature must record: a version, one of the five methods, the consent text's version, a user agent, and a session to have come through.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Nobody was named, or the caller is a key. A signature is made by a person, and a key names an account (FR-021).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The caller is not a party to this pact, or is the party that wrote it: nobody signs the terms they wrote.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No pact in this account holds that id.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"That party already signed that version, or the version the caller was shown is no longer current. Both are a conflict with a row that exists, and neither writes anything.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/paste/_/api/v1/guest/paste":{"post":{"operationId":"createGuestPaste","summary":"POST /_/api/v1/guest/paste","tags":["guest"],"responses":{"201":{"description":"The paste was written for a reserved guest principal. Nothing lists it and nothing signs in to it: the delete token is the only handle on the row.","content":{"application/json":{"schema":{"type":"object","required":["id","deletetoken"],"properties":{"id":{"type":"string","description":"The 128-bit id the paste's link carries, and the capability that reads it."},"deletetoken":{"type":"string","description":"The token that deletes the paste. Answered once, at creation, and never again."}}}}}},"400":{"description":"A lifetime longer than this path allows, which is 24 hours.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This deployment does not take pastes from strangers: the operation exists and is not open here.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than this deployment accepts.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not `application/json`.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"More pastes than this address may make in a minute. Signing in lifts the limit, because an account is an identity and an address is not.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/paste/_/api/v1/me":{"get":{"operationId":"getPasteMe","summary":"GET /_/api/v1/me","tags":["me"],"responses":{"200":{"description":"Who is asking, as the door sees them. A stranger is a 200 with `signedIn: false` and a sign-in URL, not a 401: the question has an answer whoever asks it.","content":{"application/json":{"schema":{"type":"object","required":["signedIn","accountId","rung","email","tenant","signin"],"properties":{"signedIn":{"type":"boolean"},"accountId":{"type":["string","null"],"description":"The account behind the session, or null for a stranger."},"rung":{"type":"integer","description":"The door's ladder: 0 anonymous, 1 verified, 2 account, 3 workspace, 4 organisation."},"email":{"type":["string","null"]},"tenant":{"type":["string","null"]},"signin":{"type":["string","null"],"description":"Where a stranger signs in; null when signed in."}}}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door (auth.zpe.vc) did not answer, so whether this visitor is signed in could not be checked. Retry once the door is back; nothing about the visitor is implied by this answer.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/paste/_/api/v1/paste":{"post":{"operationId":"postPaste","summary":"POST /_/api/v1/paste","tags":["paste"],"responses":{"201":{"description":"The paste was written. The id is the capability the link carries; the deletion token is answered once and never again.","content":{"application/json":{"schema":{"type":"object","required":["id","deletetoken"],"properties":{"id":{"type":"string","description":"The 128-bit id the paste's link carries, and the capability that reads it."},"deletetoken":{"type":"string","description":"The token that deletes the paste. Answered once, at creation, and never again."}}}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than this deployment accepts (`MAX_BODY_BYTES`, 4 MiB by default).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"The body is not `application/json`, and this path reads nothing else.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/paste/_/api/v1/paste/{id}":{"get":{"operationId":"getPasteById","summary":"GET /_/api/v1/paste/:id","tags":["paste"],"parameters":[{"name":"id","in":"path","description":"The paste's id · the capability the link carries.","required":true,"schema":{"type":"string"}},{"name":"meta","in":"query","description":"Answer only the paste's head (its metadata), never the ciphertext · what a preview does with an id it merely learned.","required":false,"schema":{"type":"string","enum":["1"]}}],"responses":{"200":{"description":"The paste in the format its client wrote · or only its head when it is one-time, because an ambient GET (a prefetch, a link scanner) must not spend a paste it merely learned the id of.","content":{"application/json":{"schema":{"type":"object","required":["v","wk","adata","meta"],"properties":{"v":{"type":"integer","description":"The format version the client wrote."},"ct":{"type":"string","description":"The ciphertext, base64. Absent on a one-time paste's head."},"wk":{"type":"string","description":"The content key, wrapped to the key in the link's fragment."},"adata":{"type":"object","description":"The associated data the client authenticated: `fmt` and `bar`."},"meta":{"type":"object","required":["expire","created"],"properties":{"expire":{"type":"string","description":"The lifetime the writer chose."},"created":{"type":"integer","description":"When the paste was written, in seconds since the epoch."}}}}}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"A one-time paste whose id has already been read, or that has expired: the id is the capability, and it is spent. Nothing else will ever answer at this address.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"delete":{"operationId":"deletePasteById","summary":"DELETE /_/api/v1/paste/:id","tags":["paste"],"parameters":[{"name":"id","in":"path","description":"The paste's id.","required":true,"schema":{"type":"string"}},{"name":"X-Delete-Token","in":"header","description":"The deletion token the create answered. Absent, the owner's own session is what allows the delete, and another account's row is refused.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The row is gone. The deletion token or the owner's session allowed it, and the answer names what it removed.","content":{"application/json":{"schema":{"type":"object","required":["status","id"],"properties":{"status":{"type":"string","enum":["deleted"]},"id":{"type":"string"}}}}}},"400":{"description":"Neither `X-Delete-Token` nor a signed-in session was presented, so there is nothing that could authorise the delete.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The delete token does not match this row (`wrong-delete-token`), or the row belongs to another account. The paste was not deleted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"That id is not a paste this app has: never issued, already deleted, or expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door (auth.zpe.vc) did not answer, so the owner's session could not be read. Retry once the door is back; the row is untouched.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/paste/_/api/v1/paste/{id}/consume":{"post":{"operationId":"postPasteByIdConsume","summary":"POST /_/api/v1/paste/:id/consume","tags":["paste"],"parameters":[{"name":"id","in":"path","description":"The one-time paste's id.","required":true,"schema":{"type":"string"}},{"name":"X-Burn-Intent","in":"header","description":"Must be `consume`. Required so a cross-site page cannot burn a paste by linking to it · the answer to a request without it is `400 bad-request`.","required":true,"schema":{"type":"string","enum":["consume"]}}],"responses":{"200":{"description":"The ciphertext, to the one caller whose statement won the row. `X-Burn-Intent: consume` is required, so a cross-site page cannot burn a paste by linking to it.","content":{"application/json":{"schema":{"type":"object","required":["v","wk","adata","meta"],"properties":{"v":{"type":"integer","description":"The format version the client wrote."},"ct":{"type":"string","description":"The ciphertext, base64. Absent on a one-time paste's head."},"wk":{"type":"string","description":"The content key, wrapped to the key in the link's fragment."},"adata":{"type":"object","description":"The associated data the client authenticated: `fmt` and `bar`."},"meta":{"type":"object","required":["expire","created"],"properties":{"expire":{"type":"string","description":"The lifetime the writer chose."},"created":{"type":"integer","description":"When the paste was written, in seconds since the epoch."}}}}}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"410":{"description":"Someone else consumed it first, or it has expired. The row is gone and no second read exists.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/paste/_/api/v1/pastes":{"get":{"operationId":"getPastes","summary":"GET /_/api/v1/pastes","tags":["pastes"],"responses":{"200":{"description":"The pastes filed under the caller's account, newest first, each with the state it is in.","content":{"application/json":{"schema":{"type":"object","required":["pastes"],"properties":{"pastes":{"type":"array","items":{"type":"object","required":["id","burn","created","expires","consumedAt","state"],"properties":{"id":{"type":"string"},"burn":{"type":"boolean","description":"One-time: its first read spends it."},"created":{"type":"integer"},"expires":{"type":["integer","null"],"description":"Null when the paste never expires."},"consumedAt":{"type":["integer","null"]},"state":{"type":"string","enum":["live","burned","expired"]}}}}}}}}},"401":{"description":"No credential was presented, or the one presented does not open this account. Sign in at the door, or mint a key for it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The credential is real and may not read this app: a key not granted it, or a session the door could not map to an account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"The key has spent its request ceiling. `retry-after` says when to try again; signing in raises the ceiling, because an account is an identity and a clock is not.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door (auth.zpe.vc) did not answer, so the credential could not be checked. Retry once the door is back: a 503 withholds the verdict rather than refusing the caller.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/receptionist/_/api/v1/inbox":{"get":{"operationId":"getInbox","summary":"This account's conversations, newest first.","tags":["inbox"],"responses":{"200":{"description":"This account's conversations, newest first, each with its message count.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The key is missing, malformed, unknown, revoked, expired, or names another account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"That key is not granted this app, or the door named no account to scope the request to.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its request ceiling for the current minute; retry after the break.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The key could not be checked, or this deployment could not read its store, so nothing was answered.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/receptionist/_/api/v1/inbox/{id}":{"get":{"operationId":"getInboxById","summary":"One conversation and its messages.","tags":["inbox"],"parameters":[{"name":"id","in":"path","description":"The conversation's id, as `/api/inbox` answered it.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"One conversation and its messages, oldest first, up to the handler's cap.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"The key is missing, malformed, unknown, revoked, expired, or names another account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"That key is not granted this app, or the door named no account to scope the request to.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"That conversation is not in this account.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its request ceiling for the current minute; retry after the break.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The key could not be checked, or this deployment could not read its store, so nothing was answered.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/reports/_/api/v1/reports":{"get":{"operationId":"getReportIndex","summary":"Every report this caller may read, as a keyset page","tags":["reports"],"parameters":[{"name":"limit","in":"query","description":"How many rows to answer, at most 50.","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":20}},{"name":"cursor","in":"query","description":"The last slug of the previous page, from x-next-cursor.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The caller's reports, name order, with the cursor for the next page in the body.","content":{"application/json":{"schema":{"type":"object","required":["reports","next_cursor"],"properties":{"reports":{"type":"array","items":{"type":"object","required":["slug","label","published"],"properties":{"slug":{"type":"string"},"label":{"type":"string"},"published":{"type":"boolean"},"built_at":{"type":"string"},"parts":{"type":"integer"},"sections":{"type":"integer"},"tables":{"type":"integer"}}}},"next_cursor":{"type":["string","null"]}}}}}},"401":{"description":"No session cookie, and no key the door knows.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A key was presented that is not granted this app for a read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"The door is rate-limiting this caller's key, so the read was not attempted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so nothing could be named and nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/reports/_/api/v1/reports/{slug}":{"get":{"operationId":"getReportBySlug","summary":"One report's build facts, to the caller that owns the slug","tags":["reports"],"parameters":[{"name":"slug","in":"path","description":"The client slug, as it appears in the report's address.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"What the publisher wrote: the build time, the page's size, its counts, and the loop's last pass.","content":{"application/json":{"schema":{"type":"object","required":["slug","published"],"properties":{"slug":{"type":"string"},"label":{"type":"string"},"published":{"type":"boolean"},"built_at":{"type":"string"},"bytes":{"type":"integer"},"parts":{"type":"integer"},"sections":{"type":"integer"},"tables":{"type":"integer"},"loop":{"type":"object","properties":{"last_pass":{"type":"string"},"outcome":{"type":"string"},"last_publish":{"type":"string"}}}}}}}},"401":{"description":"No session cookie, and no key the door knows.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"A key was presented that is not granted this app for a read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No report at this slug for this caller. A slug that belongs to another account answers the same.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"The door is rate-limiting this caller's key, so the read was not attempted.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so nothing could be named and nothing was read.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/store/_/api/v1/orders":{"get":{"operationId":"getOrders","summary":"GET /_/api/v1/orders","tags":["orders"],"responses":{"200":{"description":"This account's own orders, newest first, at most 200 of them","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No key and no door session was sent, or the key sent does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The key is not granted this app for reads, or the door named no account to scope the read to","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"This key has spent its request ceiling; the refusal says when to retry","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The door did not answer, so the credential could not be checked","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearerAuth":[]}]}},"/tv/_/api/v1/blocks":{"get":{"operationId":"getBlocks","summary":"A blocklist for one brand, in the dialect a blocker reads.","tags":["blocks"],"parameters":[{"name":"brand","in":"query","description":"One of samsung, lg, vizio, roku, amazon, hisense, sony, philips, xiaomi, panasonic. Default samsung.","required":false,"schema":{"type":"string"}},{"name":"dialect","in":"query","description":"One of pihole, adguard, dnsmasq, hosts, nextdns, unbound. Default pihole.","required":false,"schema":{"type":"string"}},{"name":"tier","in":"query","description":"acr (the fingerprinting endpoints only) or default (acr, ads and telemetry).","required":false,"schema":{"type":"string"}},{"name":"shared","in":"query","description":"0 drops the cross-platform measurement trackers. Default includes them. Anything but 0 or 1 is refused.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The list as text, ready to subscribe to.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"brand, dialect, tier or shared is a value this route does not understand.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/tv/_/api/v1/brands":{"get":{"operationId":"getBrands","summary":"The platform profiles, including whether the on-device opt-out was found to work.","tags":["brands"],"responses":{"200":{"description":"One profile per brand.","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/tv/_/api/v1/catalog":{"get":{"operationId":"getCatalog","summary":"Every rule with its class, confidence, sources and evidence, plus the artifact sha.","tags":["catalog"],"responses":{"200":{"description":"The catalogue.","content":{"application/json":{"schema":{"type":"object"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/tv/_/api/v1/dataset":{"get":{"operationId":"getDataset","summary":"The anonymised aggregates, floored so no single submission can publish a host.","tags":["dataset"],"parameters":[{"name":"weeks","in":"query","description":"How far back to aggregate, an integer 1 to 52. Default 8. Anything else is refused.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Aggregates, the window, and how many hosts were withheld by the floor.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"weeks is not an integer from 1 to 52.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"This app failed while answering","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"This deployment has no dataset database, so there is nothing to aggregate.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"postDataset","summary":"Submit one anonymous observation: a rotating pseudonym, the platform, and hostnames with bucketed counts.","tags":["dataset"],"requestBody":{"required":true,"description":"Never carries an address, an account, a model string or a free-text field.","content":{"application/json":{"schema":{"type":"object","required":["v","brand","os","week","pseudonym","hosts"],"properties":{"v":{"type":"integer","enum":[1]},"brand":{"type":"string","enum":["samsung","lg","vizio","roku","amazon","hisense","sony","philips","xiaomi","panasonic"]},"os":{"type":"string","enum":["tizen","webos","smartcast","roku-os","fire-os","vidaa","google-tv","titan-os","mi-tv","viera","unknown"]},"week":{"type":"string","description":"ISO week, this week or last, e.g. 2026-W44."},"pseudonym":{"type":"string","description":"32 hex characters, derived per week and thrown away."},"hosts":{"type":"array","maxItems":200,"items":{"type":"object","required":["host","n"],"properties":{"host":{"type":"string"},"n":{"type":"string","enum":["1","2-5","6-20","21-99","100+"]}}}}}}}}},"responses":{"202":{"description":"Stored. Whether it reaches the published dataset depends on other machines.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The request could not be read","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"The credential is missing or does not open this account","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"This account may not do that","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Nothing is here","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64KB.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"More than 20 submissions an hour from one address.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/u/{slug}":{"get":{"operationId":"getPublishedPage","summary":"The published page","description":"**Public.** The product the intake exists for: the page a drop becomes after the press and the publish, at `zpe.vc/u/<name>`. No key and no session — this is the address the business prints. It answers at that mount only: measured 2026-11-06, `zpe.vc/u/<name>` is 200 and `zpe.vc/links/u/<name>` is 404, which is why this path item carries its own `servers`. A name that holds no live page answers the signpost's own `404`, never somebody else's page.","tags":["pages"],"x-audience":"public","parameters":[{"name":"slug","in":"path","required":true,"description":"The page's published name, which is also its address: `zpe.vc/u/<name>`.","schema":{"type":"string","minLength":1,"maxLength":64}}],"responses":{"200":{"description":"The published page.","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"No live page holds that name.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/u/{slug}/card.vcf":{"get":{"operationId":"getPublishedPageCard","summary":"The published page's card, as a vCard","description":"**Public.** The page's 'save my card' act at `zpe.vc/u/<name>/card.vcf`: the page's own name, headline, address and contact as `text/vcard`, built from the same row the page reads. The read records its row before it answers, so the operator can see the card was taken.","tags":["pages"],"x-audience":"public","parameters":[{"name":"slug","in":"path","required":true,"description":"The page's published name, which is also its address: `zpe.vc/u/<name>`.","schema":{"type":"string","minLength":1,"maxLength":64}}],"responses":{"200":{"description":"The card.","content":{"text/vcard":{"schema":{"type":"string"}}}},"404":{"description":"No live page holds that name.","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/_/api/v1/home":{"get":{"operationId":"getHome","tags":["home"],"summary":"The home page model","description":"Every count the page prints, the addresses it links, and the list of apps a stranger can open.","responses":{"200":{"description":"The model.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Home"},"example":{"as_of":"2026-01-01","sources":{"apps":"0000000","apis":"0000000"},"counts":{"open":2,"account":1,"contracts":2,"operations":7,"total":5,"wip":2},"links":{"wall":"https://zpe.vc/start","docs":"https://zpe.vc/api/docs","spec":"https://zpe.vc/_/api/v1/openapi.json","door":"https://auth.zpe.vc/","offer":"https://zpe.vc/services/","status":"https://status.zpe.vc"},"open":[{"id":"an-app","label":"An app you can open","href":"https://an-app.example","api":"https://an-app.example/_/api/v1"},{"id":"a-page","label":"A page with no API","href":"https://a-page.example","api":null}]}}}}}}},"/_/api/v1/openapi.json":{"get":{"operationId":"getHomeContract","tags":["home"],"summary":"This document","responses":{"200":{"description":"The OpenAPI 3.1 document for this surface.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"x-public":{"published":["zpe-booking","zpe-comments","zpe-crew","zpe-feedback","zpe-files","zpe-forms","zpe-links","zpe-mind-mapper","zpe-newsletter","zpe-notes","zpe-pact","zpe-paste","zpe-receptionist","zpe-reports","zpe-scraper","zpe-stats","zpe-status","zpe-store","zpe-tv","zpe-vc"],"withheld":[{"app":"zpe-chat","lifecycle":"wip","reason":"lifecycle is wip"},{"app":"zpe-repo-access","lifecycle":"wip","reason":"lifecycle is wip"},{"app":"zpe-reviews","lifecycle":"wip","reason":"lifecycle is wip"},{"app":"zpe-screens","lifecycle":"wip","reason":"lifecycle is wip"},{"app":"zpe-uw","lifecycle":"beta","reason":"an account surface: the Upwork send queue"}]}}