How it works
A thin layer designed to stay out of your way. The SDK is a server-safe fetch helper plus a handful of React client components. It has no CopilotKit or AG-UI dependency.
Two properties shape everything below. It is additive — you add a placement next to content you already render, and nothing else changes. And it is fail-safe — a missing or failed ad never affects your UI.
Package exports
| Export path | Environment | Content |
|---|---|---|
@adzenai/ai | Any (server-safe) | fetchAdzenAd, mapResponseToPlacement, generateIdempotencyKey, FetchAdzenAdOptions, plus core type re-exports (AdzenPlacement, ProcessResponse, ProcessResponseAd, AdzenBaseConfig, …). No "use client" — safe to import from server code. |
@adzenai/ai/react | Browser (React ≥18) | Generic React/Next entry ("use client"): AdzenCard, InlineAd, useAdImpressions, useAdzenPlacement, useAdzenInlineAds, buildInlineAdSegments. |
The @adzenai/ai/copilotkit and @adzenai/ai/copilotkit/react entries add the AG-UI middleware and event bridge; see the CopilotKit: How it works.
Public API summary
Server side
| Export | Type | Description |
|---|---|---|
fetchAdzenAd | Function | fetchAdzenAd(opts: FetchAdzenAdOptions): Promise<AdzenPlacement | null>. POSTs to /process and returns the first matched ad as an AdzenPlacement, or null on no-match / non-200 / timeout / error. Never throws. Sends the API key as X-API-Key — call it server-side. |
mapResponseToPlacement | Function | mapResponseToPlacement(ad: ProcessResponseAd, adUnitPositionFallback?): AdzenPlacement. Pure field-rename mapping from the raw API ad to AdzenPlacement. |
generateIdempotencyKey | Function | Generates the per-request Idempotency-Key (/process returns 400 without one). |
FetchAdzenAdOptions | Interface | See Configuration. |
React
| Export | Type | Description |
|---|---|---|
AdzenCard | Component | Renders a sponsored ad card. Dual-mode: pass a resolved ad: AdzenPlacement (generic) or a messageId (CopilotKit). Returns null when there is no ad. Fires impression beacons via useAdImpressions. |
useAdImpressions | Hook | useAdImpressions(ad, options?) => { ref }. Attach the ref to your own ad element to get render + view impression tracking while rendering custom markup. |
InlineAd | Component | Renders an in-message creative as a plain text snippet. Used by the streaming (CopilotKit) flow, which is coming soon; takes an InlineAdEvent directly. |
useAdzenPlacement / useAdzenInlineAds / buildInlineAdSegments | Hooks / Function | Event-driven helpers for the CopilotKit/AG-UI streaming flow, coming soon (they subscribe to window CustomEvents). Exported here for completeness; a plain fetch-and-render app does not need them. |
Types (from @adzenai/core, re-exported by @adzenai/ai)
| Type | Description |
|---|---|
AdzenPlacement | Ad placement data: ad_id, advertiser_name, advertiser_image_url?, headline, description?, cta_text, destination_url, creative_url?, adUnitPosition, render_impression_url, view_impression_url. |
ProcessResponse | API response shape: message_id, ads array. |
ProcessResponseAd | Raw ad object from the API. |
Architecture
Data flow
Content (message / article / …)
│
▼
Server: fetchAdzenAd({ apiKey, content, messageId, … })
│ POST /process headers: Content-Type, X-API-Key, Idempotency-Key, [X-Profile-Id]
│ body: { content, message_id, [location], [conversation_id] }
▼
Adzen /process → { message_id, ads: [...] }
│
├─ ads.length === 0 → fetchAdzenAd returns null → nothing renders
└─ first ad → mapResponseToPlacement → AdzenPlacement (returned to client)
│
▼
<AdzenCard ad={placement} /> or useAdImpressions(placement) + your markup
│
├─ on mount → GET render_impression_url
└─ after viewability → GET view_impression_urlAd fetch lifecycle
- Your server calls
fetchAdzenAdwith the content and your key. - It POSTs to
${endpointUrl}/processwith a freshIdempotency-Key(required — the API returns 400 without one) and anAbortControllerbound totimeoutMs(unless you pass your ownsignal). - On a 2xx with a non-empty
adsarray, the first ad is mapped toAdzenPlacementand returned. - On no-match, non-200, timeout, or any thrown error, it returns
null.
AdzenAsyncMiddleware (the CopilotKit async middleware) delegates its ad fetch to fetchAdzenAd, so the middleware and the generic path share identical fetch behavior.
Impression tracking
Impression beacons are client-side GET requests fired directly to the delivery API — no server proxy or auth headers. The impression URLs are pre-built by the API with all identifiers baked in.
- Render impression — fired on mount, as a
GETtorender_impression_url. - View impression — fired after the element is continuously visible for the threshold (default 1000ms), as a
GETtoview_impression_url.
Viewability uses IntersectionObserver at a 1.0 threshold via @adzenai/core’s observeViewability(). A failed beacon is retried once after 1 second, then dropped. null impression URLs are skipped.
Why viewability matters to you. Counting an impression only once the ad has actually been seen means your impressions reflect ads people actually saw.
Server and client boundary (Next.js)
@adzenai/aicarries no"use client"directive — import it from route handlers, server actions, and Server Components. It sends the API key, so it must stay on the server.@adzenai/ai/reactcarries"use client"—AdzenCard,useAdImpressions, etc. are client components. Render them in client components and pass down theAdzenPlacementyou fetched on the server.
Silent degradation
| Scenario | Behavior |
|---|---|
API returns empty ads | fetchAdzenAd → null; nothing renders |
| API times out / non-200 / network error | fetchAdzenAd → null |
ad prop is null | AdzenCard renders nothing |
| Impression beacon fails | Retried once, then swallowed |
Supported runtimes
| Requirement | Value |
|---|---|
| Node.js | >=18 (global fetch) for fetchAdzenAd |
| React | >=18 for @adzenai/ai/react |
| Next.js | App Router and Pages Router; fetch on the server, render the card in a client component |
Next
- Integration walkthrough — step-by-step wiring
- Configuration — options, props, beacons, environment variables
- Quick start — your first matched ad