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
| Status | Code | When | What to do |
|---|---|---|---|
| 401 | unauthenticated | The credential is missing, refused, or below the scope the route needs. | Use a write key for the writes. |
| 400 | invalid_request | The 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. |
| 404 | not_found | No such routine or trigger, or the bot is not visible to this key's person. | List first. |
| 429 | quota_exceeded | The organisation's routine ceiling is reached. | Delete a routine first. |
| 402 | payment_required | The organisation needs a trial or a payment. | Settle the account, then retry. |
| 429 | rate_limited | A 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.