Pixtru docs

API reference

The dashboard is a client of this API, not a privileged sibling. Anything a screen can show, you can pull — which also means the API cannot quietly fall behind the product, because the product would break first.

Authentication

A bearer token on every request. Two kinds, telling themselves apart by shape, and both resolving to the same internal principal — so there is one place a scoping bug can live rather than two.

# a machine, with an API key
curl https://app.pixtru.com/v1/recon/summary?from=2026-09-01&to=2026-09-30 \
  -H "Authorization: Bearer pxk_live_..."

# a human, with a session token from the dashboard
curl ... -H "Authorization: Bearer eyJraWQiOi..."

Scope is never a parameter. A request may say which campaign it wants; it may not say which advertiser it is allowed to see. That is derived from the credential on every request. Asking for an advertiser you do not hold returns an empty result, not a 403 — a refusal would confirm that the advertiser exists and belongs to somebody else.

Keys

Keys are shown once and stored only as a hash. Revocation takes effect on the next request — there is no cache to wait out, because a leaked key is the realistic incident. The first twelve characters are a readable prefix, so a key can be identified in a list or a support thread without ever being handled.

Endpoints

Roles go viewer → analyst → owner, each able to do everything the one before can. deliverer stands apart: a key for an upload job, which can deliver log files and read what became of them, and cannot read a single report. Analysts and owners can deliver too.

EndpointReturnsMin role
GET /v1/recon The waterfall: impressions and spend per stage, reason and match tier. viewer
GET /v1/recon/summary One row for the headline — billed, rendered, viewable, valid, and the spend attached to each. viewer
GET /v1/recon/impressions Row level, one line per impression. Keyset paginated for export. analyst
GET /v1/sites/quality Per site or app, sorted by wasted spend rather than volume. viewer
GET /v1/campaigns/{id}/live The last two hours of beacons. Always preliminary — it reads raw events, not the reconciled table. viewer
GET /v1/measured What the tag measured, with or without a billing log: impressions, measurable, viewable and invalid traffic per day, campaign and website. CSV on the same path. viewer
POST /v1/org/logs/uploads Declare an hourly log file and get a 15-minute upload link for it. See the integration guide. deliverer
GET /v1/org/logs What became of each delivered log file in the last 14 days: loaded, or rejected and why. deliverer

The response envelope

Every response carries how fresh it is. A number that might still move and a number that is settled must never look identical, which is the mistake that turns a dashboard into a support ticket.

{
  "data": [
    { "stage": "measurable", "reason": "below_threshold",
      "match_tier": "exact", "impressions": 1496,
      "spend": 2.71, "revenue": 1.95 }
  ],
  "rows": 11,
  "data_status": "preliminary",
  "as_of": "2026-09-18T14:05:00Z"
}
FieldMeaning
data_statuspreliminary until the day is closed and the roll-up is final; final after. Show it — do not hide it behind a tooltip.
as_ofWhen the underlying roll-up last ran, not when you asked.
match_tierexact, partner_id, fuzzy or none. Never sum across tiers without saying so.

Pagination

Row-level exports are keyset paginated, not offset paginated. Pass the last imp_id you saw as after. Offsets drift when rows arrive mid-export, which silently skips or repeats impressions — precisely the failure a reconciliation product cannot have.

GET /v1/recon/impressions?day=2026-09-18&after=a3f9...&limit=5000

Errors

StatusWhen
400A malformed or missing parameter. The message names it.
401Missing, expired or revoked credential. Deliberately undetailed: “expired” and “no such key” are useful to someone enumerating credentials and identical to you.
403Authenticated, but the role is too low for this endpoint. Never used for scope.
429Rate limited. Retry after the header says.
5xxOurs. The response carries a request id; send it to us.

CSV

Add format=csv to any endpoint for the same data with a header row and a Content-Disposition filename. The freshness label moves to the X-Data-Status header, so a spreadsheet does not silently lose it.