{"openapi":"3.1.0","info":{"title":"Schaffa API","version":"0.13.0","description":"Stable HTTP API for publishing standalone HTML pages and files. New HTML pages may be published anonymously for one hour; permanent publishing, page updates, and files use bearer tokens.","license":{"name":"MIT","identifier":"MIT"}},"servers":[{"url":"https://schaffa.dev"}],"security":[],"tags":[{"name":"Pages","description":"Publish and read standalone HTML pages."},{"name":"Files","description":"Publish and read immutable files."},{"name":"Guides","description":"Record, edit, and publish incremental guides."}],"paths":{"/api/capabilities":{"get":{"summary":"Check token validity and publishing permissions without publishing content","description":"Omit the bearer token to check anonymous access. A supplied token must be valid and not revoked. Authentication updates the token's last-used timestamp. Permissions reflect token scope, account approval, and instance settings; content validation, quotas, and scanning still apply at upload time.","security":[{},{"bearerAuth":[]}],"responses":{"200":{"description":"Current publishing permissions","content":{"application/json":{"schema":{"type":"object","required":["version","authenticated","capabilities"],"properties":{"version":{"const":1},"authenticated":{"type":"boolean"},"capabilities":{"type":"object","required":["staticHtml","interactiveHtml","fileUploads","guides"],"properties":{"staticHtml":{"type":"object","required":["allowed","reason"],"properties":{"allowed":{"type":"boolean"},"reason":{"enum":[null,"writes_locked","token_required","upload_scope_required","interactive_scope_required","interactive_disabled","interactive_not_allowed"]}}},"interactiveHtml":{"type":"object","required":["allowed","reason"],"properties":{"allowed":{"type":"boolean"},"reason":{"enum":[null,"writes_locked","token_required","upload_scope_required","interactive_scope_required","interactive_disabled","interactive_not_allowed"]}}},"fileUploads":{"type":"object","required":["allowed","reason"],"properties":{"allowed":{"type":"boolean"},"reason":{"enum":[null,"writes_locked","token_required","upload_scope_required","interactive_scope_required","interactive_disabled","interactive_not_allowed"]}}},"guides":{"type":"object","required":["allowed","reason"],"properties":{"allowed":{"type":"boolean"},"reason":{"enum":[null,"writes_locked","token_required","upload_scope_required","interactive_scope_required","interactive_disabled","interactive_not_allowed"]}}}}}}}}}},"401":{"description":"Invalid or revoked bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/pages":{"post":{"tags":["Pages"],"summary":"Publish a new page","description":"Without a bearer token, creates an anonymous static page visible for one hour. Interactive pages require an explicitly trusted user and an interactive-only token.","operationId":"createPage","security":[{},{"bearerAuth":[]}],"parameters":[{"name":"title","in":"query","required":false,"description":"Optional display title. On updates, omitting it retains the existing title.","schema":{"type":"string","maxLength":160}},{"name":"type","in":"query","required":false,"description":"Page execution model. Interactive requires an interactive token and instance/user approval; the value is immutable after creation.","schema":{"enum":["static","interactive"],"default":"static"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","additionalProperties":false,"properties":{"html":{"type":"string","format":"binary","contentMediaType":"text/html"}},"required":["html"]}}}},"responses":{"202":{"description":"Page accepted for virus scanning","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagePublication"}}}},"401":{"description":"Invalid upload token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTML exceeds the configured size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Rejected HTML or invalid multipart input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Upload rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Publishing is locked or virus scanning is not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/pages/{slug}":{"put":{"tags":["Pages"],"summary":"Publish a new version of an existing page","description":"Publishes the next immutable version of an existing permanent page owned by the bearer token. New pages are created only through POST /api/pages with a server-generated random identifier.","operationId":"updatePage","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"title","in":"query","required":false,"description":"Optional display title. On updates, omitting it retains the existing title.","schema":{"type":"string","maxLength":160}},{"name":"type","in":"query","required":false,"description":"Page execution model. Interactive requires an interactive token and instance/user approval; the value is immutable after creation.","schema":{"enum":["static","interactive"],"default":"static"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","additionalProperties":false,"properties":{"html":{"type":"string","format":"binary","contentMediaType":"text/html"}},"required":["html"]}}}},"responses":{"202":{"description":"Page version accepted for virus scanning","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagePublication"}}}},"401":{"description":"Missing or invalid upload token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token does not own this page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Anonymous pages cannot be updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"HTML exceeds the configured size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Rejected HTML, slug, title, or multipart input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Upload rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Publishing is locked or virus scanning is not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Pages"],"summary":"Delete a page","description":"Permanently deletes the page and all of its versions. Only the token that created the page or an admin token may delete it.","operationId":"deletePage","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"204":{"description":"Page deleted"},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token does not own this page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid slug","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/p/{slug}":{"get":{"tags":["Pages"],"summary":"Read the latest page version","operationId":"getLatestPage","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Published HTML","content":{"text/html":{"schema":{"type":"string"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Page not found or no longer public","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/p/{slug}/{version}":{"get":{"tags":["Pages"],"summary":"Read a specific page version","operationId":"getPageVersion","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Published HTML","content":{"text/html":{"schema":{"type":"string"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Page not found or no longer public","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/p/{slug}/status":{"get":{"tags":["Pages"],"summary":"Read the latest page scan status","operationId":"getPageStatus","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Scan completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}},"202":{"description":"Scan pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}},"404":{"description":"Publication not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Upload rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}}}}},"/p/{slug}/{version}/status":{"get":{"tags":["Pages"],"summary":"Read a page version scan status","operationId":"getPageVersionStatus","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Scan completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}},"202":{"description":"Scan pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}},"404":{"description":"Publication not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Upload rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}}}}},"/p/{slug}/raw":{"get":{"tags":["Pages"],"summary":"Read the latest page source","operationId":"getLatestPageSource","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Published HTML","content":{"text/html":{"schema":{"type":"string"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Page not found or no longer public","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/p/{slug}/{version}/raw":{"get":{"tags":["Pages"],"summary":"Read a specific page version source","operationId":"getPageVersionSource","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Published HTML","content":{"text/html":{"schema":{"type":"string"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Page not found or no longer public","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/p/{slug}/run":{"get":{"tags":["Pages"],"summary":"Run the latest interactive page in its sandbox","operationId":"runLatestInteractivePage","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Published HTML","content":{"text/html":{"schema":{"type":"string"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Page not found or no longer public","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/p/{slug}/{version}/run":{"get":{"tags":["Pages"],"summary":"Run a specific interactive page version in its sandbox","operationId":"runInteractivePageVersion","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Published HTML","content":{"text/html":{"schema":{"type":"string"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Page not found or no longer public","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/api/files":{"post":{"tags":["Files"],"summary":"Upload a file","operationId":"createFile","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","additionalProperties":false,"properties":{"file":{"type":"string","format":"binary","contentMediaType":"application/octet-stream"}},"required":["file"]}}}},"responses":{"202":{"description":"File accepted for virus scanning","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FilePublication"}}}},"401":{"description":"Missing or invalid upload token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"File exceeds the configured size limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid multipart input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Upload rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Publishing is locked or virus scanning is not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/files/{id}":{"delete":{"tags":["Files"],"summary":"Delete a file","description":"Permanently deletes an uploaded file. Accepts the file ID or its public filename. Only the token that uploaded the file or an admin token may delete it.","operationId":"deleteFile","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"File deleted"},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token does not own this file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/f/{filename}":{"get":{"tags":["Files"],"summary":"Read a published file","operationId":"getFile","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string"}},{"name":"Range","in":"header","required":false,"schema":{"type":"string","pattern":"^bytes=(?:[0-9]+-[0-9]*|-[0-9]+)$"}}],"responses":{"200":{"description":"Published file","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"202":{"description":"Virus scan in progress","content":{"text/html":{"schema":{"type":"string"}}}},"206":{"description":"Requested byte range","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"416":{"description":"Invalid or unsatisfiable range","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Upload rejected by the virus scanner","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/f/{filename}/status":{"get":{"tags":["Files"],"summary":"Read a file scan status","operationId":"getFileStatus","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Scan completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}},"202":{"description":"Scan pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}},"404":{"description":"Publication not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Upload rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScanStatus"}}}}}}},"/api/guides":{"post":{"tags":["Guides"],"summary":"Start a guide recording","operationId":"createGuide","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","maxLength":160},"description":{"type":"string","maxLength":4000},"targetUrl":{"type":"string","format":"uri","maxLength":2000,"description":"Destination linked from the published guide."},"language":{"type":"string"}}}}}},"responses":{"201":{"description":"Guide recording started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}},"401":{"description":"Missing or invalid upload token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/guides/{slug}":{"get":{"tags":["Guides"],"summary":"Read the current owner view","operationId":"getGuideDraft","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Current guide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}},"403":{"description":"The token does not own this guide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Guides"],"summary":"Edit guide metadata","operationId":"updateGuide","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":["string","null"]},"targetUrl":{"type":["string","null"],"format":"uri"},"language":{"type":"string"},"videoUrl":{"type":["string","null"],"description":"Same-instance video uploaded by the guide owner and marked clean by scanning. Null removes the video from the new revision."}}}}}},"security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}}],"responses":{"200":{"description":"Updated guide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}},"409":{"description":"Edit revision conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Guides"],"summary":"Delete a guide","description":"Permanently deletes the guide, all of its revisions, and its screenshots. Only the token that created the guide or an admin token may delete it.","operationId":"deleteGuide","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"204":{"description":"Guide deleted"},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token does not own this guide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Guide not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/guides/{slug}/steps":{"post":{"tags":["Guides"],"summary":"Append a guide step","operationId":"createGuideStep","description":"Accepts JSON without a screenshot or multipart with a JSON field named step before one screenshot file.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":8,"maxLength":128}}],"responses":{"201":{"description":"Step appended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}},"409":{"description":"Edit or idempotency conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/guides/{slug}/steps/{stepId}":{"patch":{"tags":["Guides"],"summary":"Edit a guide step","operationId":"updateGuideStep","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}}],"responses":{"200":{"description":"Step updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}}}},"delete":{"tags":["Guides"],"summary":"Delete a guide step","operationId":"deleteGuideStep","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}}],"responses":{"200":{"description":"Step deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}}}}},"/api/guides/{slug}/steps/{stepId}/screenshot":{"put":{"tags":["Guides"],"summary":"Replace a step screenshot","operationId":"replaceGuideScreenshot","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","additionalProperties":false,"properties":{"screenshot":{"type":"string","format":"binary","contentMediaType":"image/*"}},"required":["screenshot"]}}}},"responses":{"200":{"description":"Screenshot replaced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}}}}},"/api/guides/{slug}/order":{"put":{"tags":["Guides"],"summary":"Reorder every guide step","operationId":"reorderGuideSteps","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}}],"responses":{"200":{"description":"Guide reordered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}}}}},"/api/guides/{slug}/finish":{"post":{"tags":["Guides"],"summary":"Finish recording and publish an immutable revision","operationId":"finishGuide","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"If-Match","in":"header","required":true,"description":"Current guide editRevision, optionally quoted.","schema":{"type":"string"}}],"responses":{"201":{"description":"Published guide revision","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"Preflight failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/g/{slug}":{"get":{"tags":["Guides"],"summary":"Read the latest published guide","operationId":"getPublishedGuide","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Published script-free guide HTML","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"No published guide revision","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/g/{slug}/{version}":{"get":{"tags":["Guides"],"summary":"Read an immutable guide revision","operationId":"getGuideRevision","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}},{"name":"version","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Published script-free guide HTML","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"No published guide revision","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/g/{slug}.json":{"get":{"tags":["Guides"],"summary":"Download guide JSON","operationId":"getGuideJson","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Published guide","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guide"}}}}}}},"/g/{slug}.md":{"get":{"tags":["Guides"],"summary":"Download guide Markdown","operationId":"getGuideMarkdown","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"}}],"responses":{"200":{"description":"Published guide Markdown","content":{"text/markdown":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sfa_ token"}},"schemas":{"Error":{"type":"object","additionalProperties":false,"properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error","message"]},"PagePublication":{"type":"object","additionalProperties":false,"properties":{"slug":{"type":"string","pattern":"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"},"title":{"type":["string","null"],"maxLength":160},"kind":{"enum":["static","interactive"]},"version":{"type":"integer","minimum":1},"bytes":{"type":"integer","minimum":1},"sha256":{"type":"string","pattern":"^[a-f0-9]{64}$"},"publicUrl":{"type":"string","format":"uri"},"versionUrl":{"type":"string","format":"uri"},"rawUrl":{"type":"string","format":"uri"},"versionRawUrl":{"type":"string","format":"uri"},"expiresAt":{"type":["string","null"],"description":"UTC SQLite timestamp for anonymous pages, otherwise null."},"purgeAt":{"type":["string","null"],"description":"UTC SQLite timestamp for anonymous-page retention, otherwise null."},"scanStatus":{"const":"pending"},"statusUrl":{"type":"string","format":"uri"}},"required":["slug","title","kind","version","bytes","sha256","publicUrl","versionUrl","rawUrl","versionRawUrl","expiresAt","purgeAt","scanStatus","statusUrl"]},"FilePublication":{"type":"object","additionalProperties":false,"properties":{"id":{"type":"string"},"filename":{"type":"string"},"mediaType":{"type":"string"},"bytes":{"type":["integer","null"],"minimum":0,"description":"Null while an image is awaiting conversion."},"sha256":{"type":["string","null"],"pattern":"^[a-f0-9]{64}$","description":"Null while an image is awaiting conversion."},"publicUrl":{"type":"string","format":"uri"},"scanStatus":{"const":"pending"},"statusUrl":{"type":"string","format":"uri"}},"required":["id","filename","mediaType","bytes","sha256","publicUrl","scanStatus","statusUrl"]},"ScanStatus":{"type":"object","additionalProperties":false,"properties":{"scanStatus":{"enum":["pending","scanning","clean","rejected"]},"message":{"type":"string"}},"required":["scanStatus"]},"Guide":{"type":"object","additionalProperties":false,"properties":{"schemaVersion":{"const":1},"videoUrl":{"type":["string","null"],"format":"uri"},"slug":{"type":"string","pattern":"^[a-z2-7]{12}$"},"title":{"type":"string"},"targetUrl":{"type":["string","null"],"format":"uri"},"status":{"enum":["recording","published"]},"revision":{"type":"integer","minimum":0},"editRevision":{"type":"integer","minimum":1},"publicUrl":{"type":"string","format":"uri"},"apiUrl":{"type":"string","format":"uri"},"steps":{"type":"array","items":{"type":"object"}}},"required":["schemaVersion","slug","title","status","revision","editRevision","publicUrl","apiUrl","steps"]}}}}