Skip to Content

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 pathEnvironmentContent
@adzenai/aiAny (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/reactBrowser (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

ExportTypeDescription
fetchAdzenAdFunctionfetchAdzenAd(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.
mapResponseToPlacementFunctionmapResponseToPlacement(ad: ProcessResponseAd, adUnitPositionFallback?): AdzenPlacement. Pure field-rename mapping from the raw API ad to AdzenPlacement.
generateIdempotencyKeyFunctionGenerates the per-request Idempotency-Key (/process returns 400 without one).
FetchAdzenAdOptionsInterfaceSee Configuration.

React

ExportTypeDescription
AdzenCardComponentRenders 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.
useAdImpressionsHookuseAdImpressions(ad, options?) => { ref }. Attach the ref to your own ad element to get render + view impression tracking while rendering custom markup.
InlineAdComponentRenders 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 / buildInlineAdSegmentsHooks / FunctionEvent-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)

TypeDescription
AdzenPlacementAd placement data: ad_id, advertiser_name, advertiser_image_url?, headline, description?, cta_text, destination_url, creative_url?, adUnitPosition, render_impression_url, view_impression_url.
ProcessResponseAPI response shape: message_id, ads array.
ProcessResponseAdRaw 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_url

Ad fetch lifecycle

  1. Your server calls fetchAdzenAd with the content and your key.
  2. It POSTs to ${endpointUrl}/process with a fresh Idempotency-Key (required — the API returns 400 without one) and an AbortController bound to timeoutMs (unless you pass your own signal).
  3. On a 2xx with a non-empty ads array, the first ad is mapped to AdzenPlacement and returned.
  4. 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 GET to render_impression_url.
  • View impression — fired after the element is continuously visible for the threshold (default 1000ms), as a GET to view_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/ai carries 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/react carries "use client" — AdzenCard, useAdImpressions, etc. are client components. Render them in client components and pass down the AdzenPlacement you fetched on the server.

Silent degradation

ScenarioBehavior
API returns empty adsfetchAdzenAd → null; nothing renders
API times out / non-200 / network errorfetchAdzenAd → null
ad prop is nullAdzenCard renders nothing
Impression beacon failsRetried once, then swallowed

Supported runtimes

RequirementValue
Node.js>=18 (global fetch) for fetchAdzenAd
React>=18 for @adzenai/ai/react
Next.jsApp Router and Pages Router; fetch on the server, render the card in a client component

Next

Last updated on