---
title: "List SLOs"
method: GET
path: "/api/{org_id}/slos"
tags: ["SLOs"]
---

# List SLOs

`GET /api/{org_id}/slos`

## Request

- Base URL: `https://api.openobserve.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.openobserve.ai/api/{org_id}/slos`
- Auth: API key in header `Authorization`
- TypeScript: <https://skmtc.dev/openobserve/apis/openobserve-api-http/docs/api/:org_id/slos/get.ts> (a function with typed inputs; its response is checked against the schema)

## Path parameters

- `org_id` string, required

## Query parameters

- `folder` string

## Response `200`

Success

- SloListResponse
  - `list` SloListItem[], required
    - union
      - object: An SLO plus its current measurement, which is what a list view needs.
        - `config` object, required
          - `source` union, required: How a count SLI obtains good/total — itself a tagged union so that unrepresentable states cannot be constructed (D40). **Adjacently tagged, not internally tagged.** This workspace builds `serde_json` with `arbitrary_precision` (root `Cargo.toml`), which encodes every number as a map. Internally-tagged enums buffer their whole subtree through `serde::__private::de::Content` before dispatching, and that buffered map cannot then be deserialized into an `f64` — the failure is `invalid type: map, expected f64`, and it survives nesting. Serialization still succeeds, so the bug only appears on read-back. This variant carries no numbers *today*, but the dual-query form is the one most likely to gain them, and the failure mode is silent enough to be worth pre-empting. See [`SliConfig`] for the same treatment and a regression test.
            - object: The native form: one scan, so good and total are provably from the same rows.
              - …
            - object: Importer-only fallback for an unfoldable imported pair. Weaker atomicity: two scans that cannot be proven to have seen the same instant.
              - …
            - object: Metrics-native counting: two PromQL expressions, each evaluated per slice (wire tag `prom_ql`, following [`QueryLanguage::PromQl`]). Exists because pre-aggregated counters have no rows for a `good_expr` to classify — "good" only exists as arithmetic between series, and correct counter arithmetic is `increase()` (monotonic-reset-aware), which SQL over raw samples cannot express. The expressions should use a range selector equal to the slice interval (`increase(http_requests_total[5m])` for a 5-minute slice); the evaluator samples them at slice ends, so each sample covers exactly its slice. Same atomicity caveat as [`Self::DualQuery`]: two evaluations that cannot be proven to have seen the same instant. Grouping needs no column list — the returned series' labels supply the group values.
              - …
        - `sli_type` 'count', required
        - `group_by` string[], nullable: THE canonical location for grouping.
        - `slice_interval_secs` integer, required
        - `window_secs` integer, required
        - `definition_generation` integer: Bumped by every computation-affecting edit, including reverts (D59). Also the writing epoch and the CAS fence for writer commits.
        - `description` string
        - `enabled` boolean
        - `folder_id` string
        - `groups_estimate` integer, nullable: Preflight `COUNT(DISTINCT …)` estimate; feeds the S-10 cap and the S-14 budget.
        - `groups_reserved` integer: Budget reservation: 1 when ungrouped, else `clamp(2 × estimate, 64, hard cap)`.
        - `id` string
        - `name` string, required
        - `org` string
        - `owner` string, nullable
        - `tags` string[]
        - `target` number, double, required: Percentage in (0, 100), up to 3 decimals. Applied at READ time.
        - `status` SloStatusView: What the UI and the API render for one SLO or one group.
          - `burn_rate` number, double, nullable: Current burn rate — multiples of the budget-neutral rate.
          - `computed_at` integer, nullable
          - `corrections` CorrectionRef[]
            - `applies` boolean: False when the window holds no slice start, so it corrects no minute of this SLO.
            - `downtime_id` string, required
            - `name` string, required
            - `status` 'scheduled' | 'active' | 'ended' | 'cancelled' | 'ended_early', required
          - `coverage` number, double, required: Fraction of the window actually measured, 0..1.
          - `covered_slices` integer, required
          - `error_budget_remaining` number, double, nullable: **Percentage** of the error budget still unspent, not a fraction. Negative once the budget is overspent, which is meaningful and deliberately not clamped: "-80% remaining" is what a user needs to see when they have burned 180% of the budget.
          - `good` number, double, required
          - `group_key` string, required
          - `measuring_since` integer, nullable: The earliest instant this SLO is allowed to measure from, epoch seconds — set only when it is LATER than the window's own start, i.e. only when it is the reason the window is not full (S-16 PR 4). An alert-sourced SLO cannot measure back to the start of its window: it has no evidence before the ledger begins, and history before the source alert's last edit was produced under a config the eligibility rules never saw. The window is therefore genuinely partial for a while, and the honest reading is "measuring since <date>, N of 30 days" rather than a coverage percentage that invites the user to hunt for a gap that does not exist.
          - `no_data` boolean, required: True when coverage is below the floor. Every derived figure is `None` in that case — the SLO is frozen, not healthy and not breached.
          - `sli` number, double, nullable: Percentage, 0..100. `None` when frozen or not yet measured.
          - `stale_watermark` boolean: The OTHER freeze door (§2). A source that stops evaluating leaves the window pinned with its coverage still high, so [`Self::no_data`] — which is the coverage floor and nothing else — cannot see that freeze at all. Set by the read path, which knows the clock; `derive` has no opinion.
          - `time_to_exhaust_secs` integer, nullable: Seconds until the budget is exhausted at the current burn. `None` when the burn is at or below neutral, because nothing is being exhausted.
          - `total` number, double, required
          - `watermark_end` integer, nullable: How far measurement actually got, epoch seconds. Reads up to ~K recompute slices later than the last real evaluation (§2) — close enough for a banner, and the only timestamp there is.
      - object: An SLO plus its current measurement, which is what a list view needs.
        - `config` object, required
          - `absent_is_bad` boolean: Freshness semantics: a slice the query PROVED empty is **bad** rather than a gap. For a pipeline-freshness SLO, absence is the failure signal — a silent pipeline is a broken pipeline, and under S-8's default it could never read as bad. Only flips the meaning of a *successful* query's empty bucket; a FAILED query still writes nothing and coverage falls, so a search outage freezes the SLO exactly as before. That distinction is structural — gap fill runs only after a successful query. `#[serde(default)]` is the upgrade guarantee: every stored time-slice SLO predates the field and keeps S-8 byte-for-byte. Ungrouped SLOs only (validated) — gap fill cannot see a group that is absent from the whole pass, so a grouped freshness SLO would freeze instead of firing for precisely the failure it watches for.
          - `comparator` '=' | '!=' | '>' | '>=' | '<' | '<=' | 'contains' | 'not_contains' | 'is_null' | 'is_not_null' | 'is_empty' | 'is_not_empty', required
          - `query` string, required
          - `query_language` 'sql' | 'prom_ql', required: Query language for a time-slice aggregate. Carried explicitly rather than inferred from the stream type, so the evaluator never guesses.
          - `scope` string, nullable
          - `stream` string, required
          - `stream_type` string, required
          - `threshold` number, double, required
        - `sli_type` 'time_slice', required
        - `group_by` string[], nullable: THE canonical location for grouping.
        - `slice_interval_secs` integer, required
        - `window_secs` integer, required
        - `definition_generation` integer: Bumped by every computation-affecting edit, including reverts (D59). Also the writing epoch and the CAS fence for writer commits.
        - `description` string
        - `enabled` boolean
        - `folder_id` string
        - `groups_estimate` integer, nullable: Preflight `COUNT(DISTINCT …)` estimate; feeds the S-10 cap and the S-14 budget.
        - `groups_reserved` integer: Budget reservation: 1 when ungrouped, else `clamp(2 × estimate, 64, hard cap)`.
        - `id` string
        - `name` string, required
        - `org` string
        - `owner` string, nullable
        - `tags` string[]
        - `target` number, double, required: Percentage in (0, 100), up to 3 decimals. Applied at READ time.
        - `status` SloStatusView: What the UI and the API render for one SLO or one group.
          - `burn_rate` number, double, nullable: Current burn rate — multiples of the budget-neutral rate.
          - `computed_at` integer, nullable
          - `corrections` CorrectionRef[]
            - `applies` boolean: False when the window holds no slice start, so it corrects no minute of this SLO.
            - `downtime_id` string, required
            - `name` string, required
            - `status` 'scheduled' | 'active' | 'ended' | 'cancelled' | 'ended_early', required
          - `coverage` number, double, required: Fraction of the window actually measured, 0..1.
          - `covered_slices` integer, required
          - `error_budget_remaining` number, double, nullable: **Percentage** of the error budget still unspent, not a fraction. Negative once the budget is overspent, which is meaningful and deliberately not clamped: "-80% remaining" is what a user needs to see when they have burned 180% of the budget.
          - `good` number, double, required
          - `group_key` string, required
          - `measuring_since` integer, nullable: The earliest instant this SLO is allowed to measure from, epoch seconds — set only when it is LATER than the window's own start, i.e. only when it is the reason the window is not full (S-16 PR 4). An alert-sourced SLO cannot measure back to the start of its window: it has no evidence before the ledger begins, and history before the source alert's last edit was produced under a config the eligibility rules never saw. The window is therefore genuinely partial for a while, and the honest reading is "measuring since <date>, N of 30 days" rather than a coverage percentage that invites the user to hunt for a gap that does not exist.
          - `no_data` boolean, required: True when coverage is below the floor. Every derived figure is `None` in that case — the SLO is frozen, not healthy and not breached.
          - `sli` number, double, nullable: Percentage, 0..100. `None` when frozen or not yet measured.
          - `stale_watermark` boolean: The OTHER freeze door (§2). A source that stops evaluating leaves the window pinned with its coverage still high, so [`Self::no_data`] — which is the coverage floor and nothing else — cannot see that freeze at all. Set by the read path, which knows the clock; `derive` has no opinion.
          - `time_to_exhaust_secs` integer, nullable: Seconds until the budget is exhausted at the current burn. `None` when the burn is at or below neutral, because nothing is being exhausted.
          - `total` number, double, required
          - `watermark_end` integer, nullable: How far measurement actually got, epoch seconds. Reads up to ~K recompute slices later than the last real evaluation (§2) — close enough for a banner, and the only timestamp there is.
      - object: An SLO plus its current measurement, which is what a list view needs.
        - `config` object, required
          - `alert_id` string, required
        - `sli_type` 'alert', required
        - `group_by` string[], nullable: THE canonical location for grouping.
        - `slice_interval_secs` integer, required
        - `window_secs` integer, required
        - `definition_generation` integer: Bumped by every computation-affecting edit, including reverts (D59). Also the writing epoch and the CAS fence for writer commits.
        - `description` string
        - `enabled` boolean
        - `folder_id` string
        - `groups_estimate` integer, nullable: Preflight `COUNT(DISTINCT …)` estimate; feeds the S-10 cap and the S-14 budget.
        - `groups_reserved` integer: Budget reservation: 1 when ungrouped, else `clamp(2 × estimate, 64, hard cap)`.
        - `id` string
        - `name` string, required
        - `org` string
        - `owner` string, nullable
        - `tags` string[]
        - `target` number, double, required: Percentage in (0, 100), up to 3 decimals. Applied at READ time.
        - `status` SloStatusView: What the UI and the API render for one SLO or one group.
          - `burn_rate` number, double, nullable: Current burn rate — multiples of the budget-neutral rate.
          - `computed_at` integer, nullable
          - `corrections` CorrectionRef[]
            - `applies` boolean: False when the window holds no slice start, so it corrects no minute of this SLO.
            - `downtime_id` string, required
            - `name` string, required
            - `status` 'scheduled' | 'active' | 'ended' | 'cancelled' | 'ended_early', required
          - `coverage` number, double, required: Fraction of the window actually measured, 0..1.
          - `covered_slices` integer, required
          - `error_budget_remaining` number, double, nullable: **Percentage** of the error budget still unspent, not a fraction. Negative once the budget is overspent, which is meaningful and deliberately not clamped: "-80% remaining" is what a user needs to see when they have burned 180% of the budget.
          - `good` number, double, required
          - `group_key` string, required
          - `measuring_since` integer, nullable: The earliest instant this SLO is allowed to measure from, epoch seconds — set only when it is LATER than the window's own start, i.e. only when it is the reason the window is not full (S-16 PR 4). An alert-sourced SLO cannot measure back to the start of its window: it has no evidence before the ledger begins, and history before the source alert's last edit was produced under a config the eligibility rules never saw. The window is therefore genuinely partial for a while, and the honest reading is "measuring since <date>, N of 30 days" rather than a coverage percentage that invites the user to hunt for a gap that does not exist.
          - `no_data` boolean, required: True when coverage is below the floor. Every derived figure is `None` in that case — the SLO is frozen, not healthy and not breached.
          - `sli` number, double, nullable: Percentage, 0..100. `None` when frozen or not yet measured.
          - `stale_watermark` boolean: The OTHER freeze door (§2). A source that stops evaluating leaves the window pinned with its coverage still high, so [`Self::no_data`] — which is the coverage floor and nothing else — cannot see that freeze at all. Set by the read path, which knows the clock; `derive` has no opinion.
          - `time_to_exhaust_secs` integer, nullable: Seconds until the budget is exhausted at the current burn. `None` when the burn is at or below neutral, because nothing is being exhausted.
          - `total` number, double, required
          - `watermark_end` integer, nullable: How far measurement actually got, epoch seconds. Reads up to ~K recompute slices later than the last real evaluation (§2) — close enough for a banner, and the only timestamp there is.

## Other responses

- `500`: Internal Server Error

---

[API](https://skmtc.dev/openobserve/apis/openobserve-api-http.md) - [All operations](https://skmtc.dev/openobserve/apis/openobserve-api-http/llms.txt) - [OpenAPI document](https://skmtc.dev/openobserve/apis/openobserve-api-http/revisions/0529b8a10252?raw)
