Server API
Quick answer
Use the Server API when your backend owns sessions, orchestration, or rollout policy and must keep secret keys off browser clients.
POST /v1/placements creates the default placement response, while the hosted renderer owns browser media rendering.
Server API is the recommended default backend path. Use it when your backend already owns session state, orchestration, or rollout policy, while Wavebird owns hosted media rendering in the browser.
.env.local
bash
create-placement.sh
bash
app/api/sponsor-slot/route.ts
typescript
chat.html
html
advanced-policy-controls.json
json
direct-server-beacon.mjs
javascript
Backend control
Why use the server path
Own the lifecycle
Request placements from your backend without exposing secret keys to the browser.
Keep secrets server-side
The secret-key path is simpler when your app already has a backend and does not need direct browser-side monetization control.
Pair with your renderer
Use the hosted renderer for the default path, or keep raw decision polling only for advanced compatibility flows.
Credentials
Server Test Key
Start with a Server Test Key from Dashboard API Keys. The full sk_test_... value is shown only when the key is created or rotated, so copy it immediately into WAVEBIRD_SECRET_KEY. If the dashboard only shows a masked preview, create or rotate a Server Test Key before running the Server API examples. Test requests are non-billable.
REST placement
Controlled request fields
Send controlled format, size, position, and consent values to your same-origin sponsor-slot endpoint. That server route validates the body and forwards only supported fields to Wavebird. Never put API keys, raw prompts, chat text, identities, or other secrets in the browser request. The renderer does not read adata-wavebird-request attribute.
Required fields
What the first Test request needs
Project and session
client_id selects the Wavebird project and must match the Server Test Key. session_id is a stable anonymous identifier for one test conversation. Do not use an email address, account ID, prompt, or other personal identifier.
Request type
job_type describes the product surface, such as chat, code, image, voice, or agent. It does not contain the user's message. slots_requested defaults to one.
Consent is not optional for this Test setup
Dashboard-created Test projects use an authoritative consent lifecycle. Include the request-level consent object shown above or sync a current record first. Omitting both can return 403 consent_not_current.
Synthetic Test boundary
The example uses gdpr_applies: false only for synthetic Test sessions that do not represent real users. For real-user traffic, derive jurisdiction and consent from the actual request and complete the required legal setup first.
Request controls
Why the consent fields are present
semantic_targeting
false requests context-only, non-personalized matching. Set it to true only when the active consent and project policy permit semantic targeting.
prompt_shared
false keeps raw prompts and chat text out of matching and partner requests. A broad controlled topic can still be sent separately when appropriate.
gdpr_applies
States whether GDPR applies to this request. Do not infer it from a convenient default in real traffic; derive it through the publisher's jurisdiction logic or CMP.
consent_source
Use wavebird_consent for the Wavebird consent lifecycle, wrapper_cmp for an authoritative publisher CMP, or none only where the approved project policy permits it.
Browser lifecycle
Filled, no-fill, and render status
Filled
The canonical API response has no top-level filled field. A fill has placement and normally decision.fill: true. Pass placement, optional decision, and authoritative_consent to renderPlacement.
No-fill
When placement is absent and decision.fill is false, hide or clear the slot and continue the normal app flow. Read decision.no_fill_reason for the controlled reason. No-fill is not an integration error.
Classify every successful response as filled, no_fill, not_ready, or invalid_response. Only filled may be passed to the hosted renderer.
To exercise this branch deterministically in Test, use /v1/placements?wait_ms=1500&test_outcome=no_fill with a Server Test Key. Remove the parameter for ordinary fill requests; Production keys reject it.
Render failure
Listen for the controlled ad:render_failed event or inspect data-wavebird-status="frame_error". Do not invent a successful render signal.
The hosted renderer waits for the creative medium before sending positive render and visibility signals. Treat the render as successful only when renderPlacement() returns a cleanup function. Anull result, ad:render_failed, or data-wavebird-status="frame_error" is a controlled render failure. A mounted frame alone is not proof of a completed render. withTurn()is an optional Script Tag lifecycle helper and is not required for this REST path.
Hosted renderer
Publisher Content Security Policy
Load https://api.wavebird.ai/v1/render.js directly and allow https://api.wavebird.aiin the publisher's script-src, frame-src, and connect-src directives. Do not proxy the renderer, hosted creative frame, or beacons through the publisher app.
Credentials
Key classes
Server Test Key
Use sk_test_... only from your backend for Test requests. These requests are non-billable.
Server Production Dry-run Key
Use sk_dry_... only from your backend for non-billable pre-live dry-run. Dry-run is selected by credential class, not by request fields.
Server Production Key
Use a Server Production Key only after Dashboard live readiness, SSP readiness, payout requirements, and live approval gates are complete.
Browser Publishable Key
Use a Browser Publishable Key only for browser activation and Script Tag flows with allowed origins. Do not put server secrets in browser code.
Raw server secrets are shown only once after create or rotate. Existing masked server-secret previews identify a key but are not usable credentials. Do not send production_dry_run, production_billing_dry_run, billing_suppressed, or production_live_approved in request bodies.
Contracts
Consent and beacon boundaries
Lifecycle consent
A valid authoritative lifecycle record is required for rendering and measurement. Refused, expired, or revoked lifecycle consent produces no render and no beacons.
Request-level placement consent
Send semantic_targeting, prompt_shared, gdpr_applies, and consent_source inside /v1/placements when consent is scoped to the current request. semantic_targeting: false means context-only matching; prompt_shared: false keeps raw prompts and chat text out.
Separate /v1/consent sync
Use /v1/consent only when you want to persist a session or user consent decision outside a single placement request.
Hosted renderer beacons
The default renderer path sends browser beacons automatically to /public/wrapper/v1/beacons. Most integrations do not need manual beacon code.
Direct server beacons
Use /v1/beacons only for advanced server-rendered, custom-rendered, or QA validation flows. It requires the issued asset_token, a fresh occurred_at, and idempotent beacon_id values.
Troubleshooting
Timestamp freshness
Avoid BEACON_TOO_LATE
occurred_at must be close to when the event actually happened. Copying an old static sample timestamp can return BEACON_TOO_LATE. Generate it at runtime with const occurred_at = new Date().toISOString();.
Need rollout review?
Start with the Server API. Use contact only when you need rollout review, enterprise coordination, or non-standard integration help.