Schedules
A chat schedule is a Job row of type chat that runs a stored prompt on a
recurring cadence. The three verbs below all resolve the chat through
chat.lookup, whose read_access scope limits the chat to the requester's own
username. They do not check any ai.* permission; the ability to see the
chat is the gate.
The schedule lives at /api/chats/{id}/schedule. A chat has at most one
schedule, so there are no list or per-id schedule routes: PUT creates it if
absent and updates it in place if present.
GET /api/chats/{id}/schedule
Retrieve the chat's schedule Job.
Authentication: Required (session)
Pre-blocks: chat.lookup
Response:
The Job for the chat (type: "chat"). nextRunAt is the computed next fire
time and timezone is the stored IANA zone. The recurrence is the
{ interval, intervalType } pair and the prompt that runs lives under
data.prompt.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/schedule"
},
"inf:job-action-order": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/schedule/_job-action-order"
}
},
"naturalId": "admin:daily-order-desk-summary",
"permissions": {
"assignTags": true,
"write": true,
"edit": true,
"run": true,
"delete": true,
"changeOwner": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "chat",
"tenant": "acme",
"slug": "daily-order-desk-summary",
"name": "Daily Order Desk Summary",
"description": null,
"nextRunAt": "2026-01-15T16:20:00.000Z",
"lockedAt": null,
"enabled": true,
"data": {
"alert": {
"enabled": true,
"condition": "anomalies detected"
},
"prompt": "Summarize yesterday’s Order Desk activity and flag anything unusual."
},
"interval": "0 9 1/1",
"startOn": "2026-01-15T16:20:00.000Z",
"intervalType": "Daily",
"endOn": null,
"notes": null,
"source": null,
"sourceId": null,
"timezone": "America/New_York",
"heartbeat": null,
"chatId": "00000000-0000-4000-8000-000000000001",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": "admin",
"folderId": null,
"datasetId": null,
"reportId": null,
"queryId": null,
"datasourceId": null,
"editingId": null,
"actions": []
}
A chat that has no schedule answers 404 (boom.notFound), which is distinct
from the 404 returned for a chat the requester cannot see.
{
"statusCode": 404,
"error": "Not Found",
"message": "Not Found"
}
PUT /api/chats/{id}/schedule
Create the schedule when the chat has none, or update the existing Job in place when it already has one.
Authentication: Required (session)
Pre-blocks: chat.lookup
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Job name. |
interval | string | No | Cron-ish recurrence expression (e.g. 0 9 1/1). Paired with intervalType. |
intervalType | string | No | Human-readable cadence label that accompanies interval (e.g. Daily, Weekly). |
startOn | date | No | When the schedule begins firing. |
endOn | date | No | When the schedule stops firing. null for indefinite. |
enabled | boolean | No | Whether the schedule is active. |
priority | number | No | Job priority. |
timezone | string | No | IANA zone the recurrence is evaluated in (e.g. America/New_York). Must be a valid IANA zone or the route answers 400. Omit (or send null) to inherit the requester's zone; on update, omit to keep the stored zone. |
data | object | No | Driver payload. Holds prompt (the text the schedule runs) and alert. |
data.prompt | string | Yes* | The prompt the schedule runs. The chat-job driver asserts this in beforeValidate, so a usable schedule must carry it. |
data.alert | object | No | Alert configuration: { enabled, condition }. |
There is no top-level alert field and no single interval: "daily" string;
the recurrence is the { interval, intervalType } pair, and alerts live under
data.alert. Anything outside the schema is stripped (stripUnknown).
Example Request:
{
"name": "Daily Order Desk Summary",
"interval": "0 9 1/1",
"intervalType": "Daily",
"startOn": "2026-01-15T09:00:00.000Z",
"enabled": true,
"timezone": "America/New_York",
"data": {
"prompt": "Summarize yesterday's Order Desk activity and flag anything unusual.",
"alert": { "enabled": true, "condition": "anomalies detected" }
}
}
Response:
Responds 200 with the created or updated Job (there is no 201 and no
Location header).
{
"name": "Daily Order Desk Summary",
"interval": "0 9 1/1",
"intervalType": "Daily",
"startOn": "2026-01-15T16:20:00.000Z",
"enabled": true,
"timezone": "America/New_York",
"data": {
"prompt": "Summarize yesterday’s Order Desk activity and flag anything unusual.",
"alert": {
"enabled": true,
"condition": "anomalies detected"
}
}
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/schedule"
},
"inf:job-action-order": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/schedule/_job-action-order"
}
},
"naturalId": "admin:daily-order-desk-summary",
"permissions": {
"assignTags": true,
"write": true,
"edit": true,
"run": true,
"delete": true,
"changeOwner": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"name": "Daily Order Desk Summary",
"interval": "0 9 1/1",
"intervalType": "Daily",
"startOn": "2026-01-15T16:20:00.000Z",
"enabled": true,
"timezone": "America/New_York",
"data": {
"alert": {
"enabled": true,
"condition": "anomalies detected"
},
"prompt": "Summarize yesterday’s Order Desk activity and flag anything unusual."
},
"type": "chat",
"chatId": "00000000-0000-4000-8000-000000000001",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"ownerId": "admin",
"nextRunAt": "2026-01-15T16:20:00.000Z",
"slug": "daily-order-desk-summary",
"description": null,
"lockedAt": null,
"endOn": null,
"notes": null,
"source": null,
"sourceId": null,
"_altid": null,
"datasetId": null,
"reportId": null,
"queryId": null,
"datasourceId": null,
"editingId": null,
"heartbeat": null,
"folderId": null
}
A second PUT updates the same schedule row in place. Omit timezone to keep
the stored zone.
{
"name": "Weekly Order Desk Summary",
"interval": "0 9 * * MON",
"intervalType": "Weekly",
"enabled": true,
"data": {
"prompt": "Summarize last week’s Order Desk activity.",
"alert": {
"enabled": false
}
}
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/schedule"
},
"inf:job-action-order": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/schedule/_job-action-order"
}
},
"naturalId": "admin:weekly-order-desk-summary",
"permissions": {
"assignTags": true,
"write": true,
"edit": true,
"run": true,
"delete": true,
"changeOwner": true,
"rename": true
},
"id": "00000000-0000-4000-8000-000000000002",
"type": "chat",
"tenant": "acme",
"slug": "weekly-order-desk-summary",
"name": "Weekly Order Desk Summary",
"description": null,
"nextRunAt": "2026-01-15T16:20:00.000Z",
"lockedAt": null,
"enabled": true,
"data": {
"prompt": "Summarize last week’s Order Desk activity.",
"alert": {
"enabled": false
}
},
"interval": "0 9 * * MON",
"startOn": "2026-01-15T16:20:00.000Z",
"intervalType": "Weekly",
"endOn": null,
"notes": null,
"source": null,
"sourceId": null,
"timezone": "America/New_York",
"heartbeat": null,
"chatId": "00000000-0000-4000-8000-000000000001",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"ownerId": "admin",
"folderId": null,
"datasetId": null,
"reportId": null,
"queryId": null,
"datasourceId": null,
"editingId": null,
"actions": []
}
The chat-job driver asserts data.prompt during validation, so a schedule
without a prompt is rejected even though data is optional at the route level.
DELETE /api/chats/{id}/schedule
Remove the chat's schedule Job.
Authentication: Required (session)
Pre-blocks: chat.lookup
Response:
Responds 200 with an empty body (Informer convention), not 204. The verb is
idempotent: deleting when no schedule exists still answers 200.
Scheduled chats are useful for daily report generation, periodic data analysis, automated monitoring alerts, and recurring summarization tasks.