Skip to Content
API referencePublisher insights

Publisher insights

GET /v1/publisher/insights returns your performance on AI Surfaces, on the web or on UGC for a date range: delivered impressions, clicks and estimated earnings, plus requests and fill on AI Surfaces and UGC, viewed impressions on AI Surfaces and the web, and page views on the web. Rows come by day, by month or as one total, and can be split by country and US state.

EndpointAuthPurpose
GET /v1/publisher/insightsyour AI API key, or a reporting keyYour reporting for a date range

Reporting is enabled per account. If your key gets 403 reporting_not_enabled, ask your Adzen account representative to turn it on.

Request

GET https://api.adzen.ai/v1/publisher/insights

The path with a trailing slash (/v1/publisher/insights/) also works.

Headers

HeaderRequiredValue
X-API-KeyOne of X-API-Key or AuthorizationYour key (see below)
AuthorizationOne of X-API-Key or AuthorizationBearer <key>. If you send both headers, Adzen uses X-API-Key.

Which key you use depends on your surface:

  • AI Surfaces: the key you use for POST /v1/ai/process.
  • Web and UGC: a read-only reporting key. Ask your Adzen account representative for one. It works on this endpoint only. A UGC partner’s /v1/ugc key doesn’t work here, and the reporting key doesn’t work there.

You read only the data of the account that owns the key. An account is on one surface, and meta.surface says which (ai, web or ugc). This endpoint rejects any parameter not listed below with 400 unknown_parameter.

Parameters

ParameterRequiredValuesDefault
start_dateyesYYYY-MM-DD, in timezone
end_dateyesYYYY-MM-DD, in timezone, inclusive
timezonenoAn IANA zone whose offset from UTC is a whole number of hours, such as America/New_York. Other zones return unsupported_timezone.UTC
granularitynoday, month, totalday
breakdownsnocountry, region, or country,regionnone
  • The range is at most 93 days, and end_date can’t be after the current date in timezone.
  • Only an omitted parameter takes its default. A parameter sent with an empty value is an error: timezone= returns unsupported_timezone, start_date= or end_date= returns invalid_date, and breakdowns= returns invalid_breakdown.
  • Days and months are local to timezone. A day that changes clock time has 23 or 25 hours, and still comes back as one row.
  • month rows are clamped to your range: a range from the 13th to the 29th returns one row with those 17 days.
  • Without breakdowns, total returns exactly one row, even when every count is zero. With breakdowns, total returns one row per country or region that has activity, and one all-zero row with the breakdown fields set to null when there is no activity.
  • breakdowns=region adds both country and region. Values in breakdowns can come in any order, with spaces around the comma.

Example

curl -G https://api.adzen.ai/v1/publisher/insights \ -H 'X-API-Key: YOUR_API_KEY' \ -d start_date=2026-10-22 \ -d end_date=2026-10-24 \ -d granularity=total

Response

{ "data": [ { "page_views": null, "ad_requests": 6, "blocked_requests": 1, "eligible_requests": 5, "matched_requests": 4, "fill_rate": 0.666667, "eligible_fill_rate": 0.8, "impressions": 6, "viewed_impressions": 3, "clicks": 2, "invalid_impressions": 1, "invalid_clicks": 1, "ctr": 0.333333, "viewed_ctr": 0.666667, "estimated_revenue_micros": 1575000, "rpm_micros": 262500000, "ecpm_micros": 262500000, "epc_micros": 787500 } ], "meta": { "surface": "ai", "start_date": "2026-10-22", "end_date": "2026-10-24", "timezone": "UTC", "currency": "USD", "data_available_end": "2026-10-30T00:35:01.020458Z", "first_incomplete_date": null, "warnings": [] } }

With granularity=day each row also has date (YYYY-MM-DD); with month, a month (YYYY-MM). With breakdowns=region, each row has country and region:

{ "date": "2026-10-22", "country": "US", "region": "US-CA", "page_views": null, "ad_requests": 1, "blocked_requests": 0, "eligible_requests": 1, "matched_requests": 1, "fill_rate": 1.0, "eligible_fill_rate": 1.0, "impressions": 2, "viewed_impressions": 2, "clicks": 1, "invalid_impressions": 1, "invalid_clicks": 0, "ctr": 0.5, "viewed_ctr": 0.5, "estimated_revenue_micros": 875000, "rpm_micros": 875000000, "ecpm_micros": 437500000, "epc_micros": 875000 }

On the web the same row has page_views, and the request funnel is null (web pages don’t send ad requests the way an AI integration does):

{ "date": "2026-10-18", "country": "US", "region": "US-CA", "page_views": 3, "ad_requests": null, "blocked_requests": null, "eligible_requests": null, "matched_requests": null, "fill_rate": null, "eligible_fill_rate": null, "impressions": 1, "viewed_impressions": 1, "clicks": 1, "invalid_impressions": 1, "invalid_clicks": 0, "ctr": 1.0, "viewed_ctr": 1.0, "estimated_revenue_micros": 611525, "rpm_micros": 203841666, "ecpm_micros": 611525000, "epc_micros": 611525 }

On UGC the row has the request funnel, no page views, and no viewed impressions (see Viewed impressions). With a location breakdown, requests are in the row with no location, because a request has no viewer:

{ "date": "2026-10-28", "country": "US", "region": "US-CA", "page_views": null, "ad_requests": 0, "blocked_requests": 0, "eligible_requests": 0, "matched_requests": 0, "fill_rate": null, "eligible_fill_rate": null, "impressions": 4, "viewed_impressions": null, "clicks": 1, "invalid_impressions": 1, "invalid_clicks": 1, "ctr": 0.25, "viewed_ctr": null, "estimated_revenue_micros": 500000, "rpm_micros": null, "ecpm_micros": 125000000, "epc_micros": 500000 }

Rows

  • With day or month, a row comes back only for a period, country and region that has at least one event. A day with no activity has no row. Treat a missing row as zero.
  • Rows are sorted by period, oldest first, then by country, then by region. A null country or region sorts first.
  • A response is at most 20,000 rows. A request that would return more fails with 400 too_many_rows. Narrow the date range or the breakdowns.

Fields

Every row has every field. A field that doesn’t apply to your surface is null, never 0:

FieldAI SurfacesWebUGC
page_viewsnullcountednull
ad_requests … eligible_fill_ratecountednullcounted
viewed_impressions, viewed_ctrcountedcountednull
everything elsecountedcountedcounted

Counts

FieldWhat it counts
page_viewsWeb only: valid page views on your sites (pages where the Adzen tag ran).
ad_requestsAI Surfaces: unique POST /v1/ai/process requests. Requests that share an Idempotency-Key count once, dated by the first attempt. A request sent without Idempotency-Key counts on its own, so a retry without the same key counts again. See Idempotency and retries. UGC: every POST /v1/ugc/process call Adzen received, retries included. Test traffic is left out.
blocked_requestsAI Surfaces: requests not served because location was missing or did not start with US- (case-sensitive, for example US-CA). A request that was retried and then served counts as served, not blocked. UGC: requests Adzen couldn’t evaluate (an invalid request, a disabled account, or an error on Adzen’s side).
eligible_requestsRequests sent to matching.
matched_requestsRequests that got an ad in that response.
impressionsValid delivered impressions: render beacons received (AI Surfaces), ad units rendered on the page (web), impression pixels fired when the post displayed the ad (UGC).
viewed_impressionsValid viewed impressions. See Viewed impressions.
clicksValid clicks.
invalid_impressionsImpressions judged invalid (bots, blocked IPs, duplicates). Not included in impressions.
invalid_clicksClicks judged invalid. Not included in clicks and never earn.

Rates. Fractions from 0 to 1, rounded to 6 places. A rate is null, not 0, when its denominator is 0.

FieldFormula
fill_ratematched_requests / ad_requests
eligible_fill_ratematched_requests / eligible_requests
ctrclicks / impressions
viewed_ctrclicks / viewed_impressions

Earnings. All money is in micros of USD (1 USD = 1,000,000 micros), rounded down.

FieldFormula
estimated_revenue_microsYour share of what advertisers paid for this traffic
rpm_microsEarnings per 1,000 ad_requests (AI Surfaces, UGC) or page_views (web)
ecpm_microsEarnings per 1,000 impressions
epc_microsEarnings per click

rpm_micros, ecpm_micros and epc_micros are null when their denominator is 0 or earnings aren’t available.

Location

country (ISO 3166-1, such as US) and region (ISO 3166-2, such as US-CA) are set as follows:

  • AI Surfaces: from the location you sent on POST /v1/ai/process. A location that Adzen can’t parse gives country alone, or neither. Blocked requests have no location, so their rows have country and region set to null.
  • Web: from the visitor’s connection, as the Adzen edge sees it. region is set for US visitors only. Traffic from before Adzen began recording location, and the rare visitor Adzen can’t place, is in rows with country and region set to null. The location_partial warning says when your window reaches back that far.
  • UGC: from the viewer’s connection, as on the web, for impressions, clicks and earnings. Requests have no viewer, so with a breakdown every request is in the rows with country and region set to null. Viewer location is recorded from November 4, 2026. Earlier traffic is in the null rows, and location_partial says so.

On every surface the rows of a breakdown add up to the totals.

Dating rules

  • Requests are dated when you sent them (AI Surfaces: the first attempt). Page views (web) are dated when the page was viewed.
  • UGC data starts on September 11, 2026. Earlier days return no rows.
  • Impressions and clicks are dated when they happened, so a click can land on a later day than its request.
  • Earnings are dated by the event that was charged: the click for CPC campaigns, the viewed impression for CPM campaigns.

meta

FieldMeaning
surfaceai, web or ugc: the surface your account is on
start_date, end_date, timezoneThe window you asked for, as resolved
currencyAlways USD
data_available_endUTC time up to which the data covers every event. Events after this time can be missing. null when Adzen can’t determine it. Counts before this time can still change until they pass first_incomplete_date.
first_incomplete_dateThe earliest date in your window that ended less than 72 hours ago, or null. Counts on or after it can still change.
warnings{code, message} objects, listed below
WarningMeaning
revenue_not_enabledEarnings aren’t enabled for your account. Earnings fields are null.
revenue_unavailableEarnings couldn’t be computed for this call. Earnings fields are null; counts are complete. Retry later.
data_delayedRecent data is delayed; check data_available_end.
location_partialWeb and UGC: your breakdown reaches back before visitor location was recorded. That traffic is in the rows with a null country.
no_sitesWeb: no sites are registered to your account yet, so there is nothing to report.

Finality and estimates

Data refreshes about every 15 minutes. meta.data_available_end gives the point the data covers.

Counts can change for 72 hours after a day ends, as late events arrive and invalid traffic is removed. Pull recent days again until they are past first_incomplete_date.

estimated_revenue_micros is not your payout. Your payout comes from the monthly statement, which can differ after invalid-traffic review and adjustments. Earnings are computed per row and rounded down, so breakdown rows can add up to a little less than the total row (at most 1 micro per row). Counts always add up exactly.

Viewed impressions

  • Web: the Adzen tag measures it. An ad counts as viewed once any part of it has been on screen for one continuous second.
  • AI Surfaces: viewed_impressions counts the view beacons Adzen receives. See Impression beacons for when a view beacon fires.
  • UGC: there is one impression pixel, fired when the post displays the ad. It isn’t a viewability measurement, so viewed_impressions and viewed_ctr are null. Use impressions and ctr.

Errors

Every error has the same shape. param is null when no single parameter caused the error.

{ "error": { "type": "invalid_request_error", "code": "range_too_large", "message": "the date range is limited to 93 days", "param": "end_date" } }
StatustypecodeWhenRetry?
400invalid_request_errorinvalid_dateA date is missing, empty, malformed, out of order, or after the current dateNo
400invalid_request_errorrange_too_largeThe range is over 93 daysNo
400invalid_request_errorunsupported_timezoneUnknown or empty zone, or an offset from UTC that is not a whole number of hoursNo
400invalid_request_errorinvalid_granularityNot day, month or totalNo
400invalid_request_errorinvalid_breakdownEmpty, not country / region, or repeatedNo
400invalid_request_errorunknown_parameterA parameter this endpoint doesn’t takeNo
400invalid_request_errorduplicate_parameterThe same parameter twiceNo
400invalid_request_errortoo_many_rowsThe response would be over 20,000 rows; narrow the range or the breakdownsNo
401authentication_errormissing_api_keyNo keyNo
401authentication_errorinvalid_api_keyUnknown key, or a key for another productNo
403permission_erroraccount_disabledThe account is disabledNo
403permission_errorreporting_not_enabledReporting isn’t enabled for this accountNo
403permission_errorsurface_not_supportedReporting isn’t available for this account’s surface yetNo
403Not JSONNot JSONThe request came from an IP address outside the USNo
404not_found_errornot_foundAn unknown path under /v1/publisher/insights/, for example /v1/publisher/insights/xNo
405invalid_request_errormethod_not_allowedNot a GETNo
429rate_limit_errorrate_limitedOver the rate limit. The response has Retry-After.Yes, after Retry-After seconds
500api_errorinternal_errorAn error on Adzen’s sideYes, with backoff
503api_errorauthentication_unavailableA temporary authentication errorYes, with backoff
503api_errorsites_unavailableWeb: your sites couldn’t be looked up just nowYes, with backoff
503api_errorbusyAdzen can’t run the report at the moment. The response has Retry-After: 2.Yes, after Retry-After seconds
503api_errortimeoutThe report took too long to buildYes, or narrow the date range or breakdowns

401 responses carry WWW-Authenticate: ApiKey realm="adzen", header="X-API-Key".

Adzen checks a request in this order and returns the first error it finds:

  1. The API key (401, 403 account_disabled, 503 authentication_unavailable).
  2. Whether reporting is enabled (403 reporting_not_enabled).
  3. The rate limit (429).
  4. The parameters (400).

Rate limit

60 requests per minute for each publisher account, counted across all keys of that account. The window is the clock minute: the count resets at the start of each minute, not 60 seconds after your first request.

Every request that passes the key and reporting checks counts, including requests that then fail with 400, 429 or 503.

Successful responses and 429s carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the current minute ends). A 429 adds Retry-After with the same value. Wait that many seconds before retrying. See Rate limits for limits across products.

Polling

Poll at most once every 15 minutes. Use breakdowns to get every country and region in one call. When first_incomplete_date is not null, request the days from that date to the current date on each poll and replace your stored counts for those days.

Last updated on