Web: HTTP API
HTTP API for fetching ads and handling interactive actions in web apps.
Authentication
| Caller | Authentication | Identity |
|---|---|---|
| Browser | Platform ID and an allowed site address. The browser supplies Origin. | visitor_id |
| Server | Authorization: Bearer sk_live_… | stable_user_id or visitor_id |
Get an API key in publisher settings. Keep it on the server. Server requests to Sidekick must omit Origin.
Send exactly one user identity. Allowed characters: A–Z, a–z, 0–9, _, ., :, -. Keep the ID across requests; do not use an email or phone number.
Request an ad
POST /api/v1/web/ads
| Field | Type | Description |
|---|---|---|
platform_idrequired | string | Public platform ID. |
visitor_id | string | Visitor ID, 16–128 characters. Required for browser requests. |
stable_user_id | string | User ID, 1–128 characters. Available for server requests instead of visitor_id. |
request_id | string | Request ID, 1–128 characters. Reusing it does not prevent another ad issuance. |
placement | string | Registered placement name. Defaults to chat. |
language_code | string | Ad language. Defaults to en. |
accept_formats | string[] | text, image, response, card, consent, quiz. Defaults to all formats. |
context | object | Ad context. Fields depend on context.type. |
test | boolean | Test ads without billing. Defaults to false. |
test_format | string | Test ad format when test is true. |
Required fields.
Content-Type: application/json. Maximum body size: 64 KiB.
Server request
POST /api/v1/web/adsAuthorization: Bearer sk_live_EXAMPLEContent-Type: application/json{"platform_id": "plt_example","stable_user_id": "customer_42","context": {"question": "Headphones?","answer": "Here are some options…"},"test": true,"test_format": "card"}
Browser request
visitorId is the persisted visitor ID. Send the request without an API key or cookies.
const response = await fetch("https://sidekick-ads.com/api/v1/web/ads", {method: "POST",credentials: "omit",headers: { "Content-Type": "application/json" },body: JSON.stringify({platform_id: "plt_example",visitor_id: visitorId,request_id: "chat-42-message-7",test: true,test_format: "card"})});if (!response.ok) throw new Error("Ad request failed");const payload = await response.json();
Context
| context.type | Fields | Limits |
|---|---|---|
conversation | question, answer | Both fields are optional: up to 4,096 and 16,384 characters. Default type. |
article | title, content | Both fields are required: up to 4,096 and 16,384 characters. |
feed | text | Required field, up to 16,384 characters. |
static | — | No text context. |
Topic matching uses the user's question: question or title. feed and static have none, so they receive only ads without topic targeting.
Response
| Field | Type | Description |
|---|---|---|
has_ad | boolean | Whether the response includes an ad. |
ad | object | null | Ad object; null when no ad is available. |
ad.delivery_id | string | Delivery ID for interactions. |
ad.expires_at | string | Ad expiry in ISO 8601 format. |
ad.format | string | Ad format. |
ad.disclosure | string | Required advertising disclosure. |
ad.test | boolean | Whether this is a test ad. |
ad.blocks | object[] | text, media, gallery and buttons blocks in display order. |
ad.view | string | Receipt for the view confirmation. Present on billable ads only. Views |
{"has_ad": false,"ad": null}
An impression is paid once the ad is seen, not when it is issued. The browser SDK confirms the view itself; with custom rendering, send a view confirmation.
Rendering
Render every block and the disclosure. Use mountSidekickAd from the browser SDK for the standard card. In this example, payload comes from your server and /my-ad-interaction handles actions in your app.
const view = Sidekick.mountSidekickAd(document.querySelector("#ad-slot"), {onAction: async (ad, action) => {const response = await fetch("/my-ad-interaction", {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify({ delivery_id: ad.delivery_id, action })});if (!response.ok) throw new Error("Interaction failed");return (await response.json()).ad;}});view.setAd(payload.has_ad ? payload.ad : null);
Interactions
POST /api/v1/web/interact
For consent and quiz, send the clicked button’s action, delivery_id and original user identity. Use the same authentication as the ad request.
{"platform_id": "plt_example","stable_user_id": "customer_42","delivery_id": "imp_example","action": "signed-action-from-button"}
Response: {ok: true, ad}. Replace the card with the returned ad. Advancing a scene does not create another paid impression.
For buttons with a url, open it on click. Do not prefetch advertising links.
View confirmation
POST /api/v1/web/seen
An impression is paid after a view: at least half of the card on screen for at least one second. The browser SDK sends this request itself. With custom rendering, call trackView(ad) or send the request with the same authentication and user identity.
{"platform_id": "plt_example","stable_user_id": "customer_42","delivery_id": "imp_example","view": "view-receipt-from-ad"}
Response: {ok: true}. The view receipt is valid for an hour. A request sooner than one second after issuance returns 409. A button press or a click on the ad link also counts the impression. Ads that are never seen are not paid.
Errors
| Status | Meaning |
|---|---|
400 | Invalid request fields or user identity. |
401 | Missing or invalid API key. |
403 | Platform disabled, site address not allowed or API key belongs to another publisher. |
409 | View confirmation sent sooner than one second after issuance. |
410 | Action or view receipt is invalid or has expired. |
413 | Request body exceeds 64 KiB. |
429 | Request rate limit exceeded. |
503 | Service temporarily unavailable. |
Preserve the app response on error. Do not automatically retry ad requests after network failures or 5xx: every request is a new issuance.