{"openapi":"3.0.3","info":{"title":"Public API","version":"1.0.0","description":"Versioned public API for managing domains, redirect rules, and API keys."},"servers":[{"url":"/api/v1","description":"Current version"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key in format `riq_<64 hex chars>`"},"cookieAuth":{"type":"apiKey","in":"cookie","name":"session","description":"Session cookie (dashboard use only)"}},"schemas":{"Error":{"type":"object","required":["error","message","correlationId"],"properties":{"error":{"type":"string","example":"not_found"},"message":{"type":"string"},"correlationId":{"type":"string","format":"uuid"},"field":{"type":"string"}}},"Domain":{"type":"object","properties":{"id":{"type":"string"},"hostname":{"type":"string","example":"www.example.com"},"sslStatus":{"type":"string","enum":["pending","active","error"]},"cnameActive":{"type":"boolean"},"cnameTarget":{"type":"string"},"dnsRecords":{"type":"object","properties":{"ownershipTxt":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"sslTxt":{"nullable":true,"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}},"cname":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}}}}},"ruleCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"Rule":{"type":"object","properties":{"id":{"type":"string"},"domainId":{"type":"string"},"hostname":{"type":"string"},"matchPath":{"type":"string","nullable":true},"destination":{"type":"string","format":"uri"},"redirectType":{"type":"string","enum":["301","302"]},"pathPassthrough":{"type":"boolean"},"queryPassthrough":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"ApiKeyMeta":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"revokedAt":{"type":"string","format":"date-time","nullable":true}}},"LogEntry":{"type":"object","properties":{"keyId":{"type":"string"},"keyName":{"type":"string"},"ip":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"status":{"type":"integer"},"durationMs":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"}}}}},"security":[{"bearerAuth":[]},{"cookieAuth":[]}],"paths":{"/domains":{"get":{"tags":["Domains"],"summary":"List domains","operationId":"listDomains","responses":{"200":{"description":"List of domains","content":{"application/json":{"schema":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Domains"],"summary":"Create a domain","operationId":"createDomain","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["hostname"],"properties":{"hostname":{"type":"string","maxLength":253,"example":"www.example.com"}}}}}},"responses":{"201":{"description":"Domain created","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"$ref":"#/components/schemas/Domain"}}}}}},"400":{"description":"Invalid request"},"402":{"description":"Plan limit reached"},"409":{"description":"Domain already exists"}}}},"/domains/{domainId}":{"parameters":[{"name":"domainId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Domains"],"summary":"Get a domain","operationId":"getDomain","responses":{"200":{"description":"Domain","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"$ref":"#/components/schemas/Domain"}}}}}},"404":{"description":"Not found"}}},"patch":{"tags":["Domains"],"summary":"Update a domain (no-op at MVP)","operationId":"updateDomain","requestBody":{"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Domain","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"$ref":"#/components/schemas/Domain"}}}}}},"404":{"description":"Not found"}}},"delete":{"tags":["Domains"],"summary":"Delete a domain","operationId":"deleteDomain","responses":{"204":{"description":"Deleted"},"404":{"description":"Not found"}}}},"/domains/{domainId}/status":{"parameters":[{"name":"domainId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Domains"],"summary":"Check domain SSL and CNAME status","operationId":"getDomainStatus","description":"Returns the current SSL provisioning status and whether the CNAME record is resolving correctly. Each call performs a live check — there are no webhooks. Poll this endpoint (e.g. every 10–30 seconds) until `sslStatus` is `\"active\"` and `cnameActive` is `true` before considering the domain fully live.","responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"type":"object","properties":{"sslStatus":{"type":"string","enum":["pending","active","error"]},"cnameActive":{"type":"boolean"}}}}}}}}},"/rules":{"get":{"tags":["Rules"],"summary":"List redirect rules","operationId":"listRules","parameters":[{"name":"domainId","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rules","content":{"application/json":{"schema":{"type":"object","properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/Rule"}}}}}}}}},"post":{"tags":["Rules"],"summary":"Create a redirect rule","operationId":"createRule","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domainId","destination","redirectType","pathPassthrough","queryPassthrough"],"properties":{"domainId":{"type":"string"},"matchPath":{"type":"string","nullable":true},"destination":{"type":"string","format":"uri"},"redirectType":{"type":"string","enum":["301","302"]},"pathPassthrough":{"type":"boolean"},"queryPassthrough":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Rule created","content":{"application/json":{"schema":{"type":"object","properties":{"rule":{"$ref":"#/components/schemas/Rule"}}}}}},"400":{"description":"Invalid request"},"402":{"description":"Plan limit reached"},"404":{"description":"Domain not found"}}}},"/rules/{ruleId}":{"parameters":[{"name":"ruleId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Rules"],"summary":"Get a rule","operationId":"getRule","responses":{"200":{"description":"Rule","content":{"application/json":{"schema":{"type":"object","properties":{"rule":{"$ref":"#/components/schemas/Rule"}}}}}},"404":{"description":"Not found"}}},"patch":{"tags":["Rules"],"summary":"Update a rule","operationId":"updateRule","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"destination":{"type":"string","format":"uri"},"redirectType":{"type":"string","enum":["301","302"]},"pathPassthrough":{"type":"boolean"},"queryPassthrough":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated rule"},"404":{"description":"Not found"}}},"delete":{"tags":["Rules"],"summary":"Delete a rule","operationId":"deleteRule","responses":{"204":{"description":"Deleted"},"404":{"description":"Not found"}}}},"/keys":{"get":{"tags":["API Keys"],"summary":"List API keys (cookie auth only)","operationId":"listKeys","security":[{"cookieAuth":[]}],"responses":{"200":{"description":"Keys","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyMeta"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"tags":["API Keys"],"summary":"Create an API key (cookie auth only)","operationId":"createKey","security":[{"cookieAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":64},"expiresAt":{"type":"string","format":"date-time","nullable":true}}}}}},"responses":{"201":{"description":"Key created — plaintext shown once","content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"plaintext":{"type":"string","example":"riq_a1b2c3..."},"createdAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time","nullable":true}}},"warning":{"type":"string"}}}}}},"409":{"description":"Key limit reached"}}}},"/keys/{keyId}":{"parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"delete":{"tags":["API Keys"],"summary":"Revoke an API key (cookie auth only)","operationId":"revokeKey","security":[{"cookieAuth":[]}],"responses":{"204":{"description":"Revoked"},"404":{"description":"Not found"}}}},"/logs":{"get":{"tags":["Logs"],"summary":"Query API auth log","operationId":"listLogs","parameters":[{"name":"keyId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"integer"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Log entries","content":{"application/json":{"schema":{"type":"object","properties":{"logs":{"type":"array","items":{"$ref":"#/components/schemas/LogEntry"}},"nextCursor":{"type":"string","nullable":true}}}}}}}}},"/openapi.json":{"get":{"tags":["Meta"],"summary":"OpenAPI spec","operationId":"getOpenApiSpec","security":[],"responses":{"200":{"description":"OpenAPI 3.0.3 JSON document"}}}}}}