Public API
The read-only HTTP endpoints the Web SDK uses to load published screens and send events, with response shapes.
The Web SDK talks to these endpoints for you. You only need this page to debug, to self-check an integration, or to build your own client. The base URL is the API address, followed by /v1. All SDK endpoints are under /v1/sdk.
Authentication
Every /v1/sdk request carries your project's public key in the query string: ?key=pk_.... A missing, wrong or revoked key answers 401 with code invalid_key.
If the project has allowed websites, a request whose Origin header is not on the list answers 403 with code origin_not_allowed. Any origin may call these endpoints otherwise (CORS is open for GET and POST).
Errors have this shape:
{ "error": { "code": "invalid_key", "message": "Missing or invalid public key" } }Caching
Delivery responses carry an ETag and Cache-Control: public, max-age=0, s-maxage=30, stale-while-revalidate=30. Send If-None-Match to get 304. A new publish reaches sites within about a minute.
GET /v1/sdk/placements/:key
The screen for a placement. Add v=2 if your client can draw toast, side drawer and launcher formats; without it, placements with those formats return display: null.
{
"placement": "upgrade_button",
"paywallId": "uuid",
"version": 4,
"paywall": { "default_locale": "en_US", "components_config": { "base": {} }, "components_localizations": {} },
"packages": ["monthly", "annual"],
"experiment": { "id": "uuid", "shareB": 50, "b": { "paywallId": "uuid", "version": 2, "paywall": {}, "packages": [] } },
"rules": [{ "id": "rule1", "conditions": [{ "field": "user.plan", "op": "eq", "value": "free" }], "paywall": null }],
"frequency": { "max": 3, "per": "day" },
"holdout": { "id": "placementId:timestamp", "share": 10 },
"display": { "format": "modal", "size": "medium" },
"trigger": { "type": "delay", "seconds": 10, "pages": [], "priority": 0 }
}| Field | Meaning |
|---|---|
paywall | The published screen JSON. A schedule in force replaces it. |
packages | Package IDs your site must pass prices for |
experiment | A running A/B test with side B, or null. Always null while a schedule is in force or when the plan lacks A/B tests. |
rules | Audiences in order. paywall: null means show nothing. Audiences whose screen is unpublished are left out. |
frequency, holdout | Cap and holdout, or null |
display, trigger | How and when it shows, or null |
Errors: 404 not_found when the key matches no placement, or the placement has no published screen.
GET /v1/sdk/paywalls/:paywallId
One published screen by ID. The response has placement: null, paywallId, version, paywall and packages. Used for Go to screen and for paywallId sources. 404 if it is unknown or not published.
GET /v1/sdk/auto
The placements that show themselves, highest trigger priority first. Only placements with a trigger other than manual, a format this client can draw, and a published screen are listed.
{ "placements": [{ "placement": "exit_offer", "display": { "format": "modal" }, "trigger": { "type": "exit_intent", "pages": ["/pricing"], "priority": 10 } }] }The SDK then loads each screen through the placement endpoint when its trigger fires.
POST /v1/sdk/events
Sends a batch of events. The body is JSON, and text/plain is accepted too so sendBeacon works. It answers 204 with no body.
{ "events": [{ "type": "view", "paywallId": "uuid", "version": 4, "placement": "upgrade_button", "visitorId": "random-id-8-to-64-chars", "occurredAt": "2026-10-06T10:00:00Z" }] }| Field | Notes |
|---|---|
type | view, close, purchase_click, purchase, purchase_cancel, purchase_error, restore, action, holdout, holdout_purchase, answer |
visitorId | Required, 8 to 64 characters |
paywallId, version, placement | Which screen. placement up to 64 characters. |
packageId | Up to 64 characters |
actionId | For action; must match the action ID pattern, else the event is dropped |
fieldId, optionId | For answer; must match the answer ID pattern, else dropped |
experimentId, variant | variant is a or b |
occurredAt | Optional; ignored if more than a day from now |
A batch holds 1 to 50 events. More than 1,200 events a minute for one key answers 429 rate_limited. A screen ID that does not belong to the project is stored without a screen.
GET /v1/preview/:token
Public. The latest saved draft behind a preview link: name, savedAt, paywall, packages. Never cached. 404 if the link is off.