---
title: "Create a routine"
method: POST
path: "/api/v1/ai/routines"
tags: ["AI Routines"]
---

# Create a routine

`POST /api/v1/ai/routines`

Create a routine that runs a saved prompt on a schedule and delivers the AI response through a single destination — email (one or more recipients / user groups) or Slack (a single channel or direct message). Each scheduled run executes once using the routine owner's permissions, and every recipient receives the same result. Organization API keys can pass `?userId=<membershipId>` to create the routine for a specific organization member. For organizations with conditional routines enabled and `gating` 'auto' (the default), the prompt's alert intent is derived in this same request: an alert prompt persists a conditional routine (the response carries the decision and a dry-run of its trigger), while an alert whose trigger can't be composed or verified is a 400 with a stable `code` and no routine is created.

## Query parameters

- `userId` string, uuid — Target user membership ID (for org-scoped API keys)

## Request body

- RoutineCreateBody
  - `branchId` string, uuid — Optional branch ID for the model. Must be a branch of the shared model specified by modelId.
  - `description` string — Optional human-readable notes about the routine. Display-only — never used as model input.
  - `gating` 'auto' | 'unconditional' — How the routine's delivery gating is decided. 'auto' (the default) derives it from the prompt when conditional routines are enabled for the organization: a prompt with an alert condition ("only if…", "alert me when…") becomes a conditional routine that delivers only when its trigger fires. 'unconditional' always creates a routine that delivers on every run, without evaluating the prompt for a condition.
  - `modelId` string, uuid, required — The UUID of the model the prompt runs against. Must be a shared model, or a shared-extension model usable as a workbook base.
  - `name` string, required — Customer-visible name of the routine. Used as the email subject for email destinations, and shown on Slack deliveries.
  - `prompt` string, required — Natural language prompt Omni runs on each scheduled run.
  - `schedule` string, required — Six-field cron expression (minute, hour, day-of-month, month, day-of-week, year; use `?` for an unspecified day field). Minimum frequency is once per hour; contact Omni support if you need more frequent scheduling.
  - `timezone` string, required — IANA timezone identifier used to evaluate the schedule.
  - `topicName` string — Topic name to scope query generation. If omitted, the AI picks the best topic.
  - `destination` union, required — Single delivery destination for the routine. To send results to multiple destinations, create one routine per destination. Omni runs the prompt once per scheduled run using the routine owner's permissions, and every recipient receives the same result regardless of their own permissions.
    - RoutineEmailDestination
      - `recipientEmails` string[] — Email addresses that receive each scheduled run of the routine.
      - `type` 'email', required — Selects email delivery — each scheduled run is sent to the listed email recipients and user groups.
      - `userGroupIds` string[] — User group IDs whose active members receive each scheduled run. Omni expands each group to the members' current email addresses when the routine runs.
    - RoutineSlackDestination
      - `recipientId` string, required — The Slack channel ID (e.g. "C01234567") or user ID (e.g. "U01234567") that receives each scheduled run. Exactly one recipient per Slack routine.
      - `slackRecipientType` 'channel' | 'users', required — Whether `recipientId` is a Slack channel or a user (delivered as a direct message).
      - `type` 'slack', required — Selects Slack delivery — each scheduled run is posted to one Slack channel or sent as a direct message to one user.

## Response `201`

Routine created successfully. `trigger` is present only when the prompt was evaluated for a delivery condition (see `gating`).

- RoutineCreateResponse
  - `id` string, uuid, required — The unique identifier for the newly created routine.
  - `trigger` RoutineCreateTrigger — The gating decision and trigger dry-run. Present only when the prompt was evaluated for a delivery condition: conditional routines enabled for the organization and gating 'auto'. Absent for gating 'unconditional' and for organizations without conditional routines.
    - `conditionPrompt` string — The alert condition in plain language, as understood from the prompt. Present only for an alert decision.
    - `conditionType` 'RESULTS_CHANGED' | 'RESULTS_UNCHANGED' | 'RESULTS_PRESENT' | 'RESULTS_MISSING' — How the trigger query gates delivery. Present only for an alert decision. RESULTS_PRESENT fires when the trigger query returns rows, RESULTS_MISSING when it returns none, RESULTS_CHANGED when its results differ from the previous scheduled run, RESULTS_UNCHANGED when they are identical.
    - `decision` 'alert' | 'summary', required — The gating decision derived from the prompt: 'alert' delivers only when the trigger query's condition is met, 'summary' delivers on every scheduled run.
    - `deliverablePrompt` string — The prompt the AI answers when the condition fires: the request with the condition clause removed. Present only for an alert decision.
    - `dryRun` RoutineTriggerDryRun — Result of the create-time test run of the trigger query. Present only for an alert decision.
      - `conditionMetNow` boolean — Whether the trigger's condition was met against the data at create time. Present for RESULTS_PRESENT and RESULTS_MISSING triggers. RESULTS_CHANGED and RESULTS_UNCHANGED triggers omit it: their first scheduled run captures the comparison baseline silently, so the earliest possible alert is the run after.
      - `rowCount` number, required — Rows the trigger query returned when it was test-run at create time.

## Other responses

- `400` — Invalid request body, recipient configuration, schedule, or timezone. Also returned when the schedule is more frequent than the organization allows, and, with a stable `code`, when the prompt reads as an alert whose trigger could not be composed or verified (no routine is created; reword the prompt or pass `gating: "unconditional"`).
- `401` — Missing or invalid API key.
- `402` — The organization's AI credit limit has shut off AI calls, so trigger derivation could not run.
- `403` — AI routines or AI query generation are not enabled for the organization, or the API key cannot act on behalf of the requested user.
- `404` — Model, branch, or topic not found, or not accessible to the requested user.
- `429` — The resolved user already has the maximum number of active routines.

---

[API](https://skmtc.net/omniapp/apis/omni-api.md) · [All operations](https://skmtc.net/omniapp/apis/omni-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/omniapp/omni-api/versions/de7cac8b5983/schema)
