API reference
Base URL: your Waveband host (https://waveband.io or your white-label domain). Every operation below is generated from the same registry as the OpenAPI 3.1 document, which carries the JSON Schema of every request body. To find the API call for something you do in the console, see Where can I do this?
Authentication
Send Authorization: Bearer wbk_... on every request. Keys are created in Console → Developers with exactly the scopes they need, optionally pinned to a single workspace. The key is shown once. MCP clients can use OAuth instead (see MCP & AI agents); an OAuth access token (rto_...) works on these endpoints too, with the scopes granted at sign-in.

Conventions
- Money is integer USD cents (
budgetUsdMinor: 500000is $5,000). Ids are UUIDs. Dates areYYYY-MM-DD; timestamps are ISO 8601. - Scopes. Each operation lists the scopes it needs; when it lists several, the key must hold all of them. Some bodies need an extra scope, noted on the operation (for example targeting changes need
targeting:write). - Idempotency. Operations marked supports Idempotency-Key accept an
Idempotency-Keyheader. A retry with the same key and body replays the first response; the same key with a different body returns 409WAVEBAND_IDEMPOTENCY_CONFLICT. So does a retry while the first request is still running; if that request never finishes, the key frees up after 5 minutes. - Lists return
{ items }. Most return the full set; lists that can grow large takelimit(andoffsetwhere shown) query parameters, listed on the operation. - Errors are JSON
{ error: { code, message, details } }with a stablecode: 400 validation, 401 bad key, 403 missing scope, 404, 409 conflict. See the error reference. - Rate limit: 240 requests per minute per key; beyond that, 429
WAVEBAND_RATE_LIMITED. Retry after a minute. - Delivery sync is asynchronous: writes return at once and reach delivery shortly after. Watch
sync.succeeded.v1/sync.failed.v1on webhooks.
Scopes
The full list (append-only; removing a scope would be a breaking change):
workspaces:readworkspaces:writecampaigns:readcampaigns:writeline_items:readline_items:writetargeting:readtargeting:writecreatives:readcreatives:writeevents:readevents:writesegments:readsegments:writeaudiences:readaudiences:writereports:readreports:writemetrics:readdatasets:readdatasets:exportbilling:readbilling:writewebhooks:readwebhooks:writeapi_keys:readapi_keys:writeautomations:readautomations:writebranding:readbranding:writedomains:readdomains:writeai:invokediagnostics:readdiagnostics:runadmin:readadmin:writecatalog:readcatalog:writeapprovals:readapprovals:writemembers:readmembers:writeOperations
- Campaigns, targeting, and creatives (28)
- Audiences (7)
- Reports, events, webhooks, and keys (10)
- Agency growth and Studio (21)
- Billing, automation, and client work (20)
- Team and brand (16)
Campaigns, targeting, and creatives
/api/v1/workspacesList workspaces
Required scope: workspaces:read · 200 Workspaces visible to the key.
/api/v1/workspacesCreate a workspace
Required scope: workspaces:write · JSON body · 201 The created workspace. · supports Idempotency-Key
/api/v1/workspaces/{id}Get a workspace
id(path): uuid
Required scope: workspaces:read · 200 The workspace, including its advertiser domain, IAB category, and disclosure level.
/api/v1/workspaces/{id}Update workspace settings
Advertiser domain and IAB category (set together; delivery needs them before a campaign can run, and they apply to every campaign in the workspace) and/or disclosureLevel, what client roles see of spend: billed_rates (spend at your rate multiplier), percent_delivery (no spend), or full_transparency (actual spend). Client roles cannot change settings.
id(path): uuid
Required scope: workspaces:write · JSON body · 200 The updated workspace.
/api/v1/campaignsList campaigns in a workspace
workspaceId(query, required)status(query): Filter by status.
Required scope: campaigns:read · 200 Campaigns ordered by creation date.
/api/v1/campaignsCreate a campaign
Required scope: campaigns:write · JSON body · 201 The created campaign (sync to delivery is asynchronous). · supports Idempotency-Key
/api/v1/campaigns/{id}Get a campaign
id(path): uuid
Required scope: campaigns:read · 200 The campaign.
/api/v1/campaigns/{id}Update a campaign
Also used for pause/resume via the status field.
id(path): uuid
Required scope: campaigns:write · JSON body · 200 The updated campaign.
/api/v1/line-itemsList line items in a campaign
campaignId(query, required)
Required scope: line_items:read · 200 Line items ordered by creation date.
/api/v1/line-itemsCreate a line item
Targeting other than the default (untargeted) also needs the targeting:write scope.
Required scope: line_items:write · JSON body · 201 The created line item. · supports Idempotency-Key
/api/v1/line-items/{id}Get a line item
id(path): uuid
Required scope: line_items:read · 200 The line item, including its targeting.
/api/v1/line-items/{id}Update a line item
Budget, bid, pacing, targeting, and status (pause/resume/activate) changes. `targeting` replaces the whole object, except that an omitted `adsTxtOnly` or `defaultExchanges` keeps its current value. A body that includes `targeting` also needs the targeting:write scope.
id(path): uuid
Required scope: line_items:write · JSON body · 200 The updated line item.
/api/v1/line-items/{id}/creativesAttach a creative to a line item
id(path): uuid
Required scope: line_items:write · JSON body · 201 The association.
/api/v1/line-items/{id}/creativesList the creatives on a line item
id(path): uuid
Required scope: line_items:read · 200 Attached creatives (removed ones excluded).
/api/v1/line-items/{id}/creativesRemove a creative from a line item
A running line item must keep at least one creative: attach the replacement first, or pause it.
id(path): uuidcreativeId(query, required)
Required scope: line_items:write · 200 No content.
/api/v1/creativesList creatives in a workspace
workspaceId(query, required)
Required scope: creatives:read · 200 Creatives ordered by creation date.
/api/v1/creativesCreate a creative
Assets are URL-based. Video/audio sync asynchronously. HTML5 may use an inline `html` body (Creative Studio / Waveforms produce ordinary html5 creatives this way; there is no separate Waveform type). For Soundframe (audio-led video), omit assets and set render.kind=audio_led_video. Supply audioSource and/or script (with targetDurationSeconds + voicePreset), and imageSource and/or imagePrompt (script alone can drive both); the worker generates gaps, renders MP4 (≤90s), then syncs as a normal video creative.
Required scope: creatives:write · JSON body · 201 The created creative. · supports Idempotency-Key
/api/v1/creatives/{id}Get a creative
id(path): uuid
Required scope: creatives:read · 200 The creative with processing status.
/api/v1/reference/{kind}Look up targeting reference values
Values for targeting fields that take ids: metros (DMA codes), regions, cities (numeric city ids), inventorySources (exchanges), contentGenres. `q` searches labels (cities need 2+ characters); `values=a,b` returns the labels for stored values (up to 100). At most 50 results.
kind(path): Which reference list.q(query): Search text.values(query): Comma-separated values to label.
Required scope: targeting:read · 200 Matching options as { value, label } items.
/api/v1/forecastsForecast available impressions
Available supply matching the targeting over the flight (UTC, from tomorrow, at most 31 days). Not a delivery promise and not unique reach. Forecast capacity is shared and limited per account per minute: when it is used up the result is status unavailable with reason busy; retry in a minute. Identical queries are cached for 30 minutes.
Required scopes (all of): line_items:read, targeting:read · JSON body · 200 status ok with impressions, low, high, days, and unaccounted settings; or status unavailable with a reason.
/api/v1/creative-generations/quotePrice an AI ad generation
Nothing is reserved or charged. Returns the typical and maximum cost in USD cents.
Required scopes (all of): creatives:write, ai:invoke · JSON body · 200 maxUsdMinor, typicalUsdMinor, creatives, and availableUsdMinor.
/api/v1/creative-generationsGenerate ads with AI
Charges the wallet: up to the quoted maximum is set aside now and settled to the actual cost when the ads are delivered. Quote first. The ads appear as processing creatives in the workspace.
Required scopes (all of): creatives:write, ai:invoke · JSON body · 201 generationId, prepaidUsdMinor, and the number of creatives. · supports Idempotency-Key
/api/v1/creative-generationsList recent AI ad generations
workspaceId(query, required)
Required scope: creatives:read · 200 The 20 most recent generations, newest first.
/api/v1/deals/{id}Update or archive a deal
Set status archived to archive. Line items can no longer target an archived deal.
id(path): uuid
Required scope: catalog:write · JSON body · 200 The updated deal.
/api/v1/curated-dealsList curated deals you can activate
Required scope: catalog:read · 200 Platform curated deals, each marked alreadyActivated when in your library.
/api/v1/curated-deals/{id}/activateActivate a curated deal
Copies the curated deal into your deal library so line items can target it. Returns the existing deal when it is already there.
id(path): uuid
Required scope: catalog:write · JSON body · 201 The library dealId.
/api/v1/conversion-eventsList conversion events
workspaceId(query, required)
Required scope: events:read · 200 Conversion event definitions.
/api/v1/conversion-eventsCreate a conversion event
Required scope: events:write · JSON body · 201 The created conversion event with its tracking snippet. · supports Idempotency-Key
Audiences
/api/v1/audiencesList audience segments in a workspace
workspaceId(query, required)status(query): Filter by status (active | archived).
Required scope: audiences:read · 200 Segments with matched counts and provisioning status.
/api/v1/audiencesCreate an audience segment
Pixel-based rules (site_visitors, converters, event_rule), uploaded lists (crm_list, ip_list), or a marketplace activation.
Required scope: audiences:write · JSON body · 201 The created segment (upstream materialization is asynchronous). · supports Idempotency-Key
/api/v1/audiences/{id}Get an audience segment
id(path): uuid
Required scope: audiences:read · 200 The segment.
/api/v1/audiences/{id}Archive an audience segment
id(path): uuid
Required scope: audiences:write · 200 The archived segment.
/api/v1/audiences/{id}/membersUpload members to a list segment
crm_list: emails/phones (hashed at ingest; raw values are never stored) or pre-hashed sha256 lines. ip_list: IP addresses.
id(path): uuid
Required scope: audiences:write · JSON body · 201 The upload batch with accepted counts.
/api/v1/audiences/{id}/attachAttach or detach a segment on a line item
One-click retargeting: merges the segment into the line item's audience targeting.
id(path): uuid
Required scopes (all of): audiences:write, targeting:write · JSON body · 200 The updated line item.
/api/v1/marketplace-segmentsSearch the third-party segment marketplace
q(query): Optional search across name, taxonomy path, provider, and description. Omit to browse.provider(query): Filter by data provider.minCpm(query): Minimum data CPM in USD dollars (inclusive).maxCpm(query): Maximum data CPM in USD dollars (inclusive).minReach(query): Minimum provider-reported reach (inclusive).sort(query): Result ordering (default path).limit(query)offset(query): Pagination offset (default 0).
Required scope: audiences:read · 200 { items, total }: catalog rows with data CPM and reach.
Reports, events, webhooks, and keys
/api/v1/reportsQuery metrics
Aggregated delivery/performance rows, including video/audio starts, quartiles, completes, and completionRate (completes / starts, null when nothing played). Grouped by creative, conversions are null: they are attributed per line item. format=csv streams a CSV export.
workspaceId(query, required)from(query, required): YYYY-MM-DD inclusive.to(query, required): YYYY-MM-DD inclusive.groupBy(query)campaignId(query)format(query)
Required scope: reports:read · 200 Metric rows at the requested grain.
/api/v1/eventsList domain events
The event stream that webhooks deliver; useful for backfill and debugging.
entityType(query)entityId(query)limit(query)
Required scope: webhooks:read · 200 Events, newest first.
/api/v1/webhooksList webhook endpoints
Required scope: webhooks:read · 200 Registered endpoints (secrets omitted).
/api/v1/webhooksCreate a webhook endpoint
The signing secret is returned once. Verify deliveries via X-Waveband-Signature: sha256=HMAC(secret, body).
Required scope: webhooks:write · JSON body · 201 The endpoint including its one-time signing secret. · supports Idempotency-Key
/api/v1/webhooks/{id}Delete a webhook endpoint
id(path): uuid
Required scope: webhooks:write · 200 Deletion confirmation.
/api/v1/webhook-deliveriesList webhook deliveries
endpointId(query)limit(query)
Required scope: webhooks:read · 200 Delivery attempts with status and response codes.
/api/v1/webhook-deliveries/{id}/replayReplay a webhook delivery
id(path): uuid
Required scope: webhooks:write · 200 The re-queued delivery.
/api/v1/api-keysList API keys
Required scope: api_keys:read · 200 Keys with prefixes only (never full secrets).
/api/v1/api-keysCreate an API key
Scopes cannot exceed the calling key's scopes. The key is returned once.
Required scope: api_keys:write · JSON body · 201 The created key including its one-time plaintext.
/api/v1/api-keys/{id}Revoke an API key
id(path): uuid
Required scope: api_keys:write · 200 Revocation confirmation.
Agency growth and Studio
/api/v1/rate-cardsList rate cards
Required scope: catalog:read · 200 The tenant's rate cards (client-facing CPM by channel).
/api/v1/rate-cardsCreate a rate card
The first active card becomes the tenant default; isDefault switches it.
Required scope: catalog:write · JSON body · 201 The created rate card. · supports Idempotency-Key
/api/v1/rate-cards/{id}Update a rate card
Edit name, description, or entries; isDefault true makes it the tenant default; status archived retires it.
id(path): uuid
Required scope: catalog:write · JSON body · 200 The updated rate card.
/api/v1/packagesList productized packages
status(query)
Required scope: catalog:read · 200 Sellable packages with targeting, floors, and pricing.
/api/v1/packagesCreate a package
Required scope: catalog:write · JSON body · 201 The created package. · supports Idempotency-Key
/api/v1/packages/{id}Get a package
id(path): uuid
Required scope: catalog:read · 200 The package with targeting, floors, and pricing.
/api/v1/packages/{id}Update a package
Only the fields sent change. The channel is fixed; status archived retires it.
id(path): uuid
Required scope: catalog:write · JSON body · 200 The updated package.
/api/v1/playbooks/applyApply a playbook as a package
Send either { vertical } for a vertical playbook or { supplyPlaybookId } for a supply playbook. Creates a tenant-owned package you can then edit.
Required scope: catalog:write · JSON body · 201 The created package. · supports Idempotency-Key
/api/v1/packages/{id}/instantiateInstantiate a package into a draft campaign
Creates a draft campaign + line item priced from the package or default rate card.
id(path): uuid
Required scopes (all of): catalog:read, campaigns:write · JSON body · 201 The created campaignId, lineItemId, and applied CPM.
/api/v1/dealsList curated deals
workspaceId(query)status(query)
Required scope: catalog:read · 200 The tenant's curated deal library.
/api/v1/dealsAdd a deal to the library
Registers an SSP-issued deal ID; provisioning to delivery is asynchronous.
Required scope: catalog:write · JSON body · 201 The created deal. · supports Idempotency-Key
/api/v1/proposalsList proposals in a workspace
workspaceId(query, required)
Required scope: campaigns:read · 200 Proposals with status and public share tokens.
/api/v1/proposals/{id}Get a proposal
id(path): uuid
Required scope: campaigns:read · 200 The proposal with items, status, signature, and share token.
/api/v1/proposalsCreate a proposal
Line items priced in USD minor units; share at /p/{token} once sent.
Required scope: campaigns:write · JSON body · 201 The created proposal (status draft). · supports Idempotency-Key
/api/v1/proposals/{id}/sendMark a proposal sent
Activates the public microsite for viewing and click-to-sign.
id(path): uuid
Required scope: campaigns:write · 200 The proposal with status sent.
/api/v1/media-plans/previewPrice a media plan
Send tiers of channel splits (percent of each tier budget). Budgets, CPMs from the default rate card (baseline when unpriced), and impression forecasts are computed. Nothing is saved.
Required scope: campaigns:read · JSON body · 200 { tiers } with priced lines.
/api/v1/media-plansList media plans in a workspace
workspaceId(query, required)
Required scope: campaigns:read · 200 Saved media plans, newest first.
/api/v1/media-plansSave a media plan
Same splits as the preview plus workspace, name, and brief. Pricing is computed server-side, never taken from the request.
Required scope: campaigns:write · JSON body · 201 The saved media plan with priced tiers. · supports Idempotency-Key
/api/v1/media-plans/{id}Get a media plan
id(path): uuid
Required scope: campaigns:read · 200 The media plan with priced tiers.
/api/v1/bookBook overview
Every client workspace with health score and band, 14-day spend against the prior 14 days, active campaigns, and pending change requests; plus cross-tenant channel benchmarks (shown only when enough tenants contribute).
Required scope: reports:read · 200 { clients, benchmarks }
/api/v1/studio/display-setsGenerate a Studio display set
AI writes the headline, subline, and call to action from the brief; html5 creatives are rendered for each size (or the chosen Waveform). AI use draws on the tenant's AI funding.
Required scopes (all of): creatives:write, ai:invoke · JSON body · 201 { creativeIds, sizes } · supports Idempotency-Key
Billing, automation, and client work
/api/v1/billingGet billing position
Billing mode, wallet balance and the amount available to fund delivery (prepaid wallet), unbilled balance, and plan limits with current usage.
Required scope: billing:read · 200 The tenant's billing overview.
/api/v1/billing/ledgerList ledger entries
Newest first. Charges are positive, payments and credits negative.
limit(query): Default 100.offset(query): Rows to skip.
Required scope: billing:read · 200 Ledger entries.
/api/v1/billing/top-up-linksCreate a wallet top-up link
Returns a hosted checkout URL that a person opens to pay. Creating the link charges nothing; the wallet is credited once payment completes. New accounts have a top-up limit in their first 30 days.
Required scope: billing:write · JSON body · 201 The checkout url and amount. · supports Idempotency-Key
/api/v1/automationsList automations in a workspace
workspaceId(query, required)
Required scope: automations:read · 200 Autopilot automations with goal, cadence, policy, and status.
/api/v1/automationsCreate an automation
Required scope: automations:write · JSON body · 201 The created automation. · supports Idempotency-Key
/api/v1/automations/{id}Update an automation
Also pauses (status "paused", the kill switch) or resumes it.
id(path): uuid
Required scope: automations:write · JSON body · 200 The updated automation.
/api/v1/decisionsList decisions
The explainable log of automation and agent decisions; proposed ones await approval.
workspaceId(query, required)status(query)limit(query): Default 100.
Required scope: automations:read · 200 Decisions, newest first.
/api/v1/decisions/{id}/approveApprove a decision
Applies a proposed decision. Tokens bound to an agent policy get 403. Audience decisions also need audiences:write.
id(path): uuid
Required scope: automations:write · 200 The updated decision.
/api/v1/decisions/{id}/rejectReject a decision
Rejects a proposed decision; nothing is applied.
id(path): uuid
Required scope: automations:write · 200 The updated decision.
/api/v1/decisions/{id}/undoUndo a decision
Restores the previous values of an applied decision. Audience decisions also need audiences:write.
id(path): uuid
Required scope: automations:write · 200 The updated decision.
/api/v1/reports/narrativeNarrate a report
A plain-English summary of what happened, why, and what to do next. Runs an AI model; usage on platform models is billed.
Required scopes (all of): reports:read, ai:invoke · JSON body · 200 The narrative and the number of report rows it covers.
/api/v1/case-studiesList case studies in a workspace
workspaceId(query, required)
Required scope: catalog:read · 200 Case studies, newest first.
/api/v1/case-studiesGenerate a case study
Computes the campaign's real delivery metrics and has an AI model write the story around them; the numbers are never model output. Usage on platform models is billed.
Required scopes (all of): campaigns:read, reports:read, catalog:write, ai:invoke · JSON body · 201 The saved case study. · supports Idempotency-Key
/api/v1/case-studies/{id}Get a case study
id(path): uuid
Required scope: catalog:read · 200 The case study with sections and metrics.
/api/v1/change-requestsList change requests
workspaceId(query)status(query)
Required scope: approvals:read · 200 Client change requests, newest first.
/api/v1/change-requestsSubmit a change request
Proposes a campaign change (pause, activate, budget) for the agency to review.
Required scopes (all of): approvals:write, campaigns:read · JSON body · 201 The pending change request. · supports Idempotency-Key
/api/v1/change-requests/{id}/reviewReview a change request
Approving applies the change with the reviewer's permissions and guardrails.
id(path): uuid
Required scope: campaigns:write · JSON body · 200 The reviewed change request.
/api/v1/marketplace-providersList audience marketplace providers
Required scope: audiences:read · 200 Providers with segment counts, and catalog freshness.
Team and brand
/api/v1/membersList people
Everyone with access to the tenant: staff and client logins. Workspace-bound people are listed only when the key can see their workspace.
Required scope: members:read · 200 People with membershipId, email, role, and workspaceId.
/api/v1/membersInvite a person
Creates or reuses the user by email and grants a role. Client roles need a workspaceId. The person signs in with an emailed code. Rejected when they already have access (change their role instead).
Required scope: members:write · JSON body · 201 The new membership (membershipId, userId, email, role, workspaceId). · supports Idempotency-Key
/api/v1/members/{id}Change a person's role
Replaces the role and workspace binding of a membership. Send workspaceId null for tenant-wide access. You cannot change your own role.
id(path): uuid
Required scope: members:write · JSON body · 200 The updated membership.
/api/v1/members/{id}Remove a person
Revokes the membership. You cannot remove your own access.
id(path): uuid
Required scope: members:write · 200 { deleted: true }
/api/v1/client-invitesInvite a client login
Grants a client role (viewer, approver, collaborator) bound to one workspace. Client logins see only that workspace.
Required scope: members:write · JSON body · 201 The new client membership. · supports Idempotency-Key
/api/v1/teamsList teams
Required scope: members:read · 200 Teams with their staff role, members, and workspace assignments.
/api/v1/teamsCreate a team
A team gives a group of staff one role across a set of client workspaces.
Required scope: members:write · JSON body · 201 The created team. · supports Idempotency-Key
/api/v1/teams/{id}Change team members and workspaces
Adds and removes members (people already in the tenant, staff only) and, when workspaceIds is sent, replaces the team's workspaces. All changes apply together or not at all.
id(path): uuid
Required scope: members:write · JSON body · 200 The team with its members and workspaces.
/api/v1/teams/{id}Delete a team
id(path): uuid
Required scope: members:write · 200 { deleted: true }
/api/v1/brandingGet branding
Required scope: branding:read · 200 Product name, logos, colors, skin, support and email settings.
/api/v1/brandingUpdate branding
Changes only the fields sent. Image fields take an https URL; uploading logo files is done in the console.
Required scope: branding:write · JSON body · 200 The full branding record after the change.
/api/v1/domainsList custom domains
Required scope: domains:read · 200 Domains with status and the DNS records to add.
/api/v1/domainsAdd a custom domain
Registers a hostname for the white-label console. Add every record in verificationRecords at your DNS provider; status moves from pending_dns to pending_ssl to active.
Required scope: domains:write · JSON body · 201 The domain with status and verificationRecords (type, name, value, purpose). · supports Idempotency-Key
/api/v1/domains/{id}Get a custom domain
Poll this for verification status; it is re-checked in the background.
id(path): uuid
Required scope: domains:read · 200 The domain with status, verificationRecords, lastError, and lastCheckedAt.
/api/v1/domains/{id}/refreshRe-check a custom domain
Checks verification now instead of waiting for the background check.
id(path): uuid
Required scope: domains:write · 200 The domain with its current status and verificationRecords.
/api/v1/domains/{id}Remove a custom domain
id(path): uuid
Required scope: domains:write · 200 Deletion confirmation.