Broadcasts Admin API¶
The broadcasts admin API allows platform superadmins to compose, preview, and publish platform-wide announcements. Broadcasts are targeted to an audience derived from audience_spec and delivered via BroadcastFanoutJob.
Admin-only
All /admin/broadcasts and /admin/notification_categories endpoints require a platform-admin account. Non-admin requests receive 403 Forbidden.
Broadcast lifecycle¶
draft → published
POST /admin/broadcasts → creates a draft (published_at = null)
GET /admin/broadcasts/:id/audience_preview → check audience before publishing
POST /admin/broadcasts/:id/publish → sets published_at, enqueues BroadcastFanoutJob
Once published, a broadcast cannot be unpublished.
Audience targeting¶
Every broadcast requires an audience_spec object that controls who receives it. BroadcastFanoutJob resolves the account set at publish time using the same Broadcasts::AudienceResolver class used by the preview endpoints.
type | Target | Additional fields |
|---|---|---|
all | Every account | — |
organization | All accounts in one org | organization_id (UUID) |
feature_enabled | Accounts in orgs with a feature enabled | feature_key (string) |
feature_provisioned | Accounts in orgs with a feature provisioned | feature_key (string) |
Examples
{ "type": "all" }
{ "type": "organization", "organization_id": "org-uuid" }
{ "type": "feature_enabled", "feature_key": "kubernetes" }
{ "type": "feature_provisioned", "feature_key": "apps" }
Broadcast endpoints¶
List broadcasts¶
Returns the 50 most recently created broadcasts (drafts and published).
Response 200
{
"metadata": { "organization_id": null, "timestamp": "2026-06-30T20:00:00Z" },
"data": [
{
"id": "uuid",
"title": "Maintenance tonight",
"body": "Scheduled maintenance 00:00–02:00 UTC.",
"severity": "notice",
"audience_spec": { "type": "all" },
"banner": true,
"channels": ["in_app"],
"created_by_id": "acct-uuid",
"published_at": "2026-06-30T20:00:00Z",
"audience_count": null,
"created_at": "2026-06-30T19:00:00Z"
}
]
}
published_at: null = draft. audience_count is populated after fanout when the job records it back; otherwise null.
Create a broadcast (draft)¶
Creates a draft. The broadcast is not delivered until POST /admin/broadcasts/:id/publish.
Request body
{
"title": "Scheduled maintenance",
"body": "We will perform maintenance on 2026-07-01 00:00–02:00 UTC.",
"severity": "notice",
"audience_spec": { "type": "all" },
"banner": true,
"channels": ["in_app"]
}
| Field | Required | Description |
|---|---|---|
title | yes | Short summary shown in the banner and inbox |
body | yes | Full message text |
severity | yes | info, notice, or critical |
audience_spec | yes | Targeting spec — see table above |
banner | no | true to show as a dismissible top-of-app banner (default false) |
channels | no | Delivery channels — in_app and/or email (defaults to ["in_app"]) |
Response 201 — returns the created broadcast object (same shape as list item).
Response 422
{
"code": "validation_error",
"message": "Title can't be blank, Audience spec type must be one of: all, organization, feature_enabled, feature_provisioned"
}
Show a broadcast¶
Returns a single broadcast by ID.
Response 200 — broadcast object.
Response 404 — broadcast not found.
Update a broadcast (draft only)¶
Updates any writable field. All fields are optional.
Request body — any subset of the create body fields.
Response 200 — updated broadcast object.
Publish a broadcast¶
Sets published_at to the current time and enqueues BroadcastFanoutJob to deliver to the resolved audience. Once published, published_at cannot be cleared.
Response 200
{
"metadata": { "organization_id": null, "timestamp": "2026-06-30T20:00:00Z" },
"data": {
"id": "uuid",
"published_at": "2026-06-30T20:05:00Z"
}
}
Response 404 — broadcast not found.
Audience preview (persisted broadcast)¶
Returns the estimated recipient count for an existing broadcast without triggering fanout. Use this to verify scope before publishing.
Response 200
{
"metadata": { "organization_id": null, "timestamp": "2026-06-30T20:00:00Z" },
"data": { "audience_count": 47 }
}
Audience preview (spec-only, no persist)¶
POST /admin/broadcasts/audience_preview
Authorization: Bearer <admin-token>
Content-Type: application/json
Returns the estimated recipient count from a submitted audience_spec without persisting any broadcast. Useful when composing the draft and wanting to check audience size before saving.
Request body
Response 200
{
"metadata": { "organization_id": null, "timestamp": "2026-06-30T20:00:00Z" },
"data": { "audience_count": 12 }
}
Response 422 — invalid audience_spec:
{
"code": "validation_error",
"message": "type must be one of: all, organization, feature_enabled, feature_provisioned"
}
Notification categories (admin)¶
Superadmins can toggle mandatory_for_admins and default_channels on any category. Changes take effect on the next delivery — existing preference rows are not back-filled.
List categories (admin view)¶
Returns all categories. Same shape as GET /notification_categories (see Notifications API).
Update a category¶
PATCH /admin/notification_categories/{id}
Authorization: Bearer <admin-token>
Content-Type: application/json
Writable fields
| Field | Type | Description |
|---|---|---|
mandatory_for_admins | boolean | Force in_app on for org admins |
default_channels | string[] | Default channel set for accounts without a preference row |
Request body
Response 200 — updated category object.
Response 404 — category not found.
Response 422 — validation error.