> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timbal.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Alarm

> Create a metric alarm with a threshold over evaluation periods.

Set `denominator_metric_name` for a **rate** rather than a count: the datapoint becomes `metric_name / denominator_metric_name` and `threshold` reads as a fraction (`0.05` = 5%). Prefer this for anything that scales with traffic — a fixed error count pages at 3am once the org grows. Pair it with `min_sample_count` so a single failure in a quiet minute isn't a 100% error rate.

Both series must be binned on the same clock, since they are divided period by period. For outcome rates that means `app.run.terminal_count` — runs that *finished* in the period — and not `app.run.count`, which counts runs that *started* and includes ones still running. Mismatched pairs are rejected.

Ratio alarms also require `stat` `sum` or `count`. The stat is applied to each series and the two results divided, so only stats that add up across the minutes in a period come out as a rate — `avg` would divide two means and `min`/`max` would take their extremes from different minutes. Other stats are rejected rather than silently producing a number that isn't a rate.

A sustained incident only ever notifies once, so repeat messages mean the alarm is flapping. Three levers fix that, and which one depends on the metric: `clear_threshold` (fire high, clear low) for continuous series like rates and latencies; `datapoints_to_alarm` for spiky ones that cross the line briefly; and `missing_data: "ignore"` for sparse ones, where a minute with no traffic is absence of evidence rather than a recovery.

Use `GET /orgs/{org_id}/metrics/catalog` to see which metric names and labels this org is actually emitting. Two metrics won't be listed until you alarm on them: `org.credits.usage_pct` (percent of the billing period's prepaid budget consumed) and `org.credits.remaining` (credits left in it) are only sampled for orgs that have an enabled alarm on one, since the underlying read is the billing gate's and too heavy to run speculatively. Both are levels sampled once a minute, so use `max`, `avg` or `min` — `sum` would add one reading per minute in the period and is rejected. `org.credits.remaining` goes negative under overage.

Percent and headroom are worth having separately: 95% of a large budget can still be thousands of credits, while 50% of a small one is one bad afternoon. For burn *volume* rather than budget position, alarm on `org.credits.spend` with `sum` — that one carries `project_id` and `workforce_id` dims, so it can scope to a single project or workforce the way a budget can't.

For latency, `stat` accepts any percentile from `p0.1` to `p99.9`, in tenths. Equivalent spellings are fine (`p95.0` is accepted and comes back as `p95`), but anything finer than a tenth is rejected rather than rounded, so an alarm never silently watches a different point than the one you asked for. These are exact, computed over the individual run durations rather than interpolated from bins, which is also why they are limited to `app.run.duration_ms`, cannot be a ratio, and cannot carry `label`, `project_env_id` or `dims.deployment_id` — a run row has no such column, so those filters would silently widen the alarm to everything. Prefer a high percentile over `avg` for latency: an average hides the tail that users actually notice, and hides it harder as traffic grows.

Two different project fields, gated separately. Top-level `project_id` is ownership — who may see and edit the alarm afterwards — and setting it requires `projects.alarms.manage` on that project, while leaving it out makes an org-level alarm and requires `alarms.manage`. `dims.project_id` only narrows which metric bins are read, but it still exposes that project's numbers through the alarm's notifications, so it requires `projects.alarms.read` on the project it names.



## OpenAPI

````yaml POST /orgs/{org_id}/alarms
openapi: 3.1.0
info:
  title: Timbal Platform API
  description: Public API documentation for the Timbal platform
  license:
    name: ''
  version: 2.0.0
servers:
  - url: https://api.timbal.ai
    description: Production
  - url: https://api.dev.timbal.ai
    description: Development
security: []
tags:
  - name: ace
    description: Action Control Engine (ACE) operations
  - name: alarms
    description: Metric series and threshold alarms
  - name: analytics
    description: Org and project analytics
  - name: billing
    description: Subscriptions, usage, and payment-related operations
  - name: channels
    description: Delivery channels alarms notify through
  - name: content
    description: Re-sign stored content URLs
  - name: files
    description: File operations
  - name: iam
    description: IAM — actions, roles, users, and authorization introspection
  - name: k2
    description: Knowledge Bases v2 operations
  - name: orgs
    description: Organization operations
  - name: projects
    description: Project operations
  - name: runs
    description: Execution operations
  - name: templates
    description: Public project template catalog
  - name: users
    description: Authenticated user profile
paths:
  /orgs/{org_id}/alarms:
    post:
      tags:
        - alarms
      summary: Create a metric alarm
      description: >-
        Create a metric alarm with a threshold over evaluation periods.


        Set `denominator_metric_name` for a **rate** rather than a count: the
        datapoint becomes `metric_name / denominator_metric_name` and
        `threshold` reads as a fraction (`0.05` = 5%). Prefer this for anything
        that scales with traffic — a fixed error count pages at 3am once the org
        grows. Pair it with `min_sample_count` so a single failure in a quiet
        minute isn't a 100% error rate.


        Both series must be binned on the same clock, since they are divided
        period by period. For outcome rates that means `app.run.terminal_count`
        — runs that *finished* in the period — and not `app.run.count`, which
        counts runs that *started* and includes ones still running. Mismatched
        pairs are rejected.


        Ratio alarms also require `stat` `sum` or `count`. The stat is applied
        to each series and the two results divided, so only stats that add up
        across the minutes in a period come out as a rate — `avg` would divide
        two means and `min`/`max` would take their extremes from different
        minutes. Other stats are rejected rather than silently producing a
        number that isn't a rate.


        A sustained incident only ever notifies once, so repeat messages mean
        the alarm is flapping. Three levers fix that, and which one depends on
        the metric: `clear_threshold` (fire high, clear low) for continuous
        series like rates and latencies; `datapoints_to_alarm` for spiky ones
        that cross the line briefly; and `missing_data: "ignore"` for sparse
        ones, where a minute with no traffic is absence of evidence rather than
        a recovery.


        Use `GET /orgs/{org_id}/metrics/catalog` to see which metric names and
        labels this org is actually emitting. Two metrics won't be listed until
        you alarm on them: `org.credits.usage_pct` (percent of the billing
        period's prepaid budget consumed) and `org.credits.remaining` (credits
        left in it) are only sampled for orgs that have an enabled alarm on one,
        since the underlying read is the billing gate's and too heavy to run
        speculatively. Both are levels sampled once a minute, so use `max`,
        `avg` or `min` — `sum` would add one reading per minute in the period
        and is rejected. `org.credits.remaining` goes negative under overage.


        Percent and headroom are worth having separately: 95% of a large budget
        can still be thousands of credits, while 50% of a small one is one bad
        afternoon. For burn *volume* rather than budget position, alarm on
        `org.credits.spend` with `sum` — that one carries `project_id` and
        `workforce_id` dims, so it can scope to a single project or workforce
        the way a budget can't.


        For latency, `stat` accepts any percentile from `p0.1` to `p99.9`, in
        tenths. Equivalent spellings are fine (`p95.0` is accepted and comes
        back as `p95`), but anything finer than a tenth is rejected rather than
        rounded, so an alarm never silently watches a different point than the
        one you asked for. These are exact, computed over the individual run
        durations rather than interpolated from bins, which is also why they are
        limited to `app.run.duration_ms`, cannot be a ratio, and cannot carry
        `label`, `project_env_id` or `dims.deployment_id` — a run row has no
        such column, so those filters would silently widen the alarm to
        everything. Prefer a high percentile over `avg` for latency: an average
        hides the tail that users actually notice, and hides it harder as
        traffic grows.


        Two different project fields, gated separately. Top-level `project_id`
        is ownership — who may see and edit the alarm afterwards — and setting
        it requires `projects.alarms.manage` on that project, while leaving it
        out makes an org-level alarm and requires `alarms.manage`.
        `dims.project_id` only narrows which metric bins are read, but it still
        exposes that project's numbers through the alarm's notifications, so it
        requires `projects.alarms.read` on the project it names.
      operationId: create_org_alarm
      parameters:
        - name: org_id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAlarmReq'
            examples:
              Credits budget above 80%:
                value:
                  name: Credits usage
                  metric_name: org.credits.usage_pct
                  stat: max
                  period_seconds: 300
                  comparison: gte
                  threshold: 80
                  clear_threshold: 70
                  datapoints_to_alarm: 1
                  evaluation_periods: 1
                  channel:
                    type: email
                    config:
                      to: finance@example.com
              Deployment down:
                value:
                  name: API down — production
                  metric_name: deployment.health
                  dims:
                    project_env_id: '559'
                  stat: avg
                  period_seconds: 120
                  comparison: lt
                  threshold: 0.5
                  missing_data: breaching
                  channel:
                    type: slack
                    config:
                      token: xoxb-1234567890123-1234567890123
                      channel: C0123456789
              Error count:
                value:
                  name: Support agent errors
                  metric_name: app.run.errors
                  dims:
                    workforce_id: '123'
                  stat: sum
                  period_seconds: 300
                  comparison: gte
                  threshold: 5
                  datapoints_to_alarm: 2
                  evaluation_periods: 3
                  channel:
                    type: email
                    config:
                      to: ops@example.com
              Error rate above 5%:
                value:
                  name: Support agent error rate
                  metric_name: app.run.errors
                  denominator_metric_name: app.run.terminal_count
                  min_sample_count: 20
                  dims:
                    workforce_id: '123'
                  stat: sum
                  period_seconds: 300
                  comparison: gt
                  threshold: 0.05
                  clear_threshold: 0.03
                  datapoints_to_alarm: 2
                  evaluation_periods: 3
                  channel:
                    type: email
                    config:
                      to: ops@example.com
              Fewer than 1000 credits left:
                value:
                  name: Credits headroom
                  metric_name: org.credits.remaining
                  stat: min
                  period_seconds: 300
                  comparison: lt
                  threshold: 1000
                  clear_threshold: 5000
                  datapoints_to_alarm: 1
                  evaluation_periods: 1
                  channel:
                    type: email
                    config:
                      to: finance@example.com
              Sparse metric, no false recovery:
                value:
                  name: Checkout tool failures
                  metric_name: trace.tool_errors
                  dims:
                    workforce_id: '123'
                    label: charge_card
                  stat: sum
                  period_seconds: 60
                  comparison: gt
                  threshold: 5
                  missing_data: ignore
                  channel:
                    type: slack
                    config:
                      token: xoxb-1234567890123-1234567890123
                      channel: C0123456789
              p99 latency:
                value:
                  name: Checkout p99 latency
                  metric_name: app.run.duration_ms
                  dims:
                    workforce_id: '123'
                  stat: p99
                  period_seconds: 300
                  comparison: gt
                  threshold: 8000
                  clear_threshold: 6000
                  datapoints_to_alarm: 2
                  evaluation_periods: 3
                  channel:
                    type: email
                    config:
                      to: ops@example.com
        required: true
      responses:
        '201':
          description: Alarm and channel link created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateAlarmOut'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMessage'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMessage'
      security:
        - bearer_auth: []
components:
  schemas:
    CreateAlarmReq:
      type: object
      description: Body for **POST** `/orgs/{org_id}/alarms`.
      required:
        - name
        - metric_name
        - stat
        - comparison
        - threshold
        - channel
      properties:
        channel:
          $ref: '#/components/schemas/CreateChannelReq'
          description: >-
            Channel: inline `type` + `config`, or reuse with `{ "id": <channel
            id> }` (same org).
        clear_threshold:
          type:
            - number
            - 'null'
          format: double
          description: >-
            Hysteresis: once in `alarm`, a period only stops counting as
            breaching

            once it clears *this* value rather than `threshold`. Must sit on the

            non-breaching side of `threshold` — below it for `gt`/`gte`, above
            for

            `lt`/`lte`.


            Fire at `0.05` and clear at `0.03`, and a metric hovering on 5%
            produces

            one transition instead of one per swing. Without it the only
            defences

            against flapping are a longer `period_seconds` or a higher

            `datapoints_to_alarm`, both of which also delay the *first*
            detection.
        comparison:
          $ref: '#/components/schemas/AlarmComparison'
        datapoints_to_alarm:
          type: integer
          format: int32
          description: Datapoints that must breach out of the last `evaluation_periods`.
        denominator_metric_name:
          type:
            - string
            - 'null'
          description: |-
            Turns the alarm into a **ratio** condition: each period's datapoint
            becomes `metric_name / denominator_metric_name`, read with the same
            dims, stat and period. `threshold` is then a rate — `0.05` for a 5%
            error rate, not `5`.

            Pair it with `min_sample_count`; see that field.
        dims:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/AlarmDims'
        evaluation_periods:
          type: integer
          format: int32
        metric_name:
          type: string
          description: >-
            Metric to watch, e.g. `app.run.errors`, `deployment.health`,

            `org.credits.spend`. The numerator when `denominator_metric_name` is
            set.
        min_sample_count:
          type: integer
          format: int64
          description: >-
            Minimum denominator for a ratio period to count. Below it (or at
            zero)

            the period has no datapoint and follows `missing_data`.


            Exists because ratios are meaningless on tiny samples: 1 error out
            of 1

            run is 100%. Set it to the smallest traffic level you'd actually act
            on.
        missing_data:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/MissingDataPolicy'
              description: >-
                Omit to get a policy chosen from the metric: `ignore` for
                `trace.*`

                (sparse — a gap is absence of data, not a recovery) and
                `not_breaching`

                for everything else.
        name:
          type: string
        period_seconds:
          type: integer
          format: int32
          description: Length of one evaluation period in seconds (multiple of 60, >= 60).
        project_id:
          type:
            - string
            - 'null'
          description: Scope the alarm to a project. Omit for an org-level alarm.
        stat:
          $ref: '#/components/schemas/AlarmStat'
        threshold:
          type: number
          format: double
    CreateAlarmOut:
      type: object
      required:
        - id
        - channel_id
        - channel_action
      properties:
        channel_action:
          $ref: '#/components/schemas/ChannelAction'
        channel_id:
          type: integer
          format: int64
        id:
          type: integer
          format: int64
    ErrorMessage:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Machine-readable error code (e.g. `ALREADY_EXISTS`, `BAD_REQUEST`).
        message:
          type: string
          description: Human-readable description.
    CreateChannelReq:
      type: object
      description: |-
        Channel reference embedded in another resource's create body (alarms).
        Either reuse by `id`, or supply `type` + `config` inline.
      properties:
        config:
          type: object
          description: Omitted or null when linking by `id` only.
        id:
          type:
            - string
            - 'null'
          description: >-
            Existing `NotificationChannels.id` for this org. When set, `type` /
            `config` / `name` are ignored.
        name:
          type:
            - string
            - 'null'
        type:
          $ref: '#/components/schemas/NotificationChannelType'
    AlarmComparison:
      type: string
      enum:
        - gt
        - gte
        - lt
        - lte
    AlarmDims:
      type: object
      description: |-
        Metric dimension filters. Omitted = aggregate across that dimension.

        Not `Copy` since `label` is owned.
      properties:
        deployment_id:
          type:
            - string
            - 'null'
        label:
          type:
            - string
            - 'null'
          description: >-
            `Metrics.label` — the span name behind a trace-derived metric.
            Scopes

            the alarm to one tool/step instead of the sum over every span the

            filter matched.
        project_env_id:
          type:
            - string
            - 'null'
        project_id:
          type:
            - string
            - 'null'
        workforce_id:
          type:
            - string
            - 'null'
          description: Agent / workflow app (`OrgsApps.id`).
    MissingDataPolicy:
      type: string
      enum:
        - not_breaching
        - breaching
        - insufficient
        - ignore
    AlarmStat:
      type: string
      description: >-
        Aggregation applied to each evaluation period. Percentiles run from p0.1
        to p99.9 in tenths; equivalent spellings such as p95.0 are accepted and
        returned canonically as p95, while a finer value such as p95.05 is
        rejected rather than rounded. They are exact rather than interpolated
        from bins, so they are only available on app.run.duration_ms and cannot
        be used with denominator_metric_name.
      examples:
        - p99
      pattern: ^(avg|sum|count|min|max|p\d+(\.\d+)?)$
    ChannelAction:
      type: string
      description: How the request was wired to `NotificationChannels`.
      enum:
        - linked
        - reused
        - created
    NotificationChannelType:
      type: string
      enum:
        - email
        - slack
  securitySchemes:
    bearer_auth:
      type: http
      scheme: bearer
      description: >-
        Timbal API key. Obtain your API key from the Timbal platform settings.
        See [Authentication](/api-reference/authentication) for more
        information.

````