A valid request URL is required to generate request examples{
"banner": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"environment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"kind": "text",
"slot": "<string>",
"active": true,
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z",
"priority": 123,
"dismissible": true,
"title": "<string>",
"body": "<string>",
"icon": "<string>",
"tone": "info",
"cta_label": "<string>",
"cta_url": "<string>",
"image_url": "<string>",
"image_alt": "<string>",
"segment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"dismissals": 123,
"views": 123,
"viewers": 123,
"clicks": 123,
"clickers": 123,
"created_by": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}Write a banner
Requires engagement:manage.
kind is text or image and cannot be changed afterwards: the two kinds fill different columns, so turning one into the other would discard what somebody wrote with no way back. Create the other kind instead — which also keeps the dismissal count attached to the thing people actually saw.
slot is where it renders, matched by string against the slot prop your own developer wrote: <Banner slot="dashboard-top" />. Lowercase letters, digits, hyphens and underscores — a name is refused rather than normalised, because this string is typed twice in two different places and case is how that goes wrong invisibly. Nothing here checks that your app renders that slot; we cannot see your code.
active defaults to false: a banner exists while its copy is being written, and creating one never puts anything in front of anybody. This is the module that draws itself on your production pages, so the off switch is one call and takes effect on the next poll.
starts_at and ends_at are the window it is true in, both optional, both RFC 3339 with an offset. They are separate from active because they answer different questions — active is “is this approved”, the window is “when is it true” — and a maintenance notice written on Monday for Saturday needs to be switched on and not yet showing. ends_at is also what takes the Black Friday strip down on Monday without anybody remembering.
segment_id is who it is for, or null for everybody, and it must name a segment of the same environment — the audience your flags, surveys and changelog posts already use, never a filter of its own.
priority decides which banner wins when a slot has more than one candidate: higher first, ties broken by newest. Only one banner is ever returned per slot, decided in the database, so the answer is the same for the React component, a mobile shell and a preview.
dismissible defaults to true: a strip somebody cannot get rid of is an ad, and these go in front of your own customers. false is for the notices that are a state of the product rather than news — “this account is read-only until you confirm your email” — where hiding it would hide the explanation for what the person is about to run into.
A valid request URL is required to generate request examples{
"banner": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"environment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"kind": "text",
"slot": "<string>",
"active": true,
"starts_at": "2023-11-07T05:31:56Z",
"ends_at": "2023-11-07T05:31:56Z",
"priority": 123,
"dismissible": true,
"title": "<string>",
"body": "<string>",
"icon": "<string>",
"tone": "info",
"cta_label": "<string>",
"cta_url": "<string>",
"image_url": "<string>",
"image_alt": "<string>",
"segment_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"dismissals": 123,
"views": 123,
"viewers": 123,
"clicks": 123,
"clickers": 123,
"created_by": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}{
"error": {
"code": "forbidden",
"message": "your role does not allow this action"
}
}Authorizations
A staff session token, uk_st_…. Minted by sign-up, sign-in or the two-factor exchange. Only a 401 means it is spent; not_a_member (403) is about the organization named in X-Organization-Id and leaves the token good for the others.
Headers
The organization the caller is acting on — the org_… code that appears in the panel URL. It identifies; the membership JOIN is what authorizes, so a forged code reads nothing: the answer is not_a_member (403), which does not mean the session is over. Absent, the session's default organization answers, or — if that membership was revoked while the session was open — any other one the caller still holds.
Query Parameters
Which environment to act in. A view parameter, valid only on the staff surface — a machine credential never chooses its environment, it is resolved from the key.
live, test Body
A banner on the way in. kind and slot are required on create and kind is refused on update. The fields of the kind you are not writing are ignored and stored empty — a row carrying both kinds' content is one whose appearance would depend on which branch of the widget read it first.
Required on create, immutable afterwards.
text, image Lowercase letters, digits, hyphens, underscores. Matched by string against the slot prop in your app.
60RFC 3339 with an offset. null clears the lower bound.
RFC 3339 with an offset. null means it never stops.
-1000 <= x <= 1000Required for text.
120500One of the names in icons, or empty.
info, success, warning, danger Needs cta_url alongside it — a button with no destination does nothing when it is clicked.
40Both kinds: the button's destination, or the whole picture's link. Empty means it goes nowhere.
A path rooted at / (/plans) or an absolute http/https URL — the same two shapes an in-app notification's url takes, and for the same reason. A path is if anything the more natural half here, since a banner is drawn inside your own product. Anything else is a 400, javascript: and the protocol-relative //host/path included: this value ends up in an href on your own page.
Required for image. An absolute http(s) URL.
300The audience, or null for everybody. Must belong to the same environment.
Response
The banner.
A message on your users' screens, in a slot your own developer left room for. kind decides which fields carry the content — text is drawn by the widget in your palette, image is a picture you made and the widget only frames — and it cannot change, because the two fill different columns and turning one into the other would discard what somebody wrote.
Show child attributes
Show child attributes