{"openapi":"3.1.0","info":{"title":"htmlbin API","version":"1.2.0","summary":"Agent-first HTML hosting. Drop self-contained HTML, get a public URL.","description":"All endpoints under /api/. Auth uses a Bearer token minted via the /api/auth/start device-code flow. All field names use snake_case. All 4xx/5xx responses share the same Error shape (see components.schemas.Error).","contact":{"url":"https://htmlbin.dev"},"license":{"name":"MIT"}},"servers":[{"url":"https://htmlbin.dev"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"hb_<base62>"}},"schemas":{"Drop":{"type":"object","required":["slug","title","url","raw_url","metadata","created_at"],"properties":{"slug":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"url":{"type":"string","format":"uri"},"raw_url":{"type":"string","format":"uri"},"locked":{"type":"boolean"},"latest_version":{"type":"integer"},"view_count":{"type":"integer"},"metadata":{"$ref":"#/components/schemas/Metadata"},"created_at":{"type":"integer","description":"unix ms"},"updated_at":{"type":"integer","description":"unix ms"}}},"Metadata":{"type":"object","description":"Owner-side tag bag — free-form string→string map for whatever an agent wants to track across its drops. Examples of what owners tag with: {repo, pr} for stable CI preview URLs, {session_id, kind} for per-conversation artifacts, {client, project, status} for portfolio work, {kind, topic} for buckets. Max 10 keys, 64 chars per key, 256 chars per value. Filterable on GET /api/drops via repeated `metadata.<key>=<value>` query params (AND across pairs). Not exposed on the public viewer.","additionalProperties":{"type":"string","maxLength":256},"maxProperties":10},"VersionListItem":{"type":"object","required":["version","size_bytes","created_at"],"properties":{"version":{"type":"integer"},"size_bytes":{"type":"integer"},"has_context":{"type":"boolean"},"created_at":{"type":"integer"}}},"Version":{"type":"object","required":["slug","version","size_bytes","created_at"],"properties":{"slug":{"type":"string"},"version":{"type":"integer"},"size_bytes":{"type":"integer"},"context":{"type":"string","nullable":true,"description":"Optional reasoning trace recorded at this version"},"created_at":{"type":"integer"},"is_latest":{"type":"boolean"}}},"Error":{"type":"object","description":"Canonical error envelope. Every 4xx/5xx response uses this shape; switch on `error.code` (not `error.message`).","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable identifier, e.g. 'html_too_large', 'not_found', 'rate_limited'."},"message":{"type":"string","description":"Human-readable summary."},"details":{"type":"object","description":"Optional context (limits, fields, retry_after_seconds, etc.)."}}}}},"Pagination":{"type":"object","required":["page","page_size","total_items","total_pages","sort_by","sort_order"],"properties":{"page":{"type":"integer"},"page_size":{"type":"integer"},"total_items":{"type":"integer"},"total_pages":{"type":"integer"},"sort_by":{"type":"string","enum":["created_at","updated_at","view_count"]},"sort_order":{"type":"string","enum":["asc","desc"]}}}}},"paths":{"/api/onboard":{"get":{"summary":"Agent onboarding instructions","description":"Returns the full agent protocol. Markdown by default; pass Accept: application/json for JSON wrapper.","responses":{"200":{"description":"Onboarding text","content":{"text/markdown":{"schema":{"type":"string"}},"application/json":{"schema":{"type":"object","properties":{"instructions":{"type":"string"},"public_url":{"type":"string"}}}}}}}}},"/api/auth/start":{"post":{"summary":"Begin device-code flow","description":"Returns a short verification code, a verification_url, and a poll_token. The agent prints the URL to the human; the human opens it and signs in with GitHub (read:user scope). htmlbin binds one account per GitHub identity, so the token returned via /api/auth/poll attaches to that user.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"}}}}}},"responses":{"200":{"description":"Code + poll token","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"verification_url":{"type":"string","format":"uri"},"poll_token":{"type":"string"},"expires_in":{"type":"integer"},"poll_interval":{"type":"integer"}}}}}}}}},"/api/auth/poll":{"get":{"summary":"Poll for verified token","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Status (and api_token if verified, exactly once)","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","verified","claimed","expired"]},"api_token":{"type":"string"},"user_id":{"type":"string"}}}}}}}}},"/api/me":{"get":{"summary":"Caller identity, drop count, and current token info","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Caller info","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string"},"created_at":{"type":"integer","nullable":true},"drop_count":{"type":"integer"},"token":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"label":{"type":"string","nullable":true},"created_at":{"type":"integer"},"last_used_at":{"type":"integer","nullable":true}}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tokens":{"get":{"summary":"List active tokens for the caller","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Array of tokens (no plaintext)","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}}}},"/api/tokens/{id}":{"delete":{"summary":"Revoke a token by its 12-hex id","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{12}$"}}],"responses":{"204":{"description":"Revoked"},"404":{"description":"No matching token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/drops":{"get":{"summary":"List your drops (paginated)","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"sortBy","in":"query","schema":{"type":"string","enum":["created_at","updated_at","view_count"],"default":"created_at"}},{"name":"sortOrder","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"metadata.<key>","in":"query","description":"Repeatable. Filter drops by metadata tag — AND across pairs. Key must match /^[a-z0-9_]([a-z0-9_.-]*[a-z0-9_])?$/i, ≤64 chars. Example: ?metadata.repo=u/r&metadata.pr=42","schema":{"type":"string","maxLength":256}}],"responses":{"200":{"description":"Paginated drops","content":{"application/json":{"schema":{"type":"object","required":["data","pagination"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Drop"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}}}},"post":{"summary":"Upload a new drop (creates v1)","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["title","html"],"properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":500},"html":{"type":"string"},"passcode":{"type":"string","minLength":4,"description":"Soft share gate, not encryption"},"context":{"type":"string","description":"Optional reasoning trace (≤64KB, opt-in)"},"metadata":{"$ref":"#/components/schemas/Metadata"}}}}}},"responses":{"201":{"description":"Created — returns the full Drop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Drop"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited or quota exceeded (Retry-After header)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/drops/{slug}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get metadata for one of your drops","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Drop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Drop"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Mint a new version (html required)","description":"PUT always mints a new version. `html` is required. `title`/`description`/`metadata` may be updated alongside the new version. Use PATCH for non-html-only edits.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["html"],"properties":{"html":{"type":"string"},"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":500},"context":{"type":"string","description":"Optional reasoning trace (≤64KB, opt-in)"},"metadata":{"$ref":"#/components/schemas/Metadata"}}}}}},"responses":{"200":{"description":"Updated — returns the full Drop with bumped latest_version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Drop"}}}},"400":{"description":"Missing html / validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit, daily quota, or per-drop version cap (Retry-After header where applicable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update title / description / metadata (no new version)","description":"PATCH updates title, description, and/or metadata without minting a new version. Metadata replace semantics: omit to leave untouched, `{}` to clear, `{k:v}` to overwrite the whole map. Including `html` returns 400 metadata_only_on_patch.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":500},"metadata":{"$ref":"#/components/schemas/Metadata"}}}}}},"responses":{"200":{"description":"Updated — returns the full Drop (latest_version unchanged)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Drop"}}}},"400":{"description":"Included html or other validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete drop (all versions)","security":[{"bearerAuth":[]}],"responses":{"204":{"description":"Deleted (no body)"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/drops/{slug}/versions":{"get":{"summary":"List versions of a drop","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Array of versions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/VersionListItem"}}}}}}}},"/api/drops/{slug}/v/{n}":{"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"n","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"get":{"summary":"Get one version's metadata + context","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Version"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a single version (refused for the last remaining)","description":"Returns the updated Drop. If the deleted version was the head, latest_version is recomputed.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Deleted — returns the updated Drop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Drop"}}}},"404":{"description":"Drop or version not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"last_version_cannot_be_deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/drops/{slug}/passcode":{"post":{"summary":"Set, change, or remove the passcode (soft share gate) on a drop","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["passcode"],"properties":{"passcode":{"type":"string","description":"Empty string to remove"}}}}}},"responses":{"200":{"description":"Updated — returns the full Drop","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Drop"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/p/{slug}":{"get":{"summary":"Public viewer (HTML)","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Viewer page"},"404":{"description":"Not found"}}}},"/p/{slug}/raw":{"get":{"summary":"Raw HTML, edge-cached","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The drop, served as text/html","content":{"text/html":{"schema":{"type":"string"}}}}}}}}}