{"openapi":"3.1.0","paths":{"/v1/apps/current":{"get":{"operationId":"AppsController_current","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Get the API key's app","tags":["Apps"]},"patch":{"description":"A given origin_url is validated (https, public hostname, not an IP, not an Apexly host, not a domain of another app) and probed before it is saved; the response carries origin_reachable and origin_latency_ms. 422 origin_not_allowed when it resolves to a private, loopback, link-local, CGNAT or reserved address. Test keys are sandboxed: once the app has a live domain, a test key gets 403 forbidden (use a live key or the dashboard).","operationId":"AppsController_update","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAppBody"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Update the API key's app","tags":["Apps"]}},"/v1/apps/current/signing-secret/rotate":{"post":{"description":"Issues a new whsec_ secret, returned only in this response. The previous secret keeps verifying for 24 hours (previous_valid_until). Needs a live key: test keys are sandboxed (403 forbidden).","operationId":"AppsController_rotate","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotatedSecretDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Rotate the signing secret","tags":["Apps"]}},"/v1/keys":{"get":{"description":"Never includes the full key or its hash. A test key sees only test keys.","operationId":"KeysController_list","parameters":[{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":50,"type":"integer"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyListDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"List the app's active API keys","tags":["Keys"]},"post":{"description":"Returns the full key once, in `key`. Live keys need a plan: Free promotional, any paid plan, or a card on file (402 live_keys_need_plan on plain Free); test keys are always allowed. A test key is sandboxed: it can only create test keys (403 forbidden for mode live).","operationId":"KeysController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyBody"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedApiKeyDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Create an API key","tags":["Keys"]}},"/v1/keys/{id}":{"delete":{"description":"The key stops working on every node within 5 seconds. A test key can only revoke test keys; a live key is 404 to it.","operationId":"KeysController_revoke","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Revoke an API key","tags":["Keys"]}},"/v1/domains":{"post":{"description":"201 with the new domain in pending_dns, or 200 with the existing one when the app already has this hostname (idempotent on app + hostname; an expired one is re-added: back to pending_dns with the same connect link and a fresh 72 h window). The mode comes from the key: test keys create test domains, which never contact a CA. 422 invalid_hostname, hostname_is_apexly, hostname_is_public_suffix, hostname_held, domain_limit_reached (live domains at the plan hard cap: 2× included, 3 on Free, Growth during the 14-day trial; error.status_detail lists { plan, trial, used, limit, hostname, plans[] } that fit one more. New accounts: 10 in the first 24 hours), origin_not_allowed, validation_failed (metadata over 4 KB, apex_redirect on a subdomain, hostname already in the app in the other mode). origin needs the Scale plan; other plans ignore it and the response carries warnings.","operationId":"DomainsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDomainBody"}}}},"responses":{"200":{"description":"The hostname already exists in this app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainWithWarningsDto"}}}},"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainWithWarningsDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Add a domain","tags":["Domains"]},"get":{"description":"Newest first (created_at desc), cursor-paginated: pass next_cursor as cursor. Filters: status, hostname (prefix), metadata.<key>=<value> (string equality on a top-level key, up to 5).","operationId":"DomainsController_list","parameters":[{"name":"status","required":false,"in":"query","schema":{"type":"string","enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]}},{"name":"hostname","required":false,"in":"query","description":"Hostname prefix","schema":{"maxLength":253,"pattern":"^[A-Za-z0-9.-]*$","type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":50,"type":"integer"}},{"name":"cursor","required":false,"in":"query","description":"next_cursor of the previous page","schema":{"maxLength":200,"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainListDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"List domains","tags":["Domains"]}},"/v1/domains/{id}":{"get":{"description":"Another app’s domain, or a deleted one, is 404.","operationId":"DomainsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Get a domain","tags":["Domains"]},"patch":{"description":"origin (Scale plan; null removes the override; other plans ignore it with warnings), metadata (replaces the object; null removes it), apex_redirect (apex domains only).","operationId":"DomainsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDomainBody"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainWithWarningsDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Update a domain","tags":["Domains"]},"delete":{"description":"Emits domain.deleted; routing is dropped within 5 seconds; the hostname can be added again at once.","operationId":"DomainsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"204":{"description":""},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Delete a domain","tags":["Domains"]}},"/v1/domains/{id}/verify":{"post":{"description":"Queues a DNS check right away instead of waiting for the next scheduled one; on a cert_failed domain it retries issuance (after re-checking CAA when that was the reason). One per 15 seconds per domain (429 rate_limited with Retry-After). 422 validation_failed when there is nothing to check (issuing, held, expired, or a test domain).","operationId":"DomainsController_verify","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"202":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"queued":{"type":"boolean","enum":[true]}}}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Check a domain now","tags":["Domains"]}},"/v1/events":{"get":{"description":"Newest first (created_at desc), cursor-paginated. Filter by domain_id and type. Each event carries a delivery summary: delivered, pending (queued or retrying, with next_attempt_at), failed (retries exhausted) or none (no webhook URL was set).","operationId":"EventsController_list","parameters":[{"name":"domain_id","required":false,"in":"query","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","schema":{"type":"string","enum":["domain.created","domain.verified","domain.active","domain.dns_broken","domain.cert_failed","domain.certificate_renewed","domain.deleted","domain.held","domain.released"]}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":50,"type":"integer"}},{"name":"cursor","required":false,"in":"query","description":"next_cursor of the previous page","schema":{"maxLength":200,"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventListDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"List webhook events","tags":["Events"]}},"/v1/events/{id}":{"get":{"description":"The event with every delivery attempt, newest first (at most 50).","operationId":"EventsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventDetailDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Get an event","tags":["Events"]}},"/v1/events/{id}/redeliver":{"post":{"description":"Sends the event's original body again now, freshly signed, to the app's current webhook URL, and returns the new attempt (a new delivery row; the receiver's answer is in status_code). A 2xx also takes the event off the automatic retry schedule. 10 per minute per app; 422 validation_failed when the app has no webhook URL.","operationId":"EventsController_redeliver","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryDto"}}}},"default":{"description":"Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"security":[{"apiKey":[]}],"summary":"Redeliver an event","tags":["Events"]}}},"info":{"title":"Apexly API","description":"Custom domains as a service. JSON only; all timestamps are RFC 3339 UTC.","version":"1.0.0","contact":{}},"tags":[{"name":"Domains","description":"Add your customers' hostnames, read the record each one needs and where it stands, re-check it, remove it."},{"name":"Apps","description":"Your app: the origin every customer domain forwards to, white-label, webhooks and the signing secret."},{"name":"Keys","description":"API keys. A live key manages live domains; a test key manages test domains, which never contact a certificate authority."},{"name":"Events","description":"Everything that happened to your domains, newest first, with webhook deliveries and redelivery."},{"name":"Errors","description":"Every error has the same envelope, and every code has a page that explains it and how to fix it."}],"servers":[{"url":"https://api.apexly.dev"}],"components":{"securitySchemes":{"apiKey":{"scheme":"bearer","bearerFormat":"API key","type":"http","description":"An app API key: sk_live_… or sk_test_…."}},"schemas":{"AppDto":{"type":"object","properties":{"id":{"type":"string","description":"app_…"},"name":{"type":"string"},"origin_url":{"description":"Where every customer domain forwards, e.g. https://app.hireflow.com","type":["string","null"]},"origin_reachable":{"type":"boolean","description":"Result of the latest probe"},"origin_latency_ms":{"description":"Latency of the latest successful probe","type":["integer","null"],"minimum":-9007199254740991,"maximum":9007199254740991},"origin_checked_at":{"description":"When the origin was last probed (RFC 3339)","type":["string","null"]},"origin_status_reason":{"description":"Why the latest probe failed: origin_not_allowed | origin_dns_failed | origin_timeout | origin_tls_error | origin_connection_failed | origin_http_error | origin_too_many_redirects | origin_bad_redirect","type":["string","null"]},"origin_unreachable":{"type":"boolean","description":"True after 3 consecutive failed probes (re-probed every 15 minutes)"},"webhook_url":{"type":["string","null"]},"whitelabel_host":{"type":["string","null"]},"whitelabel_verified":{"type":"boolean"},"whitelabel_target":{"type":"string","description":"What whitelabel_host must CNAME to, e.g. edge.apexly.dev"},"whitelabel_status_reason":{"description":"whitelabel_unverified while whitelabel_host is set but its CNAME chain does not reach whitelabel_target","type":["string","null"],"enum":["whitelabel_unverified"]},"whitelabel_found":{"description":"The CNAME last seen at whitelabel_host while unverified (null = no record)","type":["string","null"]},"whitelabel_checked_at":{"description":"When whitelabel_host was last checked (every 6 hours once verified)","type":["string","null"]},"whitelabel_grace_ends_at":{"description":"Set when a verified host broke: domains routed through it keep serving until then, then go dns_broken","type":["string","null"]},"apex_redirect_default":{"type":"string","enum":["none","www"]},"created_at":{"type":"string"}},"required":["id","name","origin_url","origin_reachable","origin_latency_ms","origin_checked_at","origin_status_reason","origin_unreachable","webhook_url","whitelabel_host","whitelabel_verified","whitelabel_target","whitelabel_status_reason","whitelabel_found","whitelabel_checked_at","whitelabel_grace_ends_at","apex_redirect_default","created_at"]},"UpdateAppBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"origin_url":{"description":"https, scheme and host only; probed before it is saved","type":"string","maxLength":2048},"webhook_url":{"description":"https; stored, deliveries start in a later release","type":["string","null"],"maxLength":2048},"whitelabel_host":{"description":"e.g. domains.hireflow.com (Growth and above, or trial); verified separately; null turns white-label off","type":["string","null"],"maxLength":253},"apex_redirect_default":{"type":"string","enum":["none","www"]}}},"RotatedSecretDto":{"type":"object","properties":{"signing_secret":{"type":"string","description":"The new whsec_… secret. Returned only in this response."},"previous_valid_until":{"type":"string","description":"The previous secret keeps verifying until then (24 h, RFC 3339)"}},"required":["signing_secret","previous_valid_until"]},"ApiKeyListDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"key_…"},"name":{"type":"string"},"mode":{"type":"string","enum":["live","test"]},"prefix":{"type":"string","description":"First 12 characters, e.g. sk_live_k7Qm"},"last4":{"description":"Last 4 characters, for a masked display like sk_live_••••••••3f9a","type":["string","null"]},"created_at":{"type":"string"},"last_used_at":{"description":"Updated at most once a minute","type":["string","null"]}},"required":["id","name","mode","prefix","last4","created_at","last_used_at"]}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]},"CreateApiKeyBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"mode":{"type":"string","enum":["live","test"],"description":"live keys need a card on file; test keys are always allowed"}},"required":["name","mode"]},"CreatedApiKeyDto":{"type":"object","properties":{"id":{"type":"string","description":"key_…"},"name":{"type":"string"},"mode":{"type":"string","enum":["live","test"]},"prefix":{"type":"string","description":"First 12 characters, e.g. sk_live_k7Qm"},"last4":{"description":"Last 4 characters, for a masked display like sk_live_••••••••3f9a","type":["string","null"]},"created_at":{"type":"string"},"last_used_at":{"description":"Updated at most once a minute","type":["string","null"]},"key":{"type":"string","description":"The full key. Returned only in this response; store it in a secrets manager."}},"required":["id","name","mode","prefix","last4","created_at","last_used_at","key"]},"CreateDomainBody":{"type":"object","properties":{"hostname":{"type":"string","maxLength":1024,"description":"The customer's hostname, e.g. careers.acme.com (Unicode is encoded)"},"origin":{"type":["string","null"],"maxLength":2048,"description":"Per-domain origin override (Scale and Enterprise plans; other plans: ignored with a warning)"},"metadata":{"type":["object","null"],"propertyNames":{"type":"string"},"additionalProperties":{},"description":"Any JSON object, at most 4 KB; returned on every domain object and event"},"apex_redirect":{"description":"Apex domains only: also serve www.<apex> and redirect it (default: the app's apex_redirect_default)","type":"string","enum":["none","www"]}},"required":["hostname"]},"DomainWithWarningsDto":{"type":"object","properties":{"id":{"type":"string","description":"dom_…"},"hostname":{"type":"string","description":"Lowercase ASCII (punycode)"},"status":{"type":"string","enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},"status_reason":{"description":"Machine code when status is not active, e.g. dns_record_mismatch","type":["string","null"]},"status_detail":{"description":"{ expected, found, hint }","type":["object","null"],"propertyNames":{"type":"string"},"additionalProperties":{}},"is_apex":{"type":"boolean"},"mode":{"type":"string","enum":["live","test"],"description":"From the key that created it; test domains never contact a CA"},"dns":{"type":"object","properties":{"cname":{"description":"Subdomains: the one record to add","type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]},"apex":{"description":"Apex domains: A (and AAAA) records plus the _apexly TXT record","type":"object","properties":{"a":{"type":"array","items":{"type":"string"}},"aaaa":{"type":"array","items":{"type":"string"}},"txt":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]}},"required":["a","aaaa","txt"]}},"description":"Record names are relative to the registrable domain (e.g. \"careers\" in acme.com)"},"origin":{"description":"Origin override, or null for the app origin","type":["string","null"]},"apex_redirect":{"type":"string","enum":["none","www"]},"connect_url":{"type":"string","description":"The page the customer opens to connect the domain"},"certificate":{"type":["object","null"],"properties":{"issuer":{"type":"string"},"not_before":{"type":"string"},"not_after":{"type":"string"},"renews_at":{"type":"string"}}},"metadata":{"type":["object","null"],"propertyNames":{"type":"string"},"additionalProperties":{}},"created_at":{"type":"string"},"updated_at":{"type":"string"},"last_checked_at":{"type":["string","null"]},"next_check_at":{"type":["string","null"]},"warnings":{"description":"Present when part of the request was ignored, e.g. origin on a plan without per-domain origins","type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}}},"required":["id","hostname","status","status_reason","status_detail","is_apex","mode","dns","origin","apex_redirect","connect_url","certificate","metadata","created_at","updated_at","last_checked_at","next_check_at"]},"DomainListDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"dom_…"},"hostname":{"type":"string","description":"Lowercase ASCII (punycode)"},"status":{"type":"string","enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},"status_reason":{"description":"Machine code when status is not active, e.g. dns_record_mismatch","type":["string","null"]},"status_detail":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"{ expected, found, hint }"},"is_apex":{"type":"boolean"},"mode":{"type":"string","enum":["live","test"],"description":"From the key that created it; test domains never contact a CA"},"dns":{"type":"object","properties":{"cname":{"description":"Subdomains: the one record to add","type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]},"apex":{"description":"Apex domains: A (and AAAA) records plus the _apexly TXT record","type":"object","properties":{"a":{"type":"array","items":{"type":"string"}},"aaaa":{"type":"array","items":{"type":"string"}},"txt":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]}},"required":["a","aaaa","txt"]}},"description":"Record names are relative to the registrable domain (e.g. \"careers\" in acme.com)"},"origin":{"description":"Origin override, or null for the app origin","type":["string","null"]},"apex_redirect":{"type":"string","enum":["none","www"]},"connect_url":{"type":"string","description":"The page the customer opens to connect the domain"},"certificate":{"anyOf":[{"type":"object","properties":{"issuer":{"type":"string"},"not_before":{"type":"string"},"not_after":{"type":"string"},"renews_at":{"type":"string"}},"required":["issuer","not_before","not_after","renews_at"]},{"type":"null"}]},"metadata":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"created_at":{"type":"string"},"updated_at":{"type":"string"},"last_checked_at":{"type":["string","null"]},"next_check_at":{"type":["string","null"]}},"required":["id","hostname","status","status_reason","status_detail","is_apex","mode","dns","origin","apex_redirect","connect_url","certificate","metadata","created_at","updated_at","last_checked_at","next_check_at"]}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]},"DomainDto":{"type":"object","properties":{"id":{"type":"string","description":"dom_…"},"hostname":{"type":"string","description":"Lowercase ASCII (punycode)"},"status":{"type":"string","enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},"status_reason":{"description":"Machine code when status is not active, e.g. dns_record_mismatch","type":["string","null"]},"status_detail":{"description":"{ expected, found, hint }","type":["object","null"],"propertyNames":{"type":"string"},"additionalProperties":{}},"is_apex":{"type":"boolean"},"mode":{"type":"string","enum":["live","test"],"description":"From the key that created it; test domains never contact a CA"},"dns":{"type":"object","properties":{"cname":{"description":"Subdomains: the one record to add","type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]},"apex":{"description":"Apex domains: A (and AAAA) records plus the _apexly TXT record","type":"object","properties":{"a":{"type":"array","items":{"type":"string"}},"aaaa":{"type":"array","items":{"type":"string"}},"txt":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"string"}},"required":["name","value"]}},"required":["a","aaaa","txt"]}},"description":"Record names are relative to the registrable domain (e.g. \"careers\" in acme.com)"},"origin":{"description":"Origin override, or null for the app origin","type":["string","null"]},"apex_redirect":{"type":"string","enum":["none","www"]},"connect_url":{"type":"string","description":"The page the customer opens to connect the domain"},"certificate":{"type":["object","null"],"properties":{"issuer":{"type":"string"},"not_before":{"type":"string"},"not_after":{"type":"string"},"renews_at":{"type":"string"}}},"metadata":{"type":["object","null"],"propertyNames":{"type":"string"},"additionalProperties":{}},"created_at":{"type":"string"},"updated_at":{"type":"string"},"last_checked_at":{"type":["string","null"]},"next_check_at":{"type":["string","null"]}},"required":["id","hostname","status","status_reason","status_detail","is_apex","mode","dns","origin","apex_redirect","connect_url","certificate","metadata","created_at","updated_at","last_checked_at","next_check_at"]},"UpdateDomainBody":{"type":"object","properties":{"origin":{"description":"Per-domain origin override (Scale and Enterprise plans; other plans: ignored with a warning)","type":["string","null"],"maxLength":2048},"metadata":{"description":"Any JSON object, at most 4 KB; returned on every domain object and event","type":["object","null"],"propertyNames":{"type":"string"},"additionalProperties":{}},"apex_redirect":{"description":"Apex domains only","type":"string","enum":["none","www"]}}},"EventListDto":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"evt_… (the Apexly-Event-Id header)"},"type":{"type":"string","enum":["domain.created","domain.verified","domain.active","domain.dns_broken","domain.cert_failed","domain.certificate_renewed","domain.deleted","domain.held","domain.released"]},"domain_id":{"type":"string"},"hostname":{"type":"string"},"previous_status":{"anyOf":[{"type":"string","enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},{"type":"null"}]},"status":{"anyOf":[{"type":"string","enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},{"type":"null"}],"description":"The domain status the event moved to"},"created_at":{"type":"string"},"delivery":{"type":"object","properties":{"status":{"type":"string","enum":["delivered","pending","failed","none"],"description":"delivered: an attempt got a 2xx. pending: queued or retrying. failed: every attempt failed and retries are over. none: no webhook URL was set."},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"WebhookDelivery rows, redeliveries included"},"last_status_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"last_error":{"description":"url_not_allowed | dns_failed | timeout | tls_error | connection_failed","type":["string","null"]},"last_attempt_at":{"type":["string","null"]},"next_attempt_at":{"description":"Next automatic attempt while pending","type":["string","null"]}},"required":["status","attempts","last_status_code","last_error","last_attempt_at","next_attempt_at"]}},"required":["id","type","domain_id","hostname","previous_status","status","created_at","delivery"]}},"next_cursor":{"type":["string","null"]}},"required":["data","next_cursor"]},"EventDetailDto":{"type":"object","properties":{"id":{"type":"string","description":"evt_… (the Apexly-Event-Id header)"},"type":{"type":"string","enum":["domain.created","domain.verified","domain.active","domain.dns_broken","domain.cert_failed","domain.certificate_renewed","domain.deleted","domain.held","domain.released"]},"domain_id":{"type":"string"},"hostname":{"type":"string"},"previous_status":{"type":["string","null"],"enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},"status":{"description":"The domain status the event moved to","type":["string","null"],"enum":["pending_dns","verifying","issuing","active","dns_broken","cert_failed","expired","held","deleted"]},"created_at":{"type":"string"},"delivery":{"type":"object","properties":{"status":{"type":"string","enum":["delivered","pending","failed","none"],"description":"delivered: an attempt got a 2xx. pending: queued or retrying. failed: every attempt failed and retries are over. none: no webhook URL was set."},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"WebhookDelivery rows, redeliveries included"},"last_status_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"last_error":{"description":"url_not_allowed | dns_failed | timeout | tls_error | connection_failed","type":["string","null"]},"last_attempt_at":{"type":["string","null"]},"next_attempt_at":{"description":"Next automatic attempt while pending","type":["string","null"]}}},"deliveries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"dlv_… (the Apexly-Delivery-Id header)"},"event_id":{"type":"string"},"attempt":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"1-based; counts every attempt of the event, redeliveries included"},"status_code":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"The receiver's HTTP status, when it answered"},"response_ms":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"error":{"description":"Why there was no HTTP answer","type":["string","null"]},"delivered_at":{"type":["string","null"]},"next_attempt_at":{"description":"When the automatic retry after this attempt was due","type":["string","null"]},"created_at":{"type":"string"}},"required":["id","event_id","attempt","status_code","response_ms","error","delivered_at","next_attempt_at","created_at"]},"description":"Newest first, at most 50"}},"required":["id","type","domain_id","hostname","previous_status","status","created_at","delivery","deliveries"]},"DeliveryDto":{"type":"object","properties":{"id":{"type":"string","description":"dlv_… (the Apexly-Delivery-Id header)"},"event_id":{"type":"string"},"attempt":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"1-based; counts every attempt of the event, redeliveries included"},"status_code":{"description":"The receiver's HTTP status, when it answered","type":["integer","null"],"minimum":-9007199254740991,"maximum":9007199254740991},"response_ms":{"type":["integer","null"],"minimum":-9007199254740991,"maximum":9007199254740991},"error":{"description":"Why there was no HTTP answer","type":["string","null"]},"delivered_at":{"type":["string","null"]},"next_attempt_at":{"description":"When the automatic retry after this attempt was due","type":["string","null"]},"created_at":{"type":"string"}},"required":["id","event_id","attempt","status_code","response_ms","error","delivered_at","next_attempt_at","created_at"]},"Error":{"type":"object","description":"The body of every 4xx and 5xx response.","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable, machine-readable; branch on this.","oneOf":[{"const":"invalid_request","description":"The request could not be read.","x-status":400,"x-fix":"Send a well-formed JSON body with Content-Type: application/json."},{"const":"unauthorized","description":"The API key is missing, invalid or revoked.","x-status":401,"x-fix":"Send a valid key as \"Authorization: Bearer sk_live_…\" or \"sk_test_…\"."},{"const":"forbidden","description":"This credential is not allowed to do that.","x-status":403,"x-fix":"Use a credential that has access to this resource."},{"const":"card_required","description":"Live keys need a card on file.","x-status":402,"x-fix":"Add a card on the billing page."},{"const":"live_keys_need_plan","description":"Live keys need a plan: this account is on Free.","x-status":402,"x-fix":"Email support@apexly.dev to upgrade."},{"const":"card_declined","description":"The card was declined.","x-status":402,"x-fix":"Use another card on the billing page."},{"const":"account_suspended","description":"The account is suspended, so changes are paused. Existing domains keep serving.","x-status":403,"x-fix":"Update the card on the billing page to restore the account."},{"const":"plan_required","description":"This feature is not included in the current plan.","x-status":403,"x-fix":"Choose a plan that includes it on the billing page."},{"const":"not_found","description":"Nothing exists at this path, or it belongs to another app.","x-status":404,"x-fix":"Check the URL and the id."},{"const":"idempotency_key_in_use","description":"A request with this Idempotency-Key is still being processed.","x-status":409,"x-fix":"Wait for the first request to finish, then retry with the same key."},{"const":"payload_too_large","description":"The request body is larger than 100 KB.","x-status":413,"x-fix":"Send a smaller body; metadata is limited to 4 KB."},{"const":"validation_failed","description":"The request did not pass validation.","x-status":422,"x-fix":"Correct the fields listed in the message and try again."},{"const":"idempotency_key_reused","description":"This Idempotency-Key was already used with a different request.","x-status":422,"x-fix":"Use a new Idempotency-Key for each distinct request."},{"const":"invalid_hostname","description":"That is not a valid hostname.","x-status":422,"x-fix":"Send a hostname like careers.acme.com: no scheme, path, port, wildcard or IP address."},{"const":"hostname_is_apexly","description":"Hostnames under Apexly zones cannot be added.","x-status":422,"x-fix":"Add your customer's own hostname instead."},{"const":"hostname_is_public_suffix","description":"That hostname is a public suffix, so nobody can prove control of it.","x-status":422,"x-fix":"Add a hostname under it, like careers.example.co.uk."},{"const":"hostname_held","description":"This hostname is held for review.","x-status":422,"x-fix":"Contact support if you believe this is a mistake."},{"const":"domain_limit_reached","description":"The plan does not allow more domains.","x-status":422,"x-fix":"Choose a larger plan on the billing page, or delete unused domains."},{"const":"origin_not_allowed","description":"That origin cannot be used.","x-status":422,"x-fix":"Use a public HTTPS origin that you control."},{"const":"rate_limited","description":"Too many requests.","x-status":429,"x-fix":"Wait for the number of seconds in Retry-After, then try again."},{"const":"billing_unavailable","description":"The billing provider did not answer.","x-status":503,"x-fix":"Try again in a minute."},{"const":"internal_error","description":"Something went wrong on our side.","x-status":500,"x-fix":"Try again. If it keeps happening, contact support with the request id."}]},"message":{"type":"string","description":"What went wrong, in plain words."},"fix":{"type":"string","description":"What to do about it, in one sentence."},"docs":{"type":"string","description":"The page for this code, e.g. https://apexly.dev/docs/errors/invalid_hostname"},"request_id":{"type":"string","description":"internal_error only: quote it when you contact support."}},"required":["code","message","fix","docs"]}},"required":["error"]}}}}