API reference.
REST over HTTPS, JSON only, bearer-authenticated. See Conventions for versioning, idempotency, errors, pagination, and rate limits. Sample tokens below are placeholders, mint your own from your VERSE workspace.
Surfaces
The API groups into ten surfaces. Each surface scopes by token; an Ads Manager token cannot read screens, a Screen Manager token cannot launch campaigns. Agency tokens can act on the sub-accounts they own.
| Surface | Audience | Root |
|---|---|---|
| Campaigns | Brands, agencies | /campaigns |
| Creatives | Brands, agencies | /creatives |
| Forecasts | Brands, agencies | /forecasts |
| Reporting | Brands, agencies | /reporting |
| Screens | Venues | /screens |
| Pairing | Venues | /pairing |
| Playlists | Venues | /playlists |
| Payouts | Venues | /payouts |
| Sub-accounts | Agencies | /agency/clients |
| Audit log | All | /audit |
Authentication
Every request carries an Authorization: Bearer sk_live_... header. Tokens are issued from your VERSE workspace under Settings → API keys. Read-write tokens can run any endpoint allowed by the workspace role; read-only tokens can only call GET. The sk_test_... prefix routes to the sandbox.
curl https://api.verse.ad/v1/me \
-H "Authorization: Bearer sk_live_4f3c8b2a..."Tokens are scoped to one workspace. Mint a separate token per integration so a leaked DSP key cannot drain a wallet. Rotate every 90 days; rotation is non-disruptive (both keys are valid for a 24h overlap).
Campaigns and creative
Campaigns
A campaign is a brief, a budget, a schedule, a target plan, and a set of creatives. Once approved, campaigns deliver impressions across paired screens that match the targeting filter. Brands and agencies use this surface; agencies prefix paths with the sub-account workspace via the X-Workspace-Id header.
GET/campaigns
List campaigns in this workspace, newest first. Filter with status, category, or scheduling window. Supports cursor pagination.
curl https://api.verse.ad/v1/campaigns?status=live&limit=20 \
-H "Authorization: Bearer sk_live_..."{
"data": [
{
"id": "cmp_8x29f1",
"name": "Iced Macchiato, Spring",
"status": "live",
"budget": { "amount": 1500.00, "currency": "GBP" },
"spent": { "amount": 421.18, "currency": "GBP" },
"starts": "2026-04-29T00:00:00Z",
"ends": "2026-05-27T23:59:59Z"
}
],
"has_more": false,
"next_cursor": null
}POST/campaigns
Create a new campaign. Returns the full campaign with status pending_review until it clears brand-safety checks (typically under five minutes).
curl -X POST https://api.verse.ad/v1/campaigns \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 8c2b7e1c-..." \
-H "Content-Type: application/json" \
-d '{
"name": "Iced Macchiato, Spring",
"category": "f-and-b",
"budget": { "amount": 1500.00, "currency": "GBP" },
"targets": {
"cities": ["london", "manchester"],
"venue_types": ["cafe", "restaurant"],
"daypart": { "start": "06:00", "end": "11:00" }
},
"creatives": ["cre_8x29f1"],
"schedule": { "starts": "2026-04-29", "ends": "2026-05-27" }
}'{
"id": "cmp_8x29f1",
"status": "pending_review",
"rate": { "amount": 0.0061, "currency": "GBP", "unit": "impression" },
"forecast": {
"impressions_low": 205000,
"impressions_high": 248000,
"scan_rate_est": 0.0034
}
}GET/campaigns/{id}
Fetch a single campaign with delivery counters, current pacing, and creative bindings.
PATCH/campaigns/{id}
Update budget, schedule, targets, creative weights, or status. Live edits propagate to delivery within minutes; pause and resume are instant.
curl -X PATCH https://api.verse.ad/v1/campaigns/cmp_8x29f1 \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: ..." \
-d '{ "status": "paused" }'DELETE/campaigns/{id}
Soft-delete a campaign. Live campaigns are paused first. Refunds any unspent reserved budget to the wallet.
Creatives
Creatives are 6 to 15 second video assets, or static stills with optional audio. The POST endpoint returns a signed URL; upload the binary directly to it. We transcode for the eight player platforms automatically.
GET/creatives
List creatives in this workspace. Filter by status (draft, ready, rejected) and orientation.
POST/creatives
Reserve a creative slot and get a signed upload URL. The asset becomes status:ready once transcoding completes (typically under two minutes).
curl -X POST https://api.verse.ad/v1/creatives \
-H "Authorization: Bearer sk_live_..." \
-d '{
"name": "Iced Macchiato 9x16 v2",
"orientation": "portrait",
"duration_ms": 8000,
"filename": "iced-macchiato-9x16-v2.mp4"
}'{
"id": "cre_8x29f1",
"status": "awaiting_upload",
"upload_url": "https://up.verse.ad/...",
"expires_at": "2026-05-05T18:42:00Z"
}DELETE/creatives/{id}
Detach a creative. Live campaigns referencing it pause unless other creatives remain in rotation.
Forecasts
Forecasts return servable impressions, scan-rate estimates, and a pacing curve for a brief, a budget, and a target plan. Cheap to call, idempotent, capped at 50 forecasts per minute per token. Use them to validate inputs before POST /campaigns.
POST/forecasts
Run a forecast. Returns a low and high impression band (90% confidence), an estimated scan rate, and a daily pacing curve.
curl -X POST https://api.verse.ad/v1/forecasts \
-H "Authorization: Bearer sk_live_..." \
-d '{
"budget": { "amount": 1500, "currency": "GBP" },
"venue_types": ["cafe", "gym"],
"cities": ["london"],
"schedule": { "starts": "2026-05-12", "ends": "2026-05-26" },
"daypart": { "start": "07:00", "end": "10:00" }
}'{
"rate": { "amount": 0.0061, "currency": "GBP" },
"impressions_low": 198400,
"impressions_high": 241800,
"scan_rate_est": 0.0034,
"screens_eligible": 642,
"fill_probability": 0.91,
"pacing": [
{ "date": "2026-05-12", "impressions_est": 13700 },
{ "date": "2026-05-13", "impressions_est": 14100 }
]
}Reporting
Reporting endpoints aggregate delivery, scan, and brand-lift data. Cursor-paginated; results are eventually consistent within 90 seconds of delivery. For agency cross-roster reports use /agency/reports below.
GET/reporting/campaigns/{id}
Aggregate delivery for a single campaign. Filter by daypart, venue, or city. Group by day or hour.
curl "https://api.verse.ad/v1/reporting/campaigns/cmp_8x29f1?from=2026-04-29&to=2026-05-05&group=day" \
-H "Authorization: Bearer sk_live_..."{
"campaign_id": "cmp_8x29f1",
"totals": {
"impressions": 75200,
"scans": 258,
"scan_rate": 0.00343,
"spend": { "amount": 458.72, "currency": "GBP" }
},
"series": [
{ "bucket": "2026-04-29", "impressions": 10200, "scans": 32 },
{ "bucket": "2026-04-30", "impressions": 11400, "scans": 41 }
]
}GET/reporting/scans
Per-scan event log. Each scan ties back to a screen, a venue, a daypart, and the creative variant rendered. Useful for downstream attribution warehouses.
GET/reporting/brand-lift
Brand-lift survey results for campaigns where opt-in was enabled. Returns awareness, recall, and intent deltas with sample sizes.
Screens, pairing, playlists, payouts
Screens
A screen is a paired display running a VERSE player on Android TV, Samsung Tizen, LG webOS, Amazon Fire TV, Google TV, Windows, Linux, or any modern browser. Each screen belongs to a venue and runs one playlist at a time.
GET/screens
List screens. Filter by venue, status (online, offline, paired_idle), or platform.
{
"data": [
{
"id": "scr_8x29f1",
"venue_id": "ven_3a14b2",
"label": "Front counter",
"platform": "tizen",
"status": "online",
"last_seen_at": "2026-05-05T17:51:09Z",
"sense_paired": true
}
]
}POST/screens
Register a screen by claiming a freshly minted pairing code. Equivalent to typing the 6-digit code on the device.
curl -X POST https://api.verse.ad/v1/screens \
-H "Authorization: Bearer sk_live_..." \
-d '{
"venue_id": "ven_3a14b2",
"label": "Front counter",
"code": "482917"
}'PATCH/screens/{id}
Update screen label, venue assignment, ad-fill ratio, or playlist binding.
DELETE/screens/{id}
Unpair a screen. The device returns to its blank pairing prompt within seconds.
Pairing
Pairing mints a 6-digit code valid for 60 seconds. The device polls for claim; the dashboard or this API confirms it. Eight platforms supported, browser-based, no app store required.
POST/pairing
Mint a pairing code. Returns the code and an expiry timestamp.
curl -X POST https://api.verse.ad/v1/pairing \
-H "Authorization: Bearer sk_live_..." \
-d '{ "venue_id": "ven_3a14b2" }'{
"code": "482917",
"expires_at": "2026-05-05T17:53:00Z",
"claim_url": "https://hub.verse.ad/pair/482917"
}GET/pairing/{code}
Poll the pairing status. Returns claimed:true and the new screen id once a device confirms.
Playlists
A playlist is an ordered list of items: AI-generated menus, brand creatives, sponsorship cards, and venue-owned content. The runtime weaves ad-fill in based on the screen’s ad-ratio setting (0% to 100%).
GET/playlists
List playlists in this venue or workspace.
POST/playlists
Create a playlist. Items can reference creative ids, AI menu templates, or static images.
curl -X POST https://api.verse.ad/v1/playlists \
-H "Authorization: Bearer sk_live_..." \
-d '{
"name": "Lunch service",
"items": [
{ "type": "ai_menu", "template": "lunch-grid", "duration_ms": 12000 },
{ "type": "image", "asset_id": "ast_2f1c8b", "duration_ms": 5000 },
{ "type": "ad_slot", "weight": 1.0 }
]
}'PATCH/playlists/{id}
Reorder items, change durations, swap assets, or update the ad-slot weighting.
DELETE/playlists/{id}
Archive a playlist. Active screens fall back to the venue default.
Payouts
Venue earnings are calculated daily and withdrawable on demand. Up to 50% of ad revenue when the venue runs ad fill at the maximum tier with a paired VERSE Sense module (£20, shipping included). Without Sense, commission is shaped by daily visitors, dwell, and ad ratio. Withdrawals over £200 are free; a small standard sending fee applies below.
GET/payouts
List payout withdrawals for this workspace. Each payout itemises which venues contributed and the gross-to-net breakdown.
{
"data": [
{
"id": "pay_2026_04",
"period": "2026-04",
"status": "paid",
"amount": { "value": 2841.06, "currency": "GBP" },
"paid_at": "2026-05-03T09:14:00Z",
"venues_count": 4
}
]
}GET/payouts/{id}
Fetch a single payout with per-venue line items, impression counts, and the commission tier applied.
Sub-accounts and consolidated reporting
Sub-accounts
Each agency client lives in its own ring-fenced workspace, scoped wallets, scoped roles, scoped audit log. Agency tokens can act on any sub-account by sending the X-Workspace-Id: ws_... header. Agencies earn up to 30% off platform CPM (volume tier up to 22%, slot fill bonus up to 6%, case-study opt-in up to 2%).
GET/agency/clients
List sub-accounts under this agency. Includes wallet balances, MTD spend, and the live discount tier per client.
{
"data": [
{
"id": "ws_velvet_ridge",
"name": "Velvet Ridge",
"wallet": { "balance": { "amount": 4180.00, "currency": "GBP" } },
"mtd_spend": { "amount": 5840.20, "currency": "GBP" },
"discount_tier": 0.20
}
]
}POST/agency/clients
Spin up a new sub-account. Defaults to inherit-from-agency for branding, audit retention, and approval workflow.
curl -X POST https://api.verse.ad/v1/agency/clients \
-H "Authorization: Bearer sk_live_..." \
-d '{
"name": "Pulse Gym",
"billing_model": "pass-through",
"currency": "GBP"
}'PATCH/agency/clients/{id}
Update a sub-account, name, billing model, branding, or seat list.
GET/agency/reports
Cross-roster delivery, spend, and savings. Group by client, region, daypart, or campaign.
curl "https://api.verse.ad/v1/agency/reports?from=2026-04-01&to=2026-04-30&group=client" \
-H "Authorization: Bearer sk_live_..."{
"totals": {
"platform_cost": { "amount": 18405.00, "currency": "GBP" },
"discount": { "amount": 3681.00, "currency": "GBP" },
"net_cost": { "amount": 14724.00, "currency": "GBP" }
},
"by_client": [
{ "client_id": "ws_velvet_ridge", "spend": 4120.10 },
{ "client_id": "ws_peak_performance","spend": 3680.40 },
{ "client_id": "ws_pulse_gym", "spend": 3210.55 }
]
}GET/invoices
List invoices for the agency or a sub-account. PDFs are returned as signed URLs valid for 24 hours.
Audit log
Every state change in a workspace appends to the audit log: who, what, when, from where (IP and user agent), with a before and after diff. Retained for seven years on Scale and Enterprise plans, two years otherwise. Read-only via the API; rotate with /audit/export for warehouse loads.
GET/audit
List audit entries. Filter by actor, entity (campaign, screen, payout), action, or time range.
{
"data": [
{
"id": "aud_8x29f1",
"at": "2026-05-05T17:42:18Z",
"actor": { "type": "user", "id": "usr_3a14b2", "email": "ops@velvetridge.co" },
"action": "campaign.paused",
"entity": { "type": "campaign", "id": "cmp_8x29f1" },
"ip": "82.14.21.7",
"diff": { "status": { "from": "live", "to": "paused" } }
}
]
}POST/audit/export
Kick off an async export of audit entries to S3, GCS, or a signed CSV URL. Returns a job id; poll with /audit/export/{job_id}.
Rate limits
Default is 600 requests per minute per token, with a burst capacity of 200 requests per second. Forecasts and audit exports are 50 per minute per token. Higher limits are raised on request, write to hello@verse.ad with a one-line description of the integration.
| Header | Description |
|---|---|
X-RateLimit-Limit | The ceiling for the bucket on this token (e.g. 600). |
X-RateLimit-Remaining | Calls left in the current minute. |
X-RateLimit-Reset | Seconds until the bucket refills. |
Retry-After | Sent only on 429. Wait this many seconds before retrying. |
Webhooks
Subscribe to events for campaigns, creatives, screens, scans, payouts, and audit entries. We POST a signed JSON payload to your endpoint and retry with exponential backoff for up to 24 hours. Endpoints must respond with a 2xx within 5 seconds.
Subscribing
POST/webhooks
Register a webhook endpoint. Returns a signing secret used to verify every payload.
curl -X POST https://api.verse.ad/v1/webhooks \
-H "Authorization: Bearer sk_live_..." \
-d '{
"url": "https://yourapp.example/verse-events",
"events": ["campaign.approved", "scan.recorded", "payout.paid"]
}'{
"id": "whk_8x29f1",
"url": "https://yourapp.example/verse-events",
"secret": "whsec_4f3c8b2a...",
"events": ["campaign.approved", "scan.recorded", "payout.paid"],
"status": "active"
}Event types
campaign.created,campaign.approved,campaign.paused,campaign.resumed,campaign.finished,campaign.rejectedcreative.uploaded,creative.ready,creative.rejectedscreen.paired,screen.online,screen.offline,screen.unpairedscan.recorded(with screen, venue, and daypart attribution)payout.issued,payout.paid,payout.failedinvoice.issued,invoice.paidaudit.entry.created
Verifying signatures
Every payload carries an X-Verse-Signature header in the form t=<timestamp>,v1=<hex>. Compute HMAC-SHA256(secret, "{timestamp}.{raw_body}") and constant-time compare. Reject anything older than 5 minutes to prevent replay attacks.
// Node.js, untrusted body parser, raw body required
import crypto from "crypto";
function verify(rawBody, header, secret) {
const [tPart, vPart] = header.split(",");
const t = tPart.split("=")[1];
const sig = vPart.split("=")[1];
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && crypto.timingSafeEqual(
Buffer.from(sig, "hex"),
Buffer.from(expected, "hex"),
);
}Retries and idempotency
Failed deliveries (non-2xx, timeout) retry on a backoff: 30s, 1m, 5m, 30m, 2h, 6h, 12h, 24h. Each retry carries the same id, deduplicate by it on your side. Endpoints that flap to 4xx (auth or schema) are auto-disabled after 24 consecutive failures and surface in the dashboard.
SDKs
Official SDKs in TypeScript, Python, and Go are in active drafting. Until they ship, the API is plain HTTP / JSON and trivially usable from any language. The X-Verse-Sdk header on requests helps us prioritise: send a string like my-app/1.4.2 if you want it counted.
Status
Surface coverage is complete. Per-endpoint deep-dives (every field, every error, every edge case) ship behind the Soon sidebar links as each section is finalised. For anything not yet documented, hello@verse.ad reaches an engineer the same day.