Deliver a gateway report on a schedule
Run one of your reports every morning at a local time you choose, and have each dated run land in a colleague's in-app inbox.
On this page
Your team lead opens the checkout gateway report every morning before stand-up. Today you run it again by hand for them each day, and on the day you forget, they read yesterday’s numbers without knowing it. You want the report to run on its own at nine o’clock their time, and to be waiting for them when they sit down.
In this tutorial we’ll schedule a report you already own, read when it will run next, check what ran, open the delivered run as the recipient, then pause the schedule and delete it. By the end you’ll have a dated run in a colleague’s inbox that they opened with their own access, and a history that says when it ran and over which window.
Delivery is to the in-app inbox only. A schedule sends no email and no Slack message: a recipient sees what arrived by listing their inbox in the product, the CLI or an SDK.
What a report run keeps, and who can open it explains why each run keeps its own answer, and Keep a dated report of a gateway view makes the report this page schedules.
Every output block on this page is program output, captured by replaying the recorded answers in
sdk/spec/report-schedule-fixtures.json through a loopback server, so --base-url names
127.0.0.1 and a port that changes on every run. Yours names your deployment. Those answers were
recorded from the control plane’s schedule handler, dispatcher, inbox and report worker; the
analytics answer inside each run comes from a test reader that stands in for the analytics
service. In the recording, it-lead owns the report and the schedule, and it-analyst is the
colleague. The CLI writes a line naming each request to stderr, and the blocks keep it where a
terminal shows it.
Before you start
| You need | Why |
|---|---|
the tokentriage CLI and your deployment’s origin for --base-url |
every step is one cloud report command |
a management key in TOKENTRIAGE_MANAGEMENT_KEY |
the owner’s steps run as you |
an organization ID (org_…) and a workspace ID (t_…) |
schedules belong to one workspace |
a report you own (rpt_…) made from a view with a relative window |
only the report’s owner can schedule it, and only a relative window repeats |
| your colleague listed as a recipient of that report, and their subject ID | a schedule delivers only to the report’s owner or its recipients |
| your colleague’s own management key, for step 5 | an inbox is read with its owner’s credential |
metadata.read on the view’s whole scope |
a narrower block is answered as not found |
You don’t need cost.read. Without it, money cells arrive as unavailable rather than as numbers.
You don’t need a runtime key, and nothing here calls a model provider.
A report’s recipients are fixed when it is created. If yours lists nobody, create a new report
that names your colleague in recipient_subject_ids, as in step 2 of
Keep a dated report of a gateway view.
tokentriage cloud member list shows each member’s subject ID if you hold members.manage;
otherwise ask your colleague for theirs.
1. Read the report’s revision
A schedule pins the revision of the report you read, so a report that changed while you were
writing the schedule is caught instead of scheduled. The report’s own revision is in its JSON
document, so read it with tokentriage cloud report get --org … --report rpt_… --json:
$ tokentriage cloud report get --org org_K11111111111 --report rpt_S11111111111 --json \
--base-url http://127.0.0.1:6976
cloud: customer.reports.get GET http://127.0.0.1:6976/portal/api/v1/customer/v1/organizations/org_K11111111111/reports/rpt_S11111111111
{
"schema": "customer/v1",
"request_id": "req_schedule-report-get",
"kind": "report",
"data": {
"report_id": "rpt_S11111111111",
"organization_id": "org_K11111111111",
"workspace_id": "t_K11111111111",
"owner": "it-lead",
"name": "Gateway daily report",
"saved_view_id": "sview_K11111111111",
"view_revision": "1",
"recipient_subject_ids": [
"it-analyst"
],
"evidence_refs": [],
"revision": "1",
"latest_run_id": "rrun_S11111111111",
"created_at": "2026-09-21T12:00:00Z",
"updated_at": "2026-09-21T12:00:00Z"
}
}
Replayed from the recorded case schedule-report-get: the report Gateway daily report,
owned by it-lead and made from the checkout gateway’s rolling 24-hour view, with
it-analyst as its one recipient. Its first run was made by hand when the report was created.
Copy data.revision as it is printed. It is a decimal string, not a number. recipient_subject_ids
in the same document lists the people you can name on the schedule.
2. Create the schedule
Write the schedule document to schedule.json. It names the report and the revision you read,
runs daily at 09:00 Los Angeles time, and delivers to your colleague:
{
"report_id": "rpt_…",
"expected_report_revision": "1",
"cadence": {
"mode": "wall_clock",
"frequency": "daily",
"local_time": "09:00",
"timezone": "America/Los_Angeles"
},
"recipient_subject_ids": ["<colleague's subject ID>"],
"paused": false
}
Create it with tokentriage cloud report schedule create --org … --workspace … --file schedule.json --idempotency-key gateway-daily-schedule-01:
$ tokentriage cloud report schedule create --org org_K11111111111 \
--workspace t_K11111111111 --file schedule.json \
--idempotency-key gateway-daily-schedule-01 --base-url http://127.0.0.1:6978
cloud: customer.report_schedules.create POST http://127.0.0.1:6978/portal/api/v1/customer/v1/organizations/org_K11111111111/workspaces/t_K11111111111/report-schedules (Idempotency-Key gateway-daily-schedule-01)
rsch_S11111111111 report rpt_S11111111111 armed revision 1 next run 2026-09-21T16:00:00Z
next 2026-09-21T16:00:00Z 2026-09-21T09:00:00-07:00
next 2026-09-22T16:00:00Z 2026-09-22T09:00:00-07:00
next 2026-09-23T16:00:00Z 2026-09-23T09:00:00-07:00
Replayed from schedule-create, recorded with the service clock at 2026-09-21T12:00Z, which
is 05:00 in Los Angeles, so the first occurrence is 09:00 that morning. The recording runs the
dispatcher, the report worker, the reader and the inbox, so the state is armed.
The first line is the schedule: its rsch_… ID, the report, its execution state, its revision
and the next run in UTC. The next lines are the next three occurrences, each as a UTC instant
and as the local time in your timezone. Keep the rsch_… ID; every later step uses it.
Read the execution state before you rely on the schedule. A saved schedule is not a promise that it will run:
execution_state |
What it means |
|---|---|
armed |
the dispatcher, the report worker, the reader and the inbox are all running, so the next occurrence will run |
paused |
nothing runs until the owner resumes it; stderr names the reason |
unavailable |
the schedule is saved, but this deployment lacks one of those four parts; stderr names which |
If the command times out, send it again with the same --idempotency-key. The server answers with
the schedule it already made instead of a second one.
If the create answers HTTP 422 validation_failed with fixed_view_not_schedulable
The report was made from a view with a fixed window, such as 1 to 7 September. A fixed view asks about one stretch of history, so running it every morning would deliver the same window again and again, and nobody could tell which question the schedule repeats. Nothing was saved.
$ tokentriage cloud report schedule create --org org_K11111111111 \
--workspace t_K11111111111 --file fixed-schedule.json \
--idempotency-key gateway-fixed-schedule-01 --base-url http://127.0.0.1:6980
cloud: customer.report_schedules.create POST http://127.0.0.1:6980/portal/api/v1/customer/v1/organizations/org_K11111111111/workspaces/t_K11111111111/report-schedules (Idempotency-Key gateway-fixed-schedule-01)
Error: customer.report_schedules.create was refused (HTTP 422); detail: foundation: unsupported combination: cadence: fixed_view_not_schedulable (view): a fixed-window view cannot be scheduled; create a relative view or run a report manually; type=validation_failed; request_id=req_schedule-create-fixed-viewReplayed from schedule-create-fixed-view, recorded for a second report, made from a view
over 1 to 7 September and named in fixed-schedule.json, with a new key. The problem type is
validation_failed, and the detail carries the code fixed_view_not_schedulable.
Save a view with a relative window, such as the last 24 hours, create a report from it, and
schedule that report. To see the fixed window again, run the existing report by hand with
tokentriage cloud report run.
If the create answers HTTP 503 dependency_unavailable
This deployment has no schedule store, dispatcher or report worker running, so a schedule could not be saved or read. Nothing was saved, and your report still opens and runs by hand. Ask whoever runs the deployment to enable scheduled reports, then create the schedule again with the same key.
$ tokentriage cloud report schedule create --org org_K11111111111 \
--workspace t_K11111111111 --file schedule.json \
--idempotency-key gateway-daily-schedule-01 --base-url http://127.0.0.1:6992
cloud: customer.report_schedules.create POST http://127.0.0.1:6992/portal/api/v1/customer/v1/organizations/org_K11111111111/workspaces/t_K11111111111/report-schedules (Idempotency-Key gateway-daily-schedule-01)
Error: customer.report_schedules.create was refused (HTTP 503); detail: this control plane composes no report schedule store, dispatcher and report worker, so no report schedule can be saved or read here; the request id identifies this attempt in its logs; type=dependency_unavailable; request_id=req_schedule-create-unavailableReplayed from schedule-create-unavailable, recorded against a control plane that composes
reports but no schedules. The server sent no Retry-After header with this 503.
A 503 means the schedule lane is missing entirely. A schedule that was saved but reads
unavailable is a different case: the lane is there, and one of the parts that runs it is not.
3. Check when it will run
A daylight-saving change moves a schedule’s UTC times, so read them before you wait for one. tokentriage cloud report schedule get --org … --schedule rsch_… prints the same
line and the next three occurrences, computed again from the current time:
$ tokentriage cloud report schedule get --org org_K11111111111 \
--schedule rsch_S11111111111 --base-url http://127.0.0.1:7025
cloud: customer.report_schedules.get GET http://127.0.0.1:7025/portal/api/v1/customer/v1/organizations/org_K11111111111/report-schedules/rsch_S11111111111
rsch_S11111111111 report rpt_S11111111111 armed revision 1 next run 2026-09-21T16:00:00Z
next 2026-09-21T16:00:00Z 2026-09-21T09:00:00-07:00
next 2026-09-22T16:00:00Z 2026-09-22T09:00:00-07:00
next 2026-09-23T16:00:00Z 2026-09-23T09:00:00-07:00
Replayed from schedule-get-next-occurrences, recorded straight after the create, before
the first occurrence.
Each next line pairs the UTC instant with the local time in America/Los_Angeles. Across a
daylight-saving change the UTC instant moves and the local time stays at 09:00, because a
wall_clock cadence follows the local clock.
A third column appears only on an occurrence a clock change touches. dst_gap means the local time
did not exist that day, so the occurrence runs later by the length of the gap: a 02:30 schedule
runs at 03:30 on the morning the clocks go forward an hour. dst_fold means the local time
happened twice, and the schedule runs once, at the earlier of the two.
Other cadences: hourly, weekly, and a fixed interval
A cadence has one of two modes, and a schedule keeps its mode until an update names a different one. There is no cron expression.
mode |
frequency |
Other fields | Runs |
|---|---|---|---|
wall_clock |
daily |
local_time HH:MM, IANA timezone |
once per local calendar day at that time |
wall_clock |
weekly |
local_time, timezone, weekday (monday to sunday) |
once per local week |
interval |
hourly, daily or weekly |
optional anchor (RFC 3339), optional timezone |
every 1, 24 or 168 hours of elapsed time from the anchor |
A weekly schedule on Monday mornings:
{"mode": "wall_clock", "frequency": "weekly", "local_time": "08:30",
"timezone": "Europe/Berlin", "weekday": "monday"}An interval cadence counts elapsed time from its anchor, which defaults to the moment you
create the schedule. Its timezone only labels the local side of each next line, so a daily
interval that runs at 09:00 in winter runs at 10:00 local time in summer. Hourly exists only as
an interval: wall_clock with hourly is answered 422 with hourly_requires_interval.
4. See what ran
After the first occurrence’s time has passed, list the schedule’s history with
tokentriage cloud report schedule occurrences --org … --schedule rsch_…. This is what ran, newest
first, and not a forecast:
$ tokentriage cloud report schedule occurrences --org org_K11111111111 \
--schedule rsch_S11111111111 --base-url http://127.0.0.1:7027
cloud: customer.report_schedules.occurrences GET http://127.0.0.1:7027/portal/api/v1/customer/v1/organizations/org_K11111111111/report-schedules/rsch_S11111111111/occurrences
rocc_S11111111111 2026-09-21T16:00:00Z delivered [2026-09-20T16:00:00Z, 2026-09-21T16:00:00Z) run rrun_T11111111111
Replayed from schedule-occurrences, recorded after the 09:00 occurrence on 21 September. In
the recording the dispatcher and the report worker were stepped one pass at a time instead of
running on their own: the dispatcher claimed the occurrence at 16:00:30Z and queued its run,
the worker settled the run, and the dispatcher delivered it at 16:01:30Z. The window still
ends at 16:00Z.
Each line is one occurrence: its rocc_… ID, the instant it was scheduled for, its state, the
window its run measured and the rrun_… it made. The window ends at the scheduled instant, not at
the moment a worker got to it, and it spans the view’s relative window in elapsed time. A
“last 24 hours” view gives [09:00 yesterday, 09:00 today) in UTC terms, and stays 24 hours long
on the night the clocks change. A retry keeps the same window.
delivered means the run settled and every recipient who can still open it has an item in their
inbox. failed carries a code on the next line. authority_lost means you, the owner, no longer
see the view’s whole scope, so the schedule pauses with that reason and runs nothing more until you
resume it.
If the schedule could not run on time, for example while the deployment was down, it runs once for
the latest time it owed within the last 24 hours, and a skipped line counts the older times it
passed over. If even the latest owed time is more than 24 hours old, the occurrence is missed: it
has no window and no run, its skipped line gives the count and range, and the schedule moves on
to its next future time.
Only one scheduled report runs at a time in a workspace. If another schedule in the workspace is mid-run at 09:00, yours waits until it finishes. The delivery arrives later, and its window still ends at 09:00.
5. Open the delivery as the recipient
Your colleague reads their own inbox with their own management key. Being named a recipient lets
nobody open anything on their behalf, including you. They run
tokentriage cloud report inbox list --org … --workspace …:
$ tokentriage cloud report inbox list --org org_K11111111111 --workspace t_K11111111111 \
--base-url http://127.0.0.1:7029
cloud: customer.report_inbox.list GET http://127.0.0.1:7029/portal/api/v1/customer/v1/organizations/org_K11111111111/workspaces/t_K11111111111/report-inbox
rdlv_S11111111111 report_delivered 2026-09-21T16:01:30Z report rpt_S11111111111 run rrun_T11111111111 unread
Replayed from inbox-list, recorded as it-analyst with the clock at 16:20Z, which is 09:20
in Los Angeles.
An item carries identifiers only: the rdlv_… delivery, the event type, when it arrived, the
report and the run, and whether it has been read. No numbers travel in the inbox. They open the
item with tokentriage cloud report inbox open --org … --delivery rdlv_…:
$ tokentriage cloud report inbox open --org org_K11111111111 --delivery rdlv_S11111111111 \
--base-url http://127.0.0.1:7031
cloud: customer.report_inbox.open POST http://127.0.0.1:7031/portal/api/v1/customer/v1/organizations/org_K11111111111/report-inbox/rdlv_S11111111111/open
rdlv_S11111111111 report_delivered 2026-09-21T16:01:30Z report rpt_S11111111111 run rrun_T11111111111 read 2026-09-21T16:20:00Z
rrun_T11111111111 ready [2026-09-20T16:00:00Z, 2026-09-21T16:00:00Z) groups 1 sha256:4781e1181553fcb188615c3a47a582e013e3b2e3aac7f4aa4e6cd120accf4871
Replayed from inbox-open, recorded as it-analyst straight after the list. it-analyst
holds cost.read, so no money cell is withheld and stderr says nothing more.
The first line is the item, now marked read. The rest is the run, as cloud report show prints it
for your colleague: their own access is checked again when they open it, and money cells are
withheld if they lack cost.read. Opening it again shows the same run and keeps the first read
time.
If inbox open answers HTTP 404 not_found
The status and type are the same whether the item is missing or no longer yours to open. The
usual cause is that your colleague’s access changed after the delivery: they were removed from the
report, or their metadata.read no longer covers its whole scope. Nothing was recorded as read.
$ tokentriage cloud report inbox open --org org_K11111111111 --delivery rdlv_T11111111111 \
--base-url http://127.0.0.1:7033
cloud: customer.report_inbox.open POST http://127.0.0.1:7033/portal/api/v1/customer/v1/organizations/org_K11111111111/report-inbox/rdlv_T11111111111/open
Error: customer.report_inbox.open was refused as not found (HTTP 404): the lane conceals organizations and resources the caller has no binding for, so this can mean it does not exist OR that you cannot see it; detail: foundation: not found: no report run rrun_U11111111111; type=not_found; request_id=req_inbox-open-not-foundReplayed from inbox-open-not-found, recorded as it-analyst for the next morning’s
delivery, after their analyst role in the workspace was revoked. The detail names the run
they can no longer open. An item that does not exist, or that belongs to somebody else, gets
the same status and type with a detail that names the delivery instead.
A recipient who has already lost access when an occurrence runs gets no item for it at all, and the other recipients are unaffected. Restore their access, and the next occurrence delivers to them again.
6. Pause and resume it
Pausing stops future runs and cancels one that is queued or running. An update replaces the whole
editable document, so write all three members to pause.json:
{
"cadence": {
"mode": "wall_clock",
"frequency": "daily",
"local_time": "09:00",
"timezone": "America/Los_Angeles"
},
"recipient_subject_ids": ["<colleague's subject ID>"],
"paused": true
}
Send it with the revision schedule get printed:
tokentriage cloud report schedule update --org … --schedule rsch_… --expected-revision 1 --file pause.json.
$ tokentriage cloud report schedule update --org org_K11111111111 \
--schedule rsch_S11111111111 --expected-revision 1 --file pause.json \
--base-url http://127.0.0.1:7036
cloud: customer.report_schedules.update PUT http://127.0.0.1:7036/portal/api/v1/customer/v1/organizations/org_K11111111111/report-schedules/rsch_S11111111111
rsch_S11111111111 report rpt_S11111111111 paused revision 2 next run -
paused manual
next 2026-09-23T16:00:00Z 2026-09-23T09:00:00-07:00
next 2026-09-24T16:00:00Z 2026-09-24T09:00:00-07:00
next 2026-09-25T16:00:00Z 2026-09-25T09:00:00-07:00
Replayed from schedule-update-pause, recorded the day after the create, when both mornings’
occurrences had run. Running an occurrence does not change the schedule’s revision, so the
revision step 3 printed was still current. next run - means nothing will run; the next
lines are the times the schedule would run if you resumed it now.
The state reads paused, stderr says manual, and the revision moved up by one. To resume, send
the same document with "paused": false and the new revision. A resumed schedule starts at its
next time after the moment you resume it and does not run the occurrences it skipped while paused.
A cadence edit does the same.
If the update answers HTTP 412 precondition_failed, somebody changed the schedule after you read
it. Read it again with schedule get, apply your change to what it now holds, and send the new
revision.
7. Delete it
tokentriage cloud report schedule delete --org … --schedule rsch_… retires the schedule and
cancels any run it has in flight:
$ tokentriage cloud report schedule delete --org org_K11111111111 \
--schedule rsch_S11111111111 --base-url http://127.0.0.1:7038
cloud: customer.report_schedules.delete DELETE http://127.0.0.1:7038/portal/api/v1/customer/v1/organizations/org_K11111111111/report-schedules/rsch_S11111111111
rsch_S11111111111 deleted 2026-09-22T16:01:30Z occurrences cancelled 0
Replayed from schedule-delete, recorded straight after the pause, with nothing in flight.
The line names the schedule, when it was deleted, and how many occurrences were cancelled. The schedule no longer appears in any read. Items already delivered stay in each recipient’s inbox, and the report itself is unchanged: you can still run it by hand.
8. Do the same from a program
Both SDKs ship steps 2 and 5 as one example: create the schedule as the report’s owner, print its
execution state and next occurrences, then list the recipient’s inbox with the recipient’s own
client and open the first item if there is one. This is the function, quoted from
sdk/python/examples/schedule_gateway_report.py and sdk/ts/examples/schedule_gateway_report.ts:
def schedule_gateway_report(
owner: CustomerClient,
recipient: CustomerClient,
org: str,
ws: str,
body: Mapping[str, Any],
*,
idempotency_key: str,
out: Callable[[str], None] = print,
) -> Mapping[str, Any]:
schedule = owner.report_schedules.create(
org, ws, dict(body), idempotency_key=idempotency_key
)["data"]
# `armed` only when every dependency is mounted; `unavailable` names what is missing.
out(f"schedule {schedule['schedule_id']} is {schedule['execution_state']}")
for occurrence in schedule.get("next_occurrences", []):
note = f" ({occurrence['note']})" if occurrence.get("note") else ""
out(f" next {occurrence['scheduled_utc']} = {occurrence['local']}{note}")
# The recipient's OWN credential: selecting them as a recipient does not let the owner
# open anything on their behalf.
items = recipient.report_inbox.list(org, ws)["data"].get("items", [])
if not items:
out(NO_DELIVERY)
return {"schedule_id": schedule["schedule_id"], "opened": None}
opened = recipient.report_inbox.open(org, items[0]["delivery_id"])["data"]
run = opened.get("run") or {}
out(f"opened {opened['item']['delivery_id']}: run {run.get('run_id')} is {run.get('state')}")
out(f" coverage {json.dumps(run.get('coverage', []))}")
return {"schedule_id": schedule["schedule_id"], "opened": opened}export async function scheduleGatewayReport(
owner: CustomerClient,
recipient: CustomerClient,
org: string,
ws: string,
body: ReportScheduleCreateInput,
options: ScheduleReportOptions,
): Promise<{ schedule_id: string; opened: OpenedInboxItemV1 | null }> {
const out = options.out ?? ((line: string) => console.log(line));
const { data: schedule } = await owner.reportSchedules.create(org, ws, body, {
idempotencyKey: options.idempotencyKey,
});
const scheduleId = schedule?.schedule_id ?? "";
// `armed` only when every dependency is mounted; `unavailable` names what is missing.
out(`schedule ${scheduleId} is ${schedule?.execution_state ?? ""}`);
for (const occurrence of schedule?.next_occurrences ?? []) {
const note = occurrence.note ? ` (${occurrence.note})` : "";
out(` next ${occurrence.scheduled_utc ?? ""} = ${occurrence.local ?? ""}${note}`);
}
// The recipient's OWN credential: selecting them as a recipient does not let the owner
// open anything on their behalf.
const { data: inbox } = await recipient.reportInbox.list(org, ws);
const first = inbox?.items?.[0];
if (first?.delivery_id === undefined) {
out(NO_DELIVERY);
return { schedule_id: scheduleId, opened: null };
}
const { data: opened } = await recipient.reportInbox.open(org, first.delivery_id);
const run = opened?.run;
out(`opened ${opened?.item?.delivery_id ?? ""}: run ${run?.run_id ?? ""} is ${run?.state ?? ""}`);
out(` coverage ${JSON.stringify(run?.coverage ?? [])}`);
return { schedule_id: scheduleId, opened: opened ?? null };
}Each script reads schedule.json (or the path you pass), TOKENTRIAGE_BASE_URL,
TOKENTRIAGE_MANAGEMENT_KEY for the owner, TOKENTRIAGE_RECIPIENT_KEY for the recipient,
TOKENTRIAGE_ORG and TOKENTRIAGE_WORKSPACE. TOKENTRIAGE_IDEMPOTENCY_KEY is optional and
defaults to gateway-report-schedule-01. The script prints one line per step:
$ python schedule_gateway_report.py
schedule rsch_S11111111111 is armed
next 2026-09-21T16:00:00Z = 2026-09-21T09:00:00-07:00
next 2026-09-22T16:00:00Z = 2026-09-22T09:00:00-07:00
next 2026-09-23T16:00:00Z = 2026-09-23T09:00:00-07:00
No delivered report yet; inspect occurrence history
Run with Python 3.14 against a loopback server answering the two requests recorded in
sdk-python-schedule-gateway-report, straight after the report was made: the create as
it-lead with the default key, then the inbox list as it-analyst with their own key.
$ node schedule_gateway_report.mjs
schedule rsch_S11111111111 is armed
next 2026-09-21T16:00:00Z = 2026-09-21T09:00:00-07:00
next 2026-09-22T16:00:00Z = 2026-09-22T09:00:00-07:00
next 2026-09-23T16:00:00Z = 2026-09-23T09:00:00-07:00
No delivered report yet; inspect occurrence history
Bundled with esbuild and run with Node 24 against the two requests recorded in
sdk-ts-schedule-gateway-report.
Run straight after creating a schedule, the inbox is normally empty, and the script prints
No delivered report yet; inspect occurrence history instead of opening anything. Nothing in it
waits for a delivery. Run it again after the first occurrence, or read the history as in step 4.
What a schedule does not do
- It delivers to the in-app inbox and nowhere else: no email, no Slack message.
- It grants nothing. A recipient opens each run under their own current access, and a recipient who has lost access gets no item.
- It repeats a relative window only. A fixed-window view cannot be scheduled.
- It has no cron expressions and no calendar-day windows: “yesterday” is the 24 hours before the scheduled time, not midnight to midnight.
- It runs one scheduled report at a time per workspace.
- It names at most 20 recipients; a 21st is answered 422
validation_failed. A workspace is sized for 20 schedules, a documented capacity that is not enforced by a refusal in this release.
Reporting and alerts lists every schedule field, state, failure code, problem code and limit.