/sites/{siteId}/banner/designChange the banner's colours and placement
Saves and publishes the banner's theme tokens and, optionally, where it sits.
Auth header
Authorization: Bearer tsk_live_…The key needsbanner:write
Parameters
siteIdstringin pathrequiredThe site id, from GET /v1/sites.
Request body
thememap of stringrequiredCSS custom properties. An absent token keeps our shipped default. Refused if a contrast check fails.
presentationobjectoptionalAbsent keeps the stored placement.
{}resets it to where we ship it.presentation.layoutstringoptionalOne of
bar_bottom,bar_top,box_bottom_left,box_bottom_right,box_top_left,box_top_right,modal_centrepresentation.overlaybooleanoptionalThe scrim behind a first-visit banner. Off also drops the focus trap.
presentation.actionsstringoptionalOne of
inline,stackedpresentation.orderstringoptionalOne of
reject_first,accept_firstpresentation.closeXstringoptionalAlways
retains_defaultspresentation.launcherobjectoptionalpresentation.launcher.positionstringoptionalOne of
bottom_left,bottom_right,top_left,top_rightpresentation.launcher.triggerstringoptionalA CSS selector for your own 'Cookie settings' control. Falls back to the floating pill when it matches nothing.
Example request
curl -X PUT "https://app.tagsentry.ai/api/v1/sites/3f6c1b8e-2d4a-4c7e-9a51-0b8f2e6d7c10/banner/design" \ -H "Authorization: Bearer tsk_live_…" \ -H "Content-Type: application/json" \ -d '{"theme":{"--ts-panel-bg":"…"},"presentation":{"layout":"bar_bottom","overlay":false,"actions":"inline","order":"reject_first","closeX":"retains_defaults","launcher":{"position":"bottom_left","trigger":"…"}}}'Response 200
savedbooleanrequiredAlways
truechangestringrequiredOne of
design,wording,linksbannerobjectrequiredbanner.titlestringrequiredbanner.bodystringrequiredbanner.acceptAllstringrequiredbanner.rejectAllstringrequiredbanner.managestringrequiredbanner.thememap of string | nullrequiredbanner.presentationmap of any | nullrequiredbanner.policyLinksmap of string | nullrequireddesignModestring | nullrequiredartifactWarningstring | nullrequiredSet when the save stored but the copy visitors fetch did not refresh yet. It retries; the save stands.
Example response
{ "saved": true, "change": "design", "banner": { "title": "…", "body": "…", "acceptAll": "…", "rejectAll": "…", "manage": "…", "theme": { "key": "…" }, "presentation": { "key": null }, "policyLinks": { "key": "…" } }, "designMode": "…", "artifactWarning": "…"}More about this endpoint
The same save the Design screen makes, with the same refusals: a theme whose text or buttons fail the contrast check is refused with the audit in detail, and nothing is stored.
A DESIGN WRITE CANNOT CHANGE A WORD. Every string a visitor reads is carried from the stored banner, because each consent record quotes the banner verbatim. Use the wording endpoint for words.
Saving IS publishing: visitors get the new design on their next page view. The site's colours are marked as yours from here, so a later scan does not re-derive over them.
Errors400 · 401 · 403 · 404 · 409 · 429 · 500
400The request did not validate against this operation's schema.401Missing, malformed, unknown, revoked or expired API key. These are deliberately indistinguishable in the response -- distinguishing them would confirm to a caller that a token was once real.403The key authenticated but does not carry the scope(s) this operation requires, or (`domain_not_verified`) the site's domain is not verified, so its consent records are not released.404No such resource on this account. A site id belonging to a DIFFERENT account answers 404, never 403 -- a 403 would confirm the id exists somewhere.409The request conflicts with existing state.429A rate limit or quota was exceeded. The body names WHICH one.500Something failed on our side. The requestId in the body is what to quote.
Every error has the same body: { error: { code, message, requestId } }.
From the OpenAPI document, version 2026-08-26. Raw OpenAPI