Routines and triggers

Developer·3 minutes to read

Verified against docs/superpowers/specs/2026-09-15-p4-a-typed-http-api-openapi-webhooks.md §4.3, §4.4, §5.1, §6; re-verified against the code at close-out.

What it does

A routine is one bot or one group, an instruction, and one or more triggers; these endpoints create it, read it, change it, delete it, and add or remove a trigger (F-RT-1 to F-RT-4 — the routine record, the editor's fields, and the event-trigger menu).

Authentication

An organisation API key. GET needs scope read or wider; the writes need write or wider. A routine runs as the key's person, so the bot it names must be one that person can reach. See Authentication and keys.

Request

GET /routines

<!-- generated:begin GET /routines --> <!-- generated:end GET /routines -->

POST /routines

<!-- generated:begin POST /routines --> <!-- generated:end POST /routines -->

GET /routines/{id}

<!-- generated:begin GET /routines/{id} --> <!-- generated:end GET /routines/{id} -->

PATCH /routines/{id}

<!-- generated:begin PATCH /routines/{id} --> <!-- generated:end PATCH /routines/{id} -->

DELETE /routines/{id}

<!-- generated:begin DELETE /routines/{id} --> <!-- generated:end DELETE /routines/{id} -->

POST /routines/{id}/triggers

<!-- generated:begin POST /routines/{id}/triggers --> <!-- generated:end POST /routines/{id}/triggers -->

DELETE /routines/{id}/triggers/{triggerId}

<!-- generated:begin DELETE /routines/{id}/triggers/{triggerId} --> <!-- generated:end DELETE /routines/{id}/triggers/{triggerId} -->

GET /routines takes an optional botId. A routine's name is 1 to 80 characters and its instruction 1 to 8 000; it is active by default, and a create may carry up to ten triggers in the same call, which are written in one transaction with the routine.

A trigger is one of two shapes. A schedule trigger carries a cron expression and a timezone; the product's cron has a one-minute floor. An event trigger carries a kind from the product's own list and a configuration object, which the repository validates a second time against that kind's own schema — so a configuration the API accepts at the outer schema can still be refused as invalid_request with the failing path named.

curl -sS -X POST "$BOTSEON_API_ORIGIN/api/v1/routines" \
  -H "authorization: Bearer $BOTSEON_API_KEY" \
  -H "idempotency-key: 7a2b1c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d" \
  -H "content-type: application/json" \
  -d '{"botId":"0f3a2b71-6c4d-4a1e-9b2c-7d5e8f0a1b2c","name":"Morning triage","instruction":"Sort the overnight mail and flag anything that needs an answer today.","triggers":[{"kind":"schedule","cron":"0 7 * * 1-5","timezone":"Europe/Copenhagen"}]}'
const res = await fetch(`${process.env.BOTSEON_API_ORIGIN}/api/v1/routines?limit=50`, {
  headers: {
    authorization: `Bearer ${process.env.BOTSEON_API_KEY}`,
    accept: 'application/json',
  },
});
if (!res.ok) throw new Error(`routines: ${res.status}`);
const page = (await res.json()) as {
  data: Array<{ id: string; name: string; active: boolean; nextFireAt: string | null }>;
  nextCursor: string | null;
};
for (const routine of page.data) console.log(routine.name, routine.active, routine.nextFireAt);

Response

{
  "id": "5d6e7f80-1a2b-4c3d-9e0f-1a2b3c4d5e6f",
  "botId": "0f3a2b71-6c4d-4a1e-9b2c-7d5e8f0a1b2c",
  "groupConversationId": null,
  "name": "Morning triage",
  "instruction": "Sort the overnight mail and flag anything that needs an answer today.",
  "active": true,
  "notifyOnFinish": true,
  "lastFiredAt": null,
  "nextFireAt": "2026-09-21T05:00:00.000Z",
  "triggers": [
    {
      "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
      "kind": "schedule",
      "cron": "0 7 * * 1-5",
      "timezone": "Europe/Copenhagen",
      "config": {},
      "createdAt": "2026-09-18T08:00:00.000Z"
    }
  ],
  "createdAt": "2026-09-18T08:00:00.000Z"
}

Exactly one of botId and groupConversationId is set: a routine belongs to one bot or to one group. nextFireAt is the scheduler's own next time and is null for a routine with no schedule trigger.

Errors

StatusCodeWhenWhat to do
401unauthenticatedThe credential is missing, refused, or below the scope the route needs.Use a write key for the writes.
400invalid_requestThe cron expression, the timezone or an event trigger's configuration failed validation, a field is outside its bounds, or the POST carries no Idempotency-Key.Read details for the failing path.
404not_foundNo such routine or trigger, or the bot is not visible to this key's person.List first.
429quota_exceededThe organisation's routine ceiling is reached.Delete a routine first.
402payment_requiredThe organisation needs a trial or a payment.Settle the account, then retry.
429rate_limitedA bucket is full.Wait for Retry-After.

Rate limits

Every call on this page is counted against two one-minute buckets: 300 requests per key and 1 000 per organisation. A full bucket answers rate_limited with Retry-After: 60; wait that many seconds and retry. Failed authentication is counted separately, against the caller's address, so a working key's allowance is never spent by somebody else's bad credential. See Envelopes, errors, idempotency and paging.

Last verified against build c0f77aa.