{
  "openapi": "3.1.0",
  "info": {
    "title": "rlake API",
    "version": "1.0.0",
    "description": "Public HTML report hosting API. Browser users may sign up with any valid verified Google email. Computers can create a durable agent identity permanently linked to a user; Hermes can also use short-lived scoped workload JWTs. API limits return 429 with Retry-After. All timestamps are ISO 8601 UTC. Errors use one stable JSON envelope."
  },
  "security": [{"neonBearer": []}, {"neonSession": []}, {"agentCredential": []}, {"hermesWorkload": []}],
  "servers": [{"url": "/"}],
  "paths": {
    "/": {"get": {"security": [], "operationId": "getRoot", "summary": "Open the public site or discover agent instructions", "description": "Browser navigations receive the public rlake site. curl, common HTTP clients, explicit text/plain or text/markdown requests, and X-ReportLake-Client: agent receive the same public instructions as /llms.txt.", "responses": {"200": {"description": "HTML public site or plain-text agent instructions selected from request headers"}}}},
    "/healthz": {"get": {"security": [], "operationId": "getHealth", "summary": "Check process liveness and deployed build identity", "description": "Public probe returning only status, application version, and a non-secret build identifier.", "responses": {"200": {"$ref": "#/components/responses/Health"}}}},
    "/readyz": {"get": {"security": [], "operationId": "getReadiness", "summary": "Check database readiness and deployed build identity", "description": "Public probe returning only status, application version, and a non-secret build identifier.", "responses": {"200": {"$ref": "#/components/responses/Health"}, "503": {"$ref": "#/components/responses/Error"}}}},
    "/login": {"get": {"security": [], "operationId": "getLogin", "summary": "Open the Neon Google OAuth sign-in screen", "responses": {"200": {"description": "HTML login page", "content": {"text/html": {"schema": {"type": "string"}}}}}}},
    "/auth/session": {"post": {"security": [{"neonBearer": []}], "operationId": "createBrowserSession", "summary": "Validate a Neon access or session token and establish a secure browser cookie", "responses": {"204": {"description": "Session established"}, "401": {"$ref": "#/components/responses/Error"}, "403": {"$ref": "#/components/responses/Error"}}}},
    "/auth/logout": {"post": {"security": [], "operationId": "endBrowserSession", "summary": "Clear the browser session cookie", "responses": {"204": {"description": "Session cleared"}}}},
    "/api/v1/agents/register": {"post": {"security": [], "operationId": "registerAgent", "summary": "Create an agent identity linked to a signed-in user", "description": "Consumes a 15-minute single-use code created by a human session, then returns the bearer credential exactly once. No unlinked agent identity or anonymous upload access is created.", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentLinkInput"}}}}, "responses": {"201": {"description": "Linked agent created; response must not be cached", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentRegistration"}}}}, "400": {"$ref": "#/components/responses/Error"}, "422": {"$ref": "#/components/responses/Error"}}}},
    "/api/v1/agents/me": {"get": {"security": [{"agentCredential": []}], "operationId": "getAgent", "summary": "Inspect the current agent identity and link status", "responses": {"200": {"description": "Agent status", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentResponse"}}}}, "401": {"$ref": "#/components/responses/Error"}}}, "delete": {"security": [{"agentCredential": []}], "operationId": "revokeAgent", "summary": "Revoke the current agent credential", "description": "Soft-disables the agent account immediately; subsequent requests with the credential fail closed.", "responses": {"204": {"description": "Agent revoked"}, "401": {"$ref": "#/components/responses/Error"}}}},
    "/api/v1/agent-links": {"post": {"security": [{"neonBearer": []}, {"neonSession": []}], "operationId": "createAgentLink", "summary": "Create a 15-minute single-use agent connection code", "description": "Only a human login can issue a code. Giving it to an agent authorizes an immutable ownership link.", "responses": {"201": {"description": "Connection code created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgentLinkCode"}}}}, "401": {"$ref": "#/components/responses/Error"}, "403": {"$ref": "#/components/responses/Error"}, "422": {"$ref": "#/components/responses/Error"}}}},
    "/api/v1/reports": {
      "post": {
        "operationId": "createReport", "summary": "Create a report",
        "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}],
        "requestBody": {"required": true, "content": {
          "application/json": {"schema": {"$ref": "#/components/schemas/CreateInput"}, "example": {"title": "Weekly results", "html": "<!doctype html><h1>Results</h1>"}},
          "multipart/form-data": {"schema": {"$ref": "#/components/schemas/MultipartCreate"}, "encoding": {"file": {"contentType": "text/html"}}}
        }},
        "responses": {"201": {"$ref": "#/components/responses/ReportCreated"}, "200": {"description": "Existing report returned for an idempotent replay", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReportResponse"}}}}, "400": {"$ref": "#/components/responses/Error"}, "409": {"$ref": "#/components/responses/Error"}, "413": {"$ref": "#/components/responses/Error"}, "415": {"$ref": "#/components/responses/Error"}, "422": {"$ref": "#/components/responses/Error"}, "507": {"$ref": "#/components/responses/Error"}}
      },
      "get": {
        "operationId": "listReports", "summary": "List and search reports",
        "parameters": [
          {"in": "query", "name": "q", "schema": {"type": "string", "maxLength": 200}},
          {"in": "query", "name": "limit", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 20}},
          {"in": "query", "name": "offset", "schema": {"type": "integer", "minimum": 0, "maximum": 1000000, "default": 0}},
          {"in": "query", "name": "sort", "schema": {"type": "string", "enum": ["created_at", "updated_at", "title", "size_bytes"], "default": "created_at"}},
          {"in": "query", "name": "order", "schema": {"type": "string", "enum": ["asc", "desc"], "default": "desc"}}
        ],
        "responses": {"200": {"description": "A page of report metadata", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReportList"}}}}, "400": {"$ref": "#/components/responses/Error"}}
      }
    },
    "/api/v1/reports/bulk-delete": {
      "post": {
        "operationId": "bulkDeleteReports", "summary": "Delete up to 100 reports",
        "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkDeleteInput"}, "example": {"ids": ["abcdefghijklmnop"]}}}},
        "responses": {"200": {"description": "Deletion result", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/BulkDeleteResult"}}}}, "400": {"$ref": "#/components/responses/Error"}, "413": {"$ref": "#/components/responses/Error"}, "415": {"$ref": "#/components/responses/Error"}, "422": {"$ref": "#/components/responses/Error"}}
      }
    },
    "/api/v1/reports/{id}": {
      "parameters": [{"$ref": "#/components/parameters/Id"}],
      "get": {"operationId": "getReport", "summary": "Get report metadata and canonical URLs as JSON", "description": "Returns metadata on this API route without redirecting to the rendered viewer. Use report.view_url for a separate GET of /r/{id}/{slug}.", "parameters": [{"$ref": "#/components/parameters/IfNoneMatch"}], "responses": {"200": {"$ref": "#/components/responses/ReportOk"}, "304": {"$ref": "#/components/responses/NotModified"}, "400": {"$ref": "#/components/responses/Error"}, "404": {"$ref": "#/components/responses/Error"}}},
      "patch": {"operationId": "updateReport", "summary": "Update report content or metadata", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/UpdateInput"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/MultipartUpdate"}, "encoding": {"file": {"contentType": "text/html"}}}}}, "responses": {"200": {"$ref": "#/components/responses/ReportOk"}, "400": {"$ref": "#/components/responses/Error"}, "404": {"$ref": "#/components/responses/Error"}, "413": {"$ref": "#/components/responses/Error"}, "415": {"$ref": "#/components/responses/Error"}, "422": {"$ref": "#/components/responses/Error"}}},
      "put": {"operationId": "replaceReport", "summary": "Replace report content and optionally update metadata", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateInput"}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/MultipartCreate"}, "encoding": {"file": {"contentType": "text/html"}}}}}, "responses": {"200": {"$ref": "#/components/responses/ReportOk"}, "400": {"$ref": "#/components/responses/Error"}, "404": {"$ref": "#/components/responses/Error"}, "413": {"$ref": "#/components/responses/Error"}, "415": {"$ref": "#/components/responses/Error"}, "422": {"$ref": "#/components/responses/Error"}}},
      "delete": {"operationId": "deleteReport", "summary": "Soft-delete a report", "description": "Sets is_active=false without removing metadata or HTML. Repeating DELETE for the same stored report returns 204.", "responses": {"204": {"description": "Report is inactive; response has no body"}, "400": {"$ref": "#/components/responses/Error"}, "404": {"description": "No report row exists for this ID", "$ref": "#/components/responses/Error"}}}
    },
    "/api/v1/reports/{id}/content": {
      "parameters": [{"$ref": "#/components/parameters/Id"}],
      "get": {"operationId": "getReportContent", "summary": "Render uploaded HTML under a CSP sandbox", "parameters": [{"$ref": "#/components/parameters/IfNoneMatch"}], "responses": {"200": {"description": "Inline UTF-8 HTML with enforced sandbox and security headers", "headers": {"ETag": {"$ref": "#/components/headers/ETag"}, "Content-Security-Policy": {"schema": {"type": "string"}}, "Content-Disposition": {"schema": {"type": "string", "const": "inline"}}}, "content": {"text/html": {"schema": {"type": "string"}}}}, "304": {"$ref": "#/components/responses/NotModified"}, "400": {"$ref": "#/components/responses/Error"}, "404": {"$ref": "#/components/responses/Error"}}}
    },
    "/r/{id}/{slug}": {
      "parameters": [{"$ref": "#/components/parameters/Id"}, {"in": "path", "name": "slug", "required": true, "schema": {"type": "string"}}],
      "get": {"operationId": "viewReport", "summary": "Open the mobile sandboxed report viewer", "responses": {"200": {"description": "HTML viewer containing a sandboxed iframe", "content": {"text/html": {"schema": {"type": "string"}}}}, "400": {"$ref": "#/components/responses/Error"}, "404": {"$ref": "#/components/responses/Error"}}}
    }
  },
  "components": {
    "securitySchemes": {
      "neonBearer": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "Neon Auth access JWT with a valid email whose provider verification claim is true. Public signup is enabled by default; optional allowlists remain available for private deployments."},
      "neonSession": {"type": "apiKey", "in": "cookie", "name": "reportlake_session", "description": "Secure HttpOnly cookie containing a revocable Neon session token or short-lived access JWT."},
      "agentCredential": {"type": "http", "scheme": "bearer", "bearerFormat": "rlake agent credential", "description": "Long-lived high-entropy credential created once per local OS user. The server stores only its SHA-256 digest. Inactive agent accounts fail closed."},
      "hermesWorkload": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "Short-lived Hermes workload JWT. Exact audience must match HERMES_WORKLOAD_AUDIENCE. Required scopes: report:create for POST, report:read for GET, report:update for PATCH/PUT, and report:delete for DELETE/bulk-delete."}
    },
    "parameters": {
      "Id": {"in": "path", "name": "id", "required": true, "description": "Opaque report identifier", "schema": {"type": "string", "pattern": "^[A-Za-z0-9_-]{10,64}$"}},
      "IdempotencyKey": {"in": "header", "name": "Idempotency-Key", "description": "Makes create retries safe. Reuse with different input returns 409.", "schema": {"type": "string", "maxLength": 200}},
      "IfNoneMatch": {"in": "header", "name": "If-None-Match", "description": "Return 304 when the current strong ETag matches.", "schema": {"type": "string"}}
    },
    "schemas": {
      "MetadataInput": {"type": "object", "properties": {"title": {"type": "string", "maxLength": 200}, "description": {"type": "string", "maxLength": 2000}, "filename": {"type": "string", "maxLength": 150}}},
      "CreateInput": {"allOf": [{"$ref": "#/components/schemas/MetadataInput"}, {"type": "object", "required": ["html"], "properties": {"html": {"type": "string", "description": "Non-empty UTF-8 HTML"}}}], "unevaluatedProperties": false},
      "UpdateInput": {"allOf": [{"$ref": "#/components/schemas/MetadataInput"}, {"type": "object", "properties": {"html": {"type": "string", "description": "Non-empty UTF-8 HTML"}}}], "minProperties": 1, "unevaluatedProperties": false},
      "MultipartCreate": {"type": "object", "required": ["file"], "properties": {"file": {"type": "string", "format": "binary", "contentMediaType": "text/html", "description": "Non-empty UTF-8 HTML markup. Prefer part Content-Type text/html; a safe .html/.htm filename is accepted for clients that send a generic part type."}, "title": {"type": "string", "maxLength": 200}, "description": {"type": "string", "maxLength": 2000}}, "additionalProperties": false},
      "MultipartUpdate": {"type": "object", "properties": {"file": {"type": "string", "format": "binary", "contentMediaType": "text/html", "description": "Non-empty UTF-8 HTML markup. Prefer part Content-Type text/html; a safe .html/.htm filename is accepted for clients that send a generic part type."}, "title": {"type": "string", "maxLength": 200}, "description": {"type": "string", "maxLength": 2000}}, "minProperties": 1, "additionalProperties": false},
      "Report": {"type": "object", "required": ["id", "slug", "title", "description", "filename", "size_bytes", "sha256", "created_at", "updated_at", "view_url", "content_url"], "properties": {"id": {"type": "string"}, "slug": {"type": "string"}, "title": {"type": "string"}, "description": {"type": "string"}, "filename": {"type": "string"}, "size_bytes": {"type": "integer", "minimum": 1}, "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$", "description": "SHA-256 digest of the exact uploaded HTML bytes"}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}, "view_url": {"type": "string", "format": "uri"}, "content_url": {"type": "string", "format": "uri"}}, "additionalProperties": false},
      "ReportResponse": {"type": "object", "required": ["report"], "properties": {"report": {"$ref": "#/components/schemas/Report"}}, "additionalProperties": false},
      "Pagination": {"type": "object", "required": ["limit", "offset", "total", "next_offset"], "properties": {"limit": {"type": "integer"}, "offset": {"type": "integer"}, "total": {"type": "integer"}, "next_offset": {"type": ["integer", "null"]}}},
      "ReportList": {"type": "object", "required": ["reports", "pagination"], "properties": {"reports": {"type": "array", "items": {"$ref": "#/components/schemas/Report"}}, "pagination": {"$ref": "#/components/schemas/Pagination"}}},
      "BulkDeleteInput": {"type": "object", "required": ["ids"], "properties": {"ids": {"type": "array", "minItems": 1, "maxItems": 100, "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{10,64}$"}}}, "additionalProperties": false},
      "BulkDeleteResult": {"type": "object", "required": ["deleted_ids", "deleted_count"], "properties": {"deleted_ids": {"type": "array", "items": {"type": "string"}}, "deleted_count": {"type": "integer"}}},
      "Agent": {"type": "object", "required": ["id", "created_at", "linked", "linked_at", "active"], "properties": {"id": {"type": "string"}, "created_at": {"type": "string", "format": "date-time"}, "linked": {"type": "boolean"}, "linked_at": {"type": ["string", "null"], "format": "date-time"}, "active": {"type": "boolean"}}, "additionalProperties": false},
      "AgentResponse": {"type": "object", "required": ["agent"], "properties": {"agent": {"$ref": "#/components/schemas/Agent"}}, "additionalProperties": false},
      "AgentRegistration": {"type": "object", "required": ["agent", "credential", "credential_notice"], "properties": {"agent": {"$ref": "#/components/schemas/Agent"}, "credential": {"type": "string", "writeOnly": true}, "credential_notice": {"type": "string"}}, "additionalProperties": false},
      "AgentLinkInput": {"type": "object", "required": ["code"], "properties": {"code": {"type": "string", "pattern": "^rlk_link_[A-Za-z0-9_-]+$"}}, "additionalProperties": false},
      "AgentLinkCode": {"type": "object", "required": ["code", "created_at", "expires_at", "instruction"], "properties": {"code": {"type": "string"}, "created_at": {"type": "string", "format": "date-time"}, "expires_at": {"type": "integer", "description": "Unix timestamp"}, "instruction": {"type": "string"}}, "additionalProperties": false},
      "Health": {"type": "object", "required": ["status", "version", "build_id"], "properties": {"status": {"type": "string", "const": "ok"}, "version": {"type": "string", "description": "rlake application version"}, "build_id": {"type": "string", "description": "Sanitized deployment commit SHA or build identifier"}}, "additionalProperties": false},
      "Error": {"type": "object", "required": ["error"], "properties": {"error": {"type": "object", "required": ["code", "message", "request_id"], "properties": {"code": {"type": "string"}, "message": {"type": "string"}, "details": {}, "request_id": {"type": "string"}}}}}
    },
    "headers": {
      "ETag": {"description": "Strong entity tag for conditional retrieval", "schema": {"type": "string"}}
    },
    "responses": {
      "Health": {"description": "Healthy", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Health"}, "example": {"status": "ok", "version": "1.0.0", "build_id": "86632a8a588f759e08257629510c72470cd6d023"}}}},
      "ReportOk": {"description": "Report metadata", "headers": {"ETag": {"$ref": "#/components/headers/ETag"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReportResponse"}}}},
      "ReportCreated": {"description": "Created", "headers": {"Location": {"description": "Absolute browser viewer URL", "schema": {"type": "string", "format": "uri"}}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ReportResponse"}}}},
      "NotModified": {"description": "The current representation matches If-None-Match", "headers": {"ETag": {"$ref": "#/components/headers/ETag"}}},
      "Error": {"description": "Structured error", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}, "example": {"error": {"code": "validation_error", "message": "html or file is required", "details": null, "request_id": "09a7d42c5303b2fb"}}}}}
    }
  }
}
