{"openapi":"3.1.0","info":{"title":"Lummet public API","version":"1","description":"Read-only, API-key authenticated endpoints under /api/v1/. Routes not listed here are not public API. No rate-limit quota is promised beyond the invalid-key protection described on 429."},"servers":[{"url":"/","description":"The Lummet origin you are calling."}],"tags":[{"name":"API v1"}],"paths":{"/api/v1/sites":{"get":{"operationId":"listSites","summary":"List the organization's sites","description":"A key restricted to one site sees only that site.","tags":["API v1"],"security":[{"apiKey":["sites:read"]}],"parameters":[],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's billing state currently blocks API access (error billing_access_restricted, with a reasonCode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the sites:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many invalid keys from this address. Retry after the interval in Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{siteId}":{"get":{"operationId":"getSite","summary":"Get one site","tags":["API v1"],"security":[{"apiKey":["sites:read"]}],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"},"description":"Site id."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's billing state currently blocks API access (error billing_access_restricted, with a reasonCode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the sites:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The site does not exist, belongs to another organization, or is outside this key's site restriction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many invalid keys from this address. Retry after the interval in Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{siteId}/domains":{"get":{"operationId":"listSiteDomains","summary":"List a site's domains","tags":["API v1"],"security":[{"apiKey":["domains:read"]}],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"},"description":"Site id."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's billing state currently blocks API access (error billing_access_restricted, with a reasonCode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the domains:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The site does not exist, belongs to another organization, or is outside this key's site restriction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many invalid keys from this address. Retry after the interval in Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{siteId}/domain-zones":{"get":{"operationId":"listSiteDomainZones","summary":"List a site's DNS zones and hostnames","tags":["API v1"],"security":[{"apiKey":["domains:read"]}],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"},"description":"Site id."}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's billing state currently blocks API access (error billing_access_restricted, with a reasonCode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the domains:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The site does not exist, belongs to another organization, or is outside this key's site restriction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many invalid keys from this address. Retry after the interval in Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/sites/{siteId}/analytics/overview":{"get":{"operationId":"getSiteAnalyticsOverview","summary":"Analytics overview for a site","description":"The response body is the analytics result object itself, not wrapped in the data envelope. Error bodies use error, reasonCode and message.","tags":["API v1"],"security":[{"apiKey":["analytics:read"]}],"parameters":[{"name":"siteId","in":"path","required":true,"schema":{"type":"string"},"description":"Site id."},{"name":"start_date","in":"query","required":false,"schema":{"type":"string"}},{"name":"end_date","in":"query","required":false,"schema":{"type":"string"}},{"name":"dimension_type","in":"query","required":false,"schema":{"type":"string"}},{"name":"currency","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Free"}}}},"401":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's billing state currently blocks API access (error billing_access_restricted, with a reasonCode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the analytics:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The site does not exist, belongs to another organization, or is outside this key's site restriction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many invalid keys from this address. Retry after the interval in Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/usage":{"get":{"operationId":"getUsage","summary":"Usage snapshot for the organization","tags":["API v1"],"security":[{"apiKey":["usage:read"]}],"parameters":[],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataEnvelope"}}}},"401":{"description":"Missing, malformed, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization's billing state currently blocks API access (error billing_access_restricted, with a reasonCode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key does not carry the usage:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many invalid keys from this address. Retry after the interval in Retry-After.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"lummetDelivery":{"post":{"operationId":"receiveLummetWebhook","summary":"Outgoing webhook delivery (implemented by the receiver)","description":"Lummet POSTs JSON to the endpoint you registered. Verify before trusting: compute HMAC-SHA256 keyed with the endpoint secret over the exact string `{t}.{raw request body}`, hex-encode it, and compare with `v1` in constant time. Reject timestamps outside a replay window you choose. Every retry is re-signed with a fresh timestamp and carries the same Lummet-Delivery-Id, so use that id to ignore duplicates. Respond 2xx to acknowledge; 410 ends deliveries to that endpoint permanently.","tags":["Webhooks"],"parameters":[{"name":"Lummet-Signature","in":"header","required":true,"schema":{"type":"string","pattern":"^t=\\d+,v1=[0-9a-f]{64}$"},"description":"t=<unix seconds>,v1=<hex HMAC-SHA256>."},{"name":"Lummet-Delivery-Id","in":"header","required":true,"schema":{"type":"string"}},{"name":"Lummet-Delivery-Attempt","in":"header","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"410":{"description":"Endpoint is gone; Lummet stops delivering to it."},"2XX":{"description":"Acknowledged."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Customer API key created by an organization owner. Scopes: sites:read, domains:read, analytics:read, usage:read."}},"schemas":{"Error":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"string"},"reasonCode":{"type":"string"},"message":{"type":"string"}},"additionalProperties":true},"DataEnvelope":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{}},"additionalProperties":true},"Free":{"type":"object","additionalProperties":true},"WebhookEvent":{"type":"object","description":"Event payload as sent; never contains secrets. id is the delivery id (also sent as Lummet-Delivery-Id).","required":["id","type","created","data"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["site.created","site.provisioned","site.deployed","site.failed","domain.verified","subscription.changed","payment.failed"]},"created":{"type":"integer","description":"Unix seconds when the event was created."},"data":{"type":"object","additionalProperties":true}},"additionalProperties":false}}}}