Skip to Content
API referencePOST /v1/ugc/process

POST /v1/ugc/process

Match a contextual ad to a piece of user-generated content.

Request

Endpoint

POST https://api.adzen.ai/v1/ugc/process

Headers

HeaderRequiredValue
X-API-KeyYesYour API key
Content-TypeYesapplication/json
X-Forwarded-ForNoClient IP (for geo targeting)

Body

{ post_id: string // Your unique identifier for the post message: string // The post text. Must be non-empty. created_at?: string // When the source post was created, RFC3339 / ISO 8601 // (e.g. "2026-06-24T14:30:45Z"). Omit or send null if // unknown. geo_target?: { // Structured geo signal for the requesting user. zipcode?: string // US ZIP, e.g. "10001" state?: string // ISO-3166-2 ("US-CA") or bare code ("CA") city?: string // e.g. "New York" — pair with state to disambiguate dma?: string // Nielsen DMA code, e.g. "501" lat?: number // Decimal degrees. Must be sent together with lon. lon?: number // Decimal degrees. Must be sent together with lat. country?: string // ISO country code. Defaults to "US" when omitted. } }

Send any one geo_target signal — an ad targeted at any granularity containing the user still matches. Precedence, richest to coarsest: lat+lon → zipcode → city → dma → state. A country-only value, or a half lat/lon pair, is not a usable signal and is ignored.

Example

curl -X POST https://api.adzen.ai/v1/ugc/process \ -H 'Content-Type: application/json' \ -H 'X-API-Key: YOUR_API_KEY' \ -d '{ "post_id": "thread-12345", "message": "Looking for a good plumber in Seattle", "geo_target": { "state": "US-WA" } }'

Response

200 OK

{ "post_id": "thread-12345", "ads": [ { "title": "Need a plumber? Same-day service in Seattle", "cta": "Book Now", "click_through_url": "https://api.adzen.ai/click/abc123", "advertiser_name": "Seattle Plumbing Co", "ttl_seconds": 300, "pixel_trackers": [ { "vendor": "adzen", "tracker_type": "impression", "url": "https://api.adzen.ai/pixel/abc123" } ] } ] }

Response fields

FieldTypeDescription
post_idstringEcho of the request post_id
adsarrayMatched ads, ordered by relevance. Can be empty.
ads[].titlestringShort headline for the ad
ads[].ctastringCall-to-action button text
ads[].click_through_urlstringURL to open on ad click. Use it as given.
ads[].advertiser_namestringHuman-readable advertiser name
ads[].ttl_secondsintegerRefresh the match after this many seconds
ads[].pixel_trackersarrayThird-party pixels to fire when the ad is displayed

Error responses

StatusMeaning
400Validation error (missing post_id, empty message, message too long)
401Missing or invalid API key
502Temporary server error — retry with backoff

No match

When no ad matches the post, the response is still 200 OK, but the body has a different shape: a single field and no ads array.

{ "no_ad_retry_after_seconds": 86400 }
FieldTypeDescription
no_ad_retry_after_secondsintegerDon’t request this post again until this many seconds have passed

The window is usually hours, and it grows each time the same post misses again. It can be much shorter, down to a few minutes, when Adzen is still looking for a match for the post. Honor the value you receive rather than a fixed interval.

Check for no_ad_retry_after_seconds before you read ads. Code that assumes every 200 has an ads array will fail on this body. The sandbox returns it on about one in five requests so you can test this path.

You can also receive { "post_id": "...", "ads": [] }. An empty ads array is not an error either: it means there is no ad to show for this post right now.

Last updated on