{"openapi":"3.1.0","info":{"title":"Ask Aletta status","version":"1.0.0","summary":"Availability of the Ask Aletta partner integration surface.","description":"A machine-readable status contract for partners integrating against Ask Aletta.\n\nUnauthenticated by design: a status endpoint gated behind authentication is useless\nin exactly the case that matters most, because a failure of authentication is one of\nthe conditions it exists to report. CORS is open so a browser dashboard can call it\ndirectly.\n\nThe snapshot behind the endpoint refreshes roughly every 30 seconds. Past 180 seconds\nwithout a fresh one, every component is reported `unknown` with its true `age_seconds`,\nrather than serving a stale picture as current. **Do not map `unknown` to an outage** -\nit means no information, and usually indicates a blip in our publishing pipeline while\nthe product itself is fine.\n\nWithin `/api/v1/` this contract changes additively only: new fields, new component ids,\nand the documented `maintenance` status may start appearing. Removing or renaming a\ncomponent id, or changing what a status means, will only ever happen at a new path."},"servers":[{"url":"https://status.askaletta.com","description":"Production"}],"paths":{"/api/v1/status":{"get":{"operationId":"getStatus","summary":"Current status of every published component","description":"Poll every 30-60 seconds and send `If-None-Match` with the last `ETag`.\nThe `ETag` excludes the ever-ticking `age_seconds`, so it stays stable while the\nunderlying snapshot is unchanged and conditional polling is cheap for both sides.","parameters":[{"name":"If-None-Match","in":"header","required":false,"schema":{"type":"string"},"description":"The `ETag` from a previous response."}],"responses":{"200":{"description":"Current status.","headers":{"ETag":{"schema":{"type":"string"},"description":"Stable while the underlying snapshot is unchanged."},"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["schema_version","status","updated_at","age_seconds","components","incidents"],"additionalProperties":false,"properties":{"schema_version":{"type":"integer","const":1},"status":{"type":"string","enum":["operational","degraded","outage","maintenance","unknown"],"description":"The worst status across all components."},"updated_at":{"type":["string","null"],"format":"date-time","description":"When the snapshot was produced. `null` when no snapshot exists."},"age_seconds":{"type":["integer","null"],"description":"Snapshot age at the time of the request. Always truthful, including when stale. `null` when no snapshot exists."},"components":{"type":"array","items":{"type":"object","required":["id","name","status","cause","depends_on","since"],"additionalProperties":false,"properties":{"id":{"type":"string","enum":["api","auth","conversations","tasks","launch"],"description":"Stable capability identifier. Consumers MUST tolerate ids not listed here: new components may be added within v1."},"name":{"type":"string","description":"Human-readable label. Not stable; do not key on it."},"status":{"type":"string","enum":["operational","degraded","outage","maintenance","unknown"]},"cause":{"type":"string","enum":["self","dependency"],"description":"`dependency` means this component is impaired because something it depends on is. Always `self` when the component is not impaired, and whenever `depends_on` is empty."},"depends_on":{"type":"array","items":{"type":"string","enum":["api","auth","conversations","tasks","launch"]},"description":"Component ids responsible for this component's status. Only ever names ids present in the same `components` array."},"since":{"type":["string","null"],"format":"date-time","description":"When this component last changed to its current status. `null` means we do not know when the state began - never that it began just now. Also `null` whenever `status` is `unknown`."}}},"description":"Always carries every component listed in `id`. A component the publisher dropped is reported `unknown` rather than omitted, so a consumer never has to distinguish \"absent\" from \"fine\"."},"incidents":{"type":"array","items":{"type":"object","required":["id","kind","title","detail","impact","status","affected","started_at","ends_at","updates"],"additionalProperties":false,"properties":{"id":{"type":"string","description":"Stable identifier for this incident."},"kind":{"type":"string","enum":["incident","maintenance"],"description":"`maintenance` is a planned window, published before it begins. `incident` is unplanned."},"title":{"type":"string","maxLength":140},"detail":{"type":["string","null"],"description":"Longer operator-authored prose. English here, like every other string on this contract. Null unless the operator wrote one."},"impact":{"type":"string","enum":["none","minor","major","critical"],"description":"Headline severity. Independent of the per-component statuses in `affected`."},"status":{"type":"string","enum":["investigating","identified","monitoring","resolved","scheduled","in_progress","completed"],"description":"Lifecycle position. `investigating`, `identified`, `monitoring`, `resolved` for an incident; `scheduled`, `in_progress`, `completed` for maintenance. The two vocabularies do not mix."},"affected":{"type":"array","description":"Components this incident declares impaired, and how. An incident can only make a component look WORSE than measured, never better, so what you read in `components` is the worse of the two. A maintenance window applies only inside its own window.","items":{"type":"object","required":["id","status","via"],"additionalProperties":false,"properties":{"id":{"type":"string","enum":["api","auth","conversations","tasks","launch"]},"status":{"type":"string","enum":["operational","degraded","outage","maintenance","unknown"]},"via":{"type":"array","items":{"type":"string","enum":["api","auth","conversations","tasks","launch"]},"description":"Components this one is impaired BY. When non-empty, the component reports `cause: \"dependency\"` with these ids in its `depends_on`. Empty means the incident attributes the impairment to the component itself."}}}},"started_at":{"type":"string","format":"date-time","description":"When the incident began, or when a maintenance window is scheduled to open."},"ends_at":{"type":["string","null"],"format":"date-time","description":"When it was resolved, or when a maintenance window is scheduled to close. `null` while an unplanned incident is ongoing."},"updates":{"type":"array","description":"The timeline, oldest first. Append-only; entries are never edited or removed.","items":{"type":"object","required":["at","status","body"],"additionalProperties":false,"properties":{"at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["investigating","identified","monitoring","resolved","scheduled","in_progress","completed"]},"body":{"type":"string","maxLength":2000}}}}}},"description":"Open incidents and scheduled maintenance windows, newest first. A finished one keeps appearing for 24h so a polling consumer cannot miss the resolution. All prose is plain text with markup stripped."}}}}}},"304":{"description":"Unchanged since the `ETag` supplied in `If-None-Match`."}}}},"/api/v1/app":{"get":{"operationId":"getAppView","summary":"What the product apps should show a user, if anything","parameters":[{"name":"locale","in":"query","required":false,"schema":{"type":"string","enum":["en","nl","fr","de","hr","sv","pl"]},"description":"Unrecognised or absent resolves to `en` rather than failing: a status endpoint must not fail closed on a junk parameter."}],"responses":{"200":{"description":"At most one display item, resolved for the requested locale.","content":{"application/json":{"schema":{"type":"object","required":["schema_version","locale","display"],"additionalProperties":false,"properties":{"schema_version":{"type":"integer","enum":[1]},"locale":{"type":"string","enum":["en","nl","fr","de","hr","sv","pl"]},"display":{"oneOf":[{"type":"null"},{"type":"object","required":["kind","id","mode","phase","level","title","body","detail","started_at","ends_at"],"additionalProperties":false,"properties":{"kind":{"type":"string","enum":["incident","maintenance","detected"]},"id":{"type":"string","description":"The incident id, or `detected:<level>:<since>` for a measured condition, which has no record behind it. The episode instant is part of the id so that dismissing one episode does not silence the next."},"mode":{"type":"string","enum":["banner","blocking"],"description":"`blocking` is served only while an operator-declared incident is applying. Consumers MUST render an unrecognised value as a banner."},"phase":{"type":"string","enum":["applying","scheduled","resolved"]},"level":{"type":["string","null"],"enum":["degraded","outage",null],"description":"Set only when `kind` is `detected`."},"title":{"type":["string","null"],"description":"Null when `kind` is `detected`."},"body":{"type":["string","null"],"description":"The latest update. Null when `kind` is `detected`."},"detail":{"type":["string","null"],"description":"Longer operator-authored text for an expandable panel. Null unless the operator wrote one, and always null when `kind` is `detected`."},"started_at":{"type":"string","format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time"}}}],"description":"At most one item. The edge selects it; consumers render it."}}}}}}}}},"/api/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"This document","responses":{"200":{"description":"The OpenAPI description of this API.","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}