---
title: "Create a project monitor"
method: POST
path: "/v1/projects/{projectId}/monitors"
tags: ["Monitors"]
---

# Create a project monitor

`POST /v1/projects/{projectId}/monitors`

## Request

- Base URL: `https://api.spice.ai`
- URL: `https://api.spice.ai/v1/projects/{projectId}/monitors`
- Auth: HTTP bearer

Creates a project monitor from a runtime template. Requires `monitors:write`. A monitor evaluates a runtime metric on a schedule and fires when the comparison holds; to fire on matching dataset rows instead, create a data reaction at `/v1/projects/{projectId}/reactions`.

## Path parameters

- `projectId` integer, required

## Headers

- `X-Org-Name` string

## Request body

- object
  - `name` string, required: Display name. Unique, case-insensitively, among the project's live monitors and data reactions. A collision answers 409.
  - `description` string
  - `kind` 'runtime': Optional assertion that `templateId` names a monitor. A value that disagrees with the template is refused rather than ignored; omit it and the template decides.
  - `templateId` 'query_failures' | 'query_latency_p95' | 'query_latency_p99' | 'llm_failures' | 'dataset_refresh_errors' | 'http_5xx' | 'flight_failures' | 'memory_working_set' | 'container_cpu' | 'dataset_status' | 'instance_health', required: The runtime template the monitor evaluates. `query_latency_p95` is retained for existing monitors but cannot be used to create new ones; use `query_latency_p99`. `container_cpu` is available only on managed projects with a defined CPU limit. A data-reaction template is refused here. Create those at `/v1/projects/{projectId}/reactions`.
  - `spec` object, required: Monitor parameters. Which properties apply is decided by `templateId`. Each property below names the templates that read it, and a property no monitor template reads is stored rather than rejected. `spec` is shared with data reactions, so it accepts their properties too; `model` is the one that is refused rather than stored.
    - `op` 'GT' | 'GEQ' | 'LT' | 'LEQ' | 'EQ' | 'NEQ', required: Comparison applied to the metric. Required on every monitor spec. `instance_health` requires `GT`.
    - `threshold` number, required: Value compared against the metric. HTTP failure monitors count estimated 5xx responses in the selected window. Rate-based conditions use responses per second. `dataset_refresh_errors` counts refresh errors in the window. `query_latency_p99` uses milliseconds. `memory_working_set` uses percent of the instance memory limit. A stored memory threshold above 1000 uses bytes. `container_cpu` uses percent of the defined CPU limit. `dataset_status` compares load-state numbers. `instance_health` requires 0. Every monitor spec requires a threshold.
    - `window` '1m' | '5m' | '15m' | '30m' | '1h': Range evaluated by `query_failures`, `query_latency_p99`, `llm_failures`, `dataset_refresh_errors`, `http_5xx`, `flight_failures`, and `container_cpu`. `memory_working_set` and `dataset_status` read the current value and ignore this field. `instance_health` reads current failure states and uses a fixed five-minute eviction observation window, independent of this field. Data reactions store this field but do not use it.
    - `sustainSecs` integer: Sustain delay in seconds. Failure monitors default to 0. Other monitors default to 300. Explicit values are preserved.
    - `severity` 'warn' | 'critical': Severity the monitor fires at. Recorded on each monitor event and carried into the email or webhook notification.
    - `dataset` string: Read by `dataset_status`, which watches this one dataset. Omit it to match any dataset in the project.
    - `includeDetails` boolean: When true, the recorded event and the notification may carry a `details` string quoted from the firing payload when it contains useful information. Off by default, because that payload may be sensitive. A notification's own summary, description, and observed value do not depend on it.
    - `value` union: Read by `dataset_status` for notification wording only, and only when `threshold` is not one of the state numbers above and this names a state. It never changes what the monitor evaluates. Otherwise a data-reaction property.
      - string
      - number
    - `column` string: A data-reaction property that no monitor evaluates. Leave it unset. A monitor carrying both `column` and `dataset` has its notification describe that row predicate instead of its own metric comparison.
    - `datasets` string[]: A data-reaction property that no monitor reads.
    - `conditions` object[]: A data-reaction property that no monitor evaluates. Leave it unset. A monitor carrying conditions has its notification describe them instead of its own metric comparison. IS_NULL and IS_NOT_NULL omit value.
      - `dataset` string, required
      - `column` string, required
      - `op` 'EQ' | 'NEQ' | 'GT' | 'GEQ' | 'LT' | 'LEQ' | 'CONTAINS' | 'STARTS_WITH' | 'ENDS_WITH' | 'IS_NULL' | 'IS_NOT_NULL', required
      - `value` union: Required except for IS_NULL and IS_NOT_NULL.
        - string
        - number
    - `returnFields` object[]: Dataset fields returned by generated row-match queries. Include identity and all monitored fields.
      - `dataset` string, required
      - `columns` string[], required
    - `queryLanguage` 'cypher' | 'gql': A data-reaction property that no monitor reads.
    - `query` string: A data-reaction property that no monitor reads.
    - `model` object: Model transform. Data reactions only. Sending it with a monitor template is refused with 400, not ignored.
      - `model` string, required
      - `instructions` string, required
  - `target` union: A notification sink in a create or update request. Cluster monitors, project monitors and data reactions accept the same shape.
    - EmailSinkInput
      - `type` 'email', required
      - `recipientUserIds` integer[]: Organization members to email, by user ID.
      - `emails` string[]
    - HttpSinkInput
      - `type` 'http', required
      - `url` string, uri, required: The URL each notification is sent to, using `method`. Must be https and must resolve to a public address.
      - `method` 'POST' | 'PUT' | 'PATCH': A create that omits it uses POST. An update that omits it keeps the stored method.
      - `token` string: Bearer token sent with each delivery. Stored encrypted and never returned.
    - SlackSinkInput
      - `type` 'slack', required
      - `channelId` string, required: Slack conversation id. The organization must already have Slack connected.
      - `channelName` string
  - `targets` ClusterMonitorTarget[]: Full destination set. When present, this replaces `target` and is the complete list. At most one sink of each type.
    - union: A notification sink in a create or update request. Cluster monitors, project monitors and data reactions accept the same shape.
      - EmailSinkInput
        - `type` 'email', required
        - `recipientUserIds` integer[]: Organization members to email, by user ID.
        - `emails` string[]
      - HttpSinkInput
        - `type` 'http', required
        - `url` string, uri, required: The URL each notification is sent to, using `method`. Must be https and must resolve to a public address.
        - `method` 'POST' | 'PUT' | 'PATCH': A create that omits it uses POST. An update that omits it keeps the stored method.
        - `token` string: Bearer token sent with each delivery. Stored encrypted and never returned.
      - SlackSinkInput
        - `type` 'slack', required
        - `channelId` string, required: Slack conversation id. The organization must already have Slack connected.
        - `channelName` string
  - `recipientUserIds` integer[]: Legacy spelling of `target.recipientUserIds`, read only when an email target omits its own.

## Response `201`

Monitor created

- ProjectMonitor: The fields a project monitor and a data reaction share. `Reaction` and `ProjectMonitor` each narrow `kind` and describe their own `spec`.
  - `id` string, uuid, required: Identifier of the monitor or reaction. This is the value the get, update and delete operations take.
  - `org_id` integer, required: Organization that owns the project
  - `app_id` integer, required: Project the monitor or reaction belongs to
  - `created_by` integer, nullable, required: User who created it, when it was created by a user credential
  - `name` string, required: Display name, unique case-insensitively among the project's live monitors and data reactions
  - `description` string, nullable, required
  - `kind` 'runtime', required: The monitors endpoints only ever return `runtime`.
  - `status` 'active' | 'disabled', required: Whether it is evaluating
  - `template_id` string, required: Template it was created from
  - `spec` object, required: Monitor parameters. Which properties are meaningful is decided by `template_id`. The create operation's request body lists the properties each template reads.
  - `target` union, required: A configured sink. An HTTP bearer token is never returned.
    - EmailSinkView
      - `type` 'email', required
      - `recipientUserIds` integer[]
      - `emails` string[]
    - HttpSinkView
      - `type` 'http', required
      - `url` union, required: The sink's URL. An empty string means the stored sink no longer passes validation. Update the sink to replace it.
        - string, uri
        - ''
      - `method` 'POST' | 'PUT' | 'PATCH'
    - SlackSinkView
      - `type` 'slack', required
      - `channelId` union, required: The Slack channel ID. An empty string means the stored sink no longer passes validation. Update the sink to replace it.
        - string
        - ''
      - `channelName` string
  - `targets` ReactionSinkView[], required: Every configured sink, ordered email, then slack, then http. `target` is the first of these. An HTTP sink's token is never returned.
    - union: A configured sink. An HTTP bearer token is never returned.
      - EmailSinkView
        - `type` 'email', required
        - `recipientUserIds` integer[]
        - `emails` string[]
      - HttpSinkView
        - `type` 'http', required
        - `url` union, required: The sink's URL. An empty string means the stored sink no longer passes validation. Update the sink to replace it.
          - string, uri
          - ''
        - `method` 'POST' | 'PUT' | 'PATCH'
      - SlackSinkView
        - `type` 'slack', required
        - `channelId` union, required: The Slack channel ID. An empty string means the stored sink no longer passes validation. Update the sink to replace it.
          - string
          - ''
        - `channelName` string
  - `recipient_user_ids` integer[], required: Organization members emailed when the monitor fires. Empty when there is no email sink, and for an email sink addressed only by `emails`. When a created email sink named no recipients at all, the API stores the creating user; a machine credential has no user, so it stores none and the monitor emails the organization owner.
  - `last_fired_at` string, date-time, nullable, required: When it last fired, or null if it never has
  - `last_resolved_at` string, date-time, nullable, required: When it last resolved, or null if it never has
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `evaluation_unavailable_reason` 'cpu_limit_missing': Present when an existing Instance CPU monitor cannot evaluate because its CPU limit was removed.

## Other responses

- `400`: Invalid request body or project ID
- `401`: Unauthorized
- `403`: Insufficient scope
- `404`: Project not found, or the template is not available for this project (`code` is `monitor_template_unavailable`). Monitors run only on managed projects, and only released templates are available unless the organization has early access.
- `409`: A live monitor or data reaction in this project already has this name
- `413`: Request body too large
- `422`: Slack is not connected for the organization
- `500`: Internal server error
- `502`: Failed to provision monitor

---

[API](https://skmtc.dev/spice/apis/spice-cloud-control-plane-api.md) - [All operations](https://skmtc.dev/spice/apis/spice-cloud-control-plane-api/llms.txt) - [OpenAPI document](https://skmtc.dev/spice/apis/spice-cloud-control-plane-api/revisions/5f3b75dd847d?raw)
