{"openapi":"3.0.3","info":{"title":"RelayHost API","version":"1.0.0","description":"Agent-friendly publishing API for safe files and static sites."},"servers":[{"url":"https://host.anri.ai/api/v1"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key (rh_...)"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"}},"required":["error","message"]},"ManifestFile":{"type":"object","properties":{"path":{"type":"string"},"size":{"type":"integer"},"mime":{"type":"string"}},"required":["path","size"]},"Upload":{"type":"object","properties":{"upload_id":{"type":"string"},"kind":{"type":"string","enum":["artifact","site"]},"state":{"type":"string"},"persistent":{"type":"boolean"},"hostname":{"type":"string"},"public_url":{"type":"string"},"total_bytes":{"type":"integer"},"file_count":{"type":"integer"},"scan":{"type":"string"},"expires_at":{"type":"string","nullable":true},"published_at":{"type":"string","nullable":true}}}}},"paths":{"/capabilities":{"get":{"summary":"Service capabilities, file types, plans, limits","responses":{"200":{"description":"OK"}}}},"/uploads":{"post":{"summary":"Create an upload manifest (anonymous allowed when enabled)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["artifact","site"]},"persistent":{"type":"boolean"},"title":{"type":"string"},"files":{"type":"array","items":{"$ref":"#/components/schemas/ManifestFile"}}},"required":["kind","files"]}}}},"responses":{"201":{"description":"Manifest + presigned upload URLs"},"409":{"description":"persistent_quota_exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"upload_too_large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List uploads","security":[{"apiKey":[]}],"responses":{"200":{"description":"OK"}}}},"/uploads/{id}":{"get":{"summary":"Upload status","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete upload","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"}}}},"/uploads/{id}/finalize":{"post":{"summary":"Validate, scan and publish","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Published (includes public_url, expires_at, claim_url for anonymous)"},"400":{"description":"validation failure","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"invalid_state / persistent_quota_exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/{id}/files/{path}":{"put":{"summary":"Proxied file upload (when presigned URLs are unavailable)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"path","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stored"}}}},"/uploads/claim":{"post":{"summary":"Claim an anonymous upload with a one-time token","security":[{"apiKey":[]}],"responses":{"200":{"description":"Claimed"},"400":{"description":"claim_invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/subdomains/{handle}/availability":{"get":{"summary":"Stable subdomain availability","parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ handle, available, reason?, suggestions? }"}}}},"/subdomains":{"get":{"summary":"List stable subdomains","security":[{"apiKey":[]}],"responses":{"200":{"description":"OK"}}},"post":{"summary":"Reserve a stable subdomain (plan-gated)","security":[{"apiKey":[]}],"responses":{"201":{"description":"Reserved"},"409":{"description":"handle_unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/subdomains/{handle}":{"patch":{"summary":"Point a subdomain at an upload","security":[{"apiKey":[]}],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}},"delete":{"summary":"Release a subdomain","security":[{"apiKey":[]}],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/domains":{"get":{"summary":"List custom domains","security":[{"apiKey":[]}],"responses":{"200":{"description":"OK"}}},"post":{"summary":"Connect a custom domain (returns DNS instructions)","security":[{"apiKey":[]}],"responses":{"201":{"description":"Created (status pending_dns)"}}}},"/domains/{id}":{"get":{"summary":"Domain status + DNS instructions","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}},"patch":{"summary":"Map an upload to a verified domain","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"400":{"description":"domain_verification_pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove domain","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/domains/{id}/verify":{"post":{"summary":"Run DNS verification","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated status"}}}},"/keys":{"get":{"summary":"List API keys (hashes only server-side)","security":[{"apiKey":[]}],"responses":{"200":{"description":"OK"}}},"post":{"summary":"Create an API key (session auth only; full key shown once)","responses":{"201":{"description":"Created"}}}},"/keys/{id}":{"delete":{"summary":"Revoke an API key","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked"}}}}}}