{"service":"heyadhi share","base_url":"https://share.heyadhi.com","page_url":"https://{site_id}.share.heyadhi.com/","limits":{"html_bytes":10000000,"asset_bytes":50000000,"json_body_bytes":10485760,"multipart_body_bytes":52494336,"files":1000,"totalBytes":100000000,"fileBytes":50000000,"indexBytes":10000000},"authentication":["Every write, and POST …/source and …/inspect, needs a one-use token for that exact request. heyadhi.mjs does all of this for you.","A password-protected page also needs its CURRENT viewer password, as current_password in the request body (never the URL or a header), for anything that reads or changes it: …/source, …/inspect, a publish in update or merge mode, PUT, and the asset route (a form field beside \"file\"). Checked like the viewer screen (the same per-page, per-address limit): without it 403 password_required, a wrong one 403 wrong_password, and nothing is read or changed. Every try, refused or not, and every delete of a protected page, is recorded for the owner (the machine's key, the page, the time, the address) before anything is done, else 503 pull_log_unavailable. A change checked against one password does not commit if the password changed meanwhile (403 password_required: start again).","GET /v1/auth/challenge?method=<METHOD>&target=<path and query> → {challenge_id, message, expires_at}","Sign the UTF-8 bytes of `message` with the machine's registered Ed25519 private key.","POST /v1/auth/session {challenge_id, public_key: \"ed25519:<base64>\", signature: <base64url>} → {token}","Send the request within 60 seconds with Authorization: Bearer <token>."],"routes":{"POST /v1/sites/{site_id}/publish":"Publish a folder, step 1. JSON {manifest: {path: {sha256, bytes}}, mode: \"create\"|\"update\"|\"merge\", password?, current_password?}; header Idempotency-Key for create. In update and merge, password (the NEW one) omitted or null = unchanged, nonempty = set/rotate, \"\" = remove; a protected page needs current_password (above). 200 {publish_id, missing: [sha256…]}, or {status: \"existing\"} for a create already done. missing is every file not already named by a public page or this page: those are uploaded, even if the server has them.","PUT /v1/blobs/{sha256}":"Step 2, once per missing hash: the file's bytes, header X-Publish-Id and Content-Length (50 MB max). No signature: the publish id allows exactly its missing hashes, each once, for an hour. 201.","POST /v1/sites/{site_id}/publish/{publish_id}/commit":"Step 3: switch the page to the new version. 201 created, 200 updated or unchanged; 409 blobs_missing {missing} until every blob is uploaded; 404 publish_unknown after an hour or a restart (start again: a file no public page names is uploaded again).","POST /v1/sites":"Create. JSON {site_id, html_content, password?} — site_id is the page name and address, 3–60 lowercase letters, digits and single hyphens; header Idempotency-Key (128 random bits, base64url). 201 created, 200 same request replayed, 409 name taken.","GET /v1/sites/{site_id}":"Inspect: status, protected, html_sha256, html_bytes, created_at, updated_at, files (a count), bytes, referenced assets present/missing. Signed, a public page's answer adds title and manifest: every file as [{path, sha256, bytes}], sorted by path. A protected page needs authentication, and then answers only site_id, url, status, protected, created_at and updated_at: the rest needs its password (POST …/inspect).","POST /v1/sites/{site_id}/source":"Signed. JSON body {current_password?} (a protected page's, above). The page's files as published, without the tags the server adds to the head (heyadhi.mjs share pull): one line of JSON {files: [{path, sha256, bytes}, …]}, then each file's bytes in that order. Check each file against its sha256 (index.html's is inspect's html_sha256). Never cached, never compressed.","POST /v1/sites/{site_id}/inspect":"Signed. JSON body {current_password?} (a protected page's, above): the full inspect (GET's fields plus title and manifest).","PUT /v1/sites/{site_id}":"Replace HTML. JSON {html_content, password?, current_password?}: password (the NEW one) omitted or null = unchanged, nonempty = set/rotate, \"\" = remove; a protected page needs current_password (above).","POST /v1/sites/{site_id}/assets?relative_path=<path>":"Upload one asset as multipart field \"file\"; a protected page also needs the form field current_password (above).","DELETE /v1/sites/{site_id}":"Unpublish. The name is free for a new page afterwards. Needs no password; a protected page's delete is recorded for the owner.","GET /v1/help":"This document."},"errors":{"401":"Missing or invalid machine authentication.","403":"Unsafe path, an upload this publish id does not allow, or a protected page without its current password (password_required, wrong_password).","404":"Unknown page, or a protected page without authentication.","409":"The name is already in use, or a concurrent update landed first.","411":"A blob upload without Content-Length.","413":"Over a limit: see limits. POST and PUT /v1/sites refuse a JSON body over json_body_bytes (10 MiB) before reading the HTML, and HTML over html_bytes (10 MB); the asset route refuses a multipart body over multipart_body_bytes.","422":"Invalid input.","429":"Rate limited.","503":"Temporarily unavailable: storage, the program still loading, or budget_exhausted (strangers used up the public share of the monthly storage budget). Retry after the Retry-After header; the owner's own requests keep working."}}