Skip to content

Triggers API ​

Create, change and fire triggers: the per-agent way to start an agent on its own. A trigger is a cron schedule, a one-off time, a manual button or an incoming webhook (one type each), sends a prompt with optional variables, and creates an agent run. An agent can have several triggers, for example a schedule trigger and a webhook trigger. Concepts, reliability and examples are in the Triggers guide and the Webhook triggers guide; this page is the route reference.

All routes live under /api/client/v1, take Authorization: Bearer cpeer_… and need the agents service (GET read, everything else write). A token sees only its own project. Field names are snake_case; timestamps are unix seconds (except a once trigger's run_at, see below); errors use { "error": { "type", "message", "code" } }. The one exception to the token is the public hook endpoint, which senders call with the trigger's secret.

Endpoints ​

Endpoint
GET /agents/{agentKey}/triggersList an agent's triggers: { "object": "list", "data": […], "has_more": false }
POST /agents/{agentKey}/triggersCreate
GET /agents/{agentKey}/triggers/{triggerId}Read
PATCH /agents/{agentKey}/triggers/{triggerId}Update
DELETE /agents/{agentKey}/triggers/{triggerId}Delete: { "id", "object": "agent.trigger.deleted", "deleted": true }
POST /agents/{agentKey}/triggers/{triggerId}/runFire now (webhook triggers: with a sample payload, or a dry_run)
POST /agents/{agentKey}/triggers/{triggerId}/webhook/rotate-secretRotate the webhook secret
GET /agents/{agentKey}/triggers/{triggerId}/deliveriesThe delivery log of a webhook trigger
GET /agents/runs?trigger_id={triggerId}History: the runs the trigger created
POST /api/public/hooks/{tenantId}/{hookId}Incoming webhook: no token

The dashboard has the same set under /api/agents/{agentId}/triggers… (browser session, agents permission). It uses the same snake_case fields and the same error envelope as the client API; the only difference is that Run now takes no Idempotency-Key there.

The trigger object ​

json
{
  "id": "6720b4c3d2e1f0a9b8c7d6e5",
  "object": "agent.trigger",
  "agent": "support-bot",
  "name": "Morning ticket digest",
  "description": null,
  "enabled": true,
  "type": "schedule",
  "schedule": { "cron": "0 9 * * 1-5", "timezone": "Europe/Istanbul" },
  "prompt": "Summarise the tickets opened since yesterday 09:00.",
  "variables": null,
  "version": null,
  "class": "deferred",
  "metadata": { "report": "morning-digest" },
  "callback": { "url": "https://hooks.example.com/digest", "signed": true },
  "response_format": null,
  "next_fire_at": 1792389600,
  "last_fire_at": 1792303201,
  "last_run_id": "run_65f1c0a2b9d3e4f5a6b7c8d9",
  "last_status": "started",
  "last_error": null,
  "created_at": 1791622800,
  "updated_at": 1791622800
}
Field
type and its sub-objectWhat starts the trigger: see trigger types. One sub-object only, named after the type.
promptThe message each fire sends, up to 32 000 characters. For a webhook trigger it is a template.
variablesValues for the agent's variables, or null. For a webhook trigger a mapping of variable names to values or "{{payload.…}}" tags.
versionA published version number, or null for the published version at fire time.
classdefault or deferred.
metadataString pairs copied onto each run (at most 16 keys); {} when none.
callback{ url, signed } or null. The secret is write-only.
response_formatA JSON-schema answer format (as on runs) or null.
next_fire_atWhen the scheduler will fire next (unix seconds); null for manual and webhook triggers, disabled triggers and finished one-offs.
last_fire_at, last_run_idThe latest fire and the run it created.
last_status, last_errorstarted when the run was created, failed with the reason when it could not be (for example invalid variables: customer_id: Required). This is about creating the run; the run has its own status.
created_at, updated_atUnix seconds.

Trigger types ​

A trigger has exactly one type, and the settings of that type sit in a sub-object of the same name. The sub-objects of the other types are not present.

typeSub-object
schedule"schedule": { "cron": "0 9 * * 1-5", "timezone": "Europe/Istanbul" }. 5-field cron; timezone is an IANA name (default UTC).
onceOn write "once": { "run_at": "2026-10-18T06:00:00Z" } or "once": { "delay_seconds": 3600 }; run_at is ISO-8601 or unix seconds and must not be in the past. Read back, once.run_at is an ISO-8601 string.
manualNone: "type": "manual" alone.
webhookSee webhook trigger.

Webhook trigger ​

json
{
  "type": "webhook",
  "webhook": {
    "url": "https://console.example.com/api/public/hooks/65f1c0a2b9d3e4f5a6b7c8d9/wh_Zk3p0xH2m5Qe8TnV1aBcDe7Y",
    "preset": "github",
    "auth": "github_signature",
    "header_name": null,
    "secret_hint": "9F2c",
    "secret_source": "generated",
    "previous_secret_valid_until": null,
    "events": ["pull_request"],
    "filter": [ { "path": "payload.action", "op": "in", "value": ["opened", "synchronize"] } ],
    "include_payload": true
  }
}
FieldOn writeOn read
presetRequired on create: github, gitlab, azure_devops or generic.
authOne the preset offers (default: the first): github → github_signature; gitlab → gitlab_token, standard_webhooks; azure_devops → basic, header; generic → bearer, signature, header, standard_webhooks.
header_nameWith auth: "header": the header that carries the secret, like x-my-token (default x-cognipeer-token). Names that carry signatures or routing are refused.null unless auth is header.
secretOnly with standard_webhooks: the sender's whsec_… secret (base64 that decodes to 24 to 64 bytes). Any other auth mode refuses it: the secret is generated.Only in the answer that creates or replaces a generated secret, once; never after that.
eventsUp to 50 event names (A-Za-z0-9._:-, at most 64 characters); empty = all events.
filterUp to 20 rules { path, op, value? }, all of which must match. op: eq, neq, in, not_in, exists, not_exists, contains, starts_with, glob. in and not_in take a list of 1 to 50 values; exists and not_exists take none. Reference.
include_payloadBoolean, default true: append the event JSON (up to 32 KiB) to the message.
url (the address a sender posts to), secret_hint (the last four characters), secret_source (generated or provided), previous_secret_valid_until (unix seconds while a rotated-out secret is still accepted, else null).

The first time a secret is generated, which is on create, the answer carries it once, inside webhook:

json
{ "id": "6720b4c3d2e1f0a9b8c7d6e5", "object": "agent.trigger", "type": "webhook", "…": "…",
  "webhook": { "url": "https://console.example.com/api/public/hooks/…/wh_…", "secret": "whsec_…", "secret_hint": "9F2c", "…": "…" } }

The same webhook.secret appears on an update that generates a new secret (changing from standard_webhooks to another auth mode). It is never returned again.

A PATCH with a webhook sub-object keeps the hook id (so the URL) and the secret, with two exceptions: changing to standard_webhooks needs the sender's secret, and changing from it generates a new secret. A webhook sub-object sent back unchanged is not treated as a change. Changing type (together with the new type's sub-object) is allowed; a trigger that stops being a webhook stops answering at its URL (404).

Create ​

POST /api/client/v1/agents/{agentKey}/triggers
FieldType
namestringRequired, up to 200 characters.
descriptionstringUp to 2 000 characters.
enabledbooleanDefault true.
typestringRequired. schedule, once, manual or webhook.
schedule, once, webhookobjectThe settings of the chosen type: schedule for type: "schedule", and so on. manual has none.
promptstringUp to 32 000 characters. Required unless the agent declares variables and variables is given. For a webhook trigger: a template of at most 16 KiB, required unless include_payload is on or variables is given.
variablesobjectValidated against the agent's declared variables; defaults apply at fire time. For a webhook trigger a mapping, checked at save: every name is declared, tags parse, other values have the declared type.
versionnumber | nullPin a published version.
classstringdefault (default) or deferred.
metadataobjectUp to 16 string pairs.
callback{ url, secret? }Same rules as on a run: public http(s) URL, secret 16–256 characters.
response_formatobjectOptional structured-output format.
bash
curl -X POST "$CONSOLE_URL/api/client/v1/agents/support-bot/triggers" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "name": "Morning ticket digest",
    "type": "schedule",
    "schedule": { "cron": "0 9 * * 1-5", "timezone": "Europe/Istanbul" },
    "prompt": "Summarise the tickets opened since yesterday 09:00.",
    "class": "deferred"
  }'

201 with the trigger (and, for a webhook trigger, the one-time webhook.secret).

Errors: 400 invalid_trigger for any rejected field, with error.errors listing { "field", "message" } for each (schedule.cron, webhook.preset, webhook.secret, webhook.filter[0].op, prompt, variables.customer_id, …); 400 invalid_request when the body is not a JSON object and 400 invalid_json when it is not JSON; 404 agent_not_found.

An agent can have several triggers: call create again for each one.

Update ​

PATCH /api/client/v1/agents/{agentKey}/triggers/{triggerId}

Accepts any field of create; omitted fields are untouched, null clears a nullable one. callback: null removes the callback; inside callback, an omitted secret or the mask keeps the stored secret and "" removes it. Changing the type or its settings, or enabled, recomputes next_fire_at; enabled: false stops scheduled fires and makes a webhook trigger answer 200 with accepted: false (manual run still works). 200 with the trigger; 404 trigger_not_found; 400 invalid_trigger as on create.

Delete ​

DELETE …/triggers/{triggerId} removes the trigger and its delivery log. Runs it created stay. 200 { "id", "object": "agent.trigger.deleted", "deleted": true }; 404 trigger_not_found.

Run now ​

POST /api/client/v1/agents/{agentKey}/triggers/{triggerId}/run

Fires the trigger immediately, whatever its type or enabled state. The body is optional:

Field
variablesReplaces (merges over) the trigger's variables for this run.
metadataMerged over the trigger's metadata for this run.
waitSeconds to hold the request, as on the runs API. Prefer: wait=N works too.
payloadWebhook triggers only: a sample event (a JSON object or array).
headersWebhook triggers only: sample headers (an object of string values, at most 50).
dry_runWebhook triggers only: true evaluates the sample and starts nothing.

An Idempotency-Key header makes the call safe to retry (client API only). 200 with the run if it finished inside wait, 202 otherwise (with Location and Retry-After). The wait stops if the client disconnects; the run carries on. The run has trigger: "manual" and trigger_id set, and counts as the trigger's last fire. Its spend, run limits, quota and usage are counted against the caller and the API token that pressed Run now (the other fires count against the trigger's last updater, or its creator while nobody has changed it); the session stays owned by system:trigger:<id>.

With a webhook trigger. payload and headers are treated as a delivery would be, but they are data, never templates: the event name and delivery id are read from them the way the preset reads them, the prompt template and the variable mapping are rendered over them, and the run is built the way a delivery's is (external-data wording included). Events and Filter are not applied and nothing is de-duplicated. Without a payload the prompt is rendered over an empty event and nothing external is added.

With dry_run: true the answer is 200:

json
{
  "object": "agent.trigger.dry_run",
  "matched": false,
  "reason": "rule 2 (payload.pull_request.draft neq) did not match (value: boolean)",
  "event": "pull_request",
  "delivery_id": null,
  "prompt": "This run was started by an incoming webhook (github · pull_request). …",
  "variables": null,
  "errors": []
}

matched says whether Events and Filter would let the delivery through (reason is why not); prompt and variables are what a run would get, or null with errors listing { field, message } when the template or the mapping cannot be applied.

StatusCode
400invalid_variables, invalid_metadata, invalid_wait, invalid_requestA field was rejected. invalid_request also covers payload, headers or dry_run sent to a trigger that is not a webhook trigger.
400invalid_payload, invalid_headersThe sample is not an object or array, or the headers are not an object of strings.
400template_error, invalid_variablesThe webhook turn cannot be built (error.errors has the fields).
400agent_inactiveThe agent is not active.
404trigger_not_found, agent_not_found
409idempotency_key_conflictThe key belongs to a different request.
429agent_run_concurrency_limit, spend_limit_exceeded, agent_quota_exceededCapacity, the spend limit, or the quota; with Retry-After.

Rotate the webhook secret ​

POST /api/client/v1/agents/{agentKey}/triggers/{triggerId}/webhook/rotate-secret
Field
grace_secondsA whole number from 0 to 86 400 (default 0): how long the current secret keeps working beside the new one. 0 also ends an earlier grace.
secretstandard_webhooks only, and required there: the sender's new whsec_… secret. Refused for other auth modes.

200:

json
{ "object": "agent.trigger.webhook_secret", "secret": "whsec_…", "secret_hint": "Qm4x", "previous_valid_until": 1792306800 }

secret is present only for a generated secret, and only in this answer. Errors: 400 trigger_not_webhook when the trigger has another type, 400 invalid_trigger for a bad grace_seconds or secret, 404 trigger_not_found.

Deliveries ​

GET /api/client/v1/agents/{agentKey}/triggers/{triggerId}/deliveries?limit=20&before={id}

The log of requests that reached the trigger's URL, newest first, kept for 7 days. limit is 1 to 100 (default 20; otherwise 400 invalid_limit); pass the last id as before for the next page while has_more is true.

json
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "id": "6720b4c3d2e1f0a9b8c7d6f0",
      "object": "agent.trigger.delivery",
      "received_at": 1792303200,
      "outcome": "accepted",
      "http_status": 202,
      "event": "pull_request",
      "delivery_id": "72d3162e-cc78-11e3-81ab-4c9367dc0958",
      "run_id": "run_65f1c0a2b9d3e4f5a6b7c8d9",
      "reason": null,
      "bytes": 8123
    }
  ]
}
Field
outcomeaccepted (a run started), duplicate, filtered, ignored (a ping, a test, a disabled trigger), unauthorized, rejected (a rate, capacity, spend or quota limit) or failed.
http_statusWhat Console answered.
event, delivery_idAs read from the delivery; null when not found or not valid.
run_idThe run that started, or the one a duplicate repeats.
reasonA short note of at most 300 characters, such as which filter rule did not match. It never holds payload text or a secret.
bytesThe body size.

Payloads are not stored. Rows other than accepted are sampled (up to 10 a minute per trigger on each node).

The public hook endpoint ​

POST /api/public/hooks/{tenantId}/{hookId}

The URL a sender posts to is webhook.url of a webhook trigger. There is no API token: the tenantId and the random hookId (wh_…) locate the trigger, and every delivery proves the trigger's secret.

AuthThe sender sends
github_signatureX-Hub-Signature-256: sha256=<hex HMAC-SHA256 of the body>
gitlab_tokenX-Gitlab-Token: <secret>
basicAuthorization: Basic base64(<any user>:<secret>)
bearerAuthorization: Bearer <secret>
header<header_name>: <secret>
signatureX-Cognipeer-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<body>">, with t within 5 minutes of now
standard_webhookswebhook-id, webhook-timestamp (within 5 minutes), webhook-signature: v1,<base64 HMAC-SHA256 of "<id>.<timestamp>.<body>"> with the base64-decoded secret

The body must be JSON (application/json or application/*+json, an object or an array, UTF-8, at most 64 levels deep) and at most AGENT_WEBHOOK_BODY_LIMIT_BYTES (default 5 MiB). Signatures are over the exact bytes received. The answers are tabulated in the guide: what Console answers. In short:

StatusAnswer
202{ "accepted": true, "run_id": "run_…", "status": "queued" }: a run was created.
200{ "ok": true, "event": "ping" | "test" } (a GitHub ping, an Azure DevOps test), or { "accepted": false, "reason": "disabled" | "event_not_selected" | "filtered" | "duplicate", "run_id"? }.
200For gitlab and azure_devops, also { "accepted": false, "reason": "rate_limited" | "concurrency_limit" | "spend_limit" | "quota_exceeded" | "invalid_variables" | "template_error" | "internal_error", "message" } once a delivery reached the run step.
400invalid_json; invalid_variables and template_error for github and generic.
401webhook_unauthorized
404webhook_not_found
413payload_too_large
415unsupported_media_type
429webhook_rate_limited (the request ceiling, or the run rate); for github and generic also agent_run_concurrency_limit, spend_limit_exceeded and agent_quota_exceeded. Always with Retry-After.
500internal_error (github, generic)
503webhook_unavailable, with Retry-After: 30

A started run has trigger: "webhook", channel: "trigger" and trigger_id, carries the labels webhook_event and webhook_delivery in its metadata, and is unattended. Environment: AGENT_WEBHOOK_BODY_LIMIT_BYTES, AGENT_WEBHOOK_RATE_LIMIT_PER_MINUTE (operator settings).

Firing, misfires and exactly-once ​

The scheduler looks for due triggers every 30 seconds (AGENT_TRIGGER_TICK_MS). A fire claims its slot (trigger id and fire time) in the database and uses the run idempotency key trigger:<triggerId>:<fireTime>, so a trigger fires once even with several Console nodes. A slot missed by up to one hour (AGENT_TRIGGER_MISFIRE_GRACE_MS) fires once to catch up; an older slot is skipped. Each fire is a run in the trigger's class, with channel: "trigger" and trigger: "schedule", "once", "manual" or "webhook". A webhook delivery uses the key hook:<triggerId>:<hash> (de-duplication).

Events: trigger.fired and trigger.failed. trigger.failed is sent at most once an hour for each trigger and reason, so a schedule that is refused on every tick does not flood a subscriber.

Errors ​

StatusCode
400invalid_triggerA trigger field was rejected on create or update; error.errors lists { field, message }.
400invalid_request, invalid_json, trigger_not_webhook, invalid_limit, and those under Run nowThe body or a parameter is not valid.
404agent_not_found, trigger_not_foundThe agent or trigger does not exist in the token's project.
409idempotency_key_conflictOn run-now only.
429agent_run_concurrency_limit, spend_limit_exceeded, agent_quota_exceededOn run-now: capacity, the spend limit, or the quota.

SDK: client.agents.triggers.list() / create() / get() / update() / delete() / run() / rotateWebhookSecret() / deliveries() and signTriggerWebhook() (4.0.0+). See the SDK reference.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub