---
title: "Create a trigger"
method: POST
path: "/v1/projects/{project_id}/triggers"
tags: ["Triggers"]
---

# Create a trigger

`POST /v1/projects/{project_id}/triggers`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/triggers`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Creates a new trigger for a project

## Request body

- CreateTriggerRequest
  - `name` string, required
  - `description` string
  - `type` 'manual' | 'webhook' | 'schedule' | 'event', required
  - `target_type` 'orchestration' | 'agent' | 'tool' | 'eval', required
  - `target_id` string, required
  - `action` string: Tool targets only — the action for mcp tools
  - `input` object
  - `tool_context` object: Caller context every firing forwards to the run it starts, so an agent whose tools authorize through `{{context:<key>}}` can be put on a schedule — a firing has no request to carry one. Each key is forwarded as one `X-Naturali-Context-<key>` header. **Write-only.** It is accepted here and never returned on a read, so the record cannot be used to recover a value. A value may be a `{{secret:sec_...}}` reference, which keeps the credential in the [secret](/docs/modules/secrets) store and leaves only its id on the trigger; it is resolved at fire time, so rotating the secret changes the next firing without touching the trigger. A reference is refused here rather than at fire time when it is not in that id form (a secret's name does not resolve) or names a secret that does not exist in this project.
  - `cron` string: 5-field cron expression (UTC). Required when type is schedule
  - `event_pattern` string: Internal-event subscription pattern. Required when type is event, rejected otherwise. `*` matches every event, `prefix.*` a namespace, or give an exact event name such as `documents.ingested`
  - `active` boolean
  - `policy_id` string

## Response `201`

Trigger created

- TriggerWithSecret
  - `id` string
  - `project_id` string
  - `name` string
  - `description` string, nullable
  - `type` 'manual' | 'webhook' | 'schedule' | 'event'
  - `target_type` 'orchestration' | 'agent' | 'tool' | 'eval'
  - `target_id` string: Public ID of the target resource (orchestration, agent, tool, or eval)
  - `action` string, nullable: Tool targets only — the action for mcp tools
  - `input` object, nullable: Static input, shallow-merged under fire-time input. For `eval` targets the effective input may carry `agent_version` and `baseline_run_id`, which are passed to the queued run.
  - `cron` string, nullable: 5-field cron expression (UTC). Present only for schedule triggers
  - `event_pattern` string, nullable: Internal-event subscription pattern. Present only for event triggers: `*`, `prefix.*`, or an exact event name
  - `active` boolean
  - `policy_id` string, nullable: Optional boundary policy that further restricts firings
  - `next_fire_at` string, date-time, nullable: Read-only, schedule triggers only. Server-computed next fire time
  - `created_at` string, date-time
  - `updated_at` string, date-time
  - `secret` string: Webhook triggers only. Returned only on create and rotate

## Other responses

- `400`: Bad request
- `401`: Unauthorized
- `403`: Forbidden

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
