Skip to content

WARNING

Treat API keys like passwords. Do not commit them to git or paste them in public channels.

Platform overview ​

Pulseflow is the live bidding and pacing service: it reads your campaign configuration, applies spend and pacing rules, works with the DSP bidder on OpenRTB traffic from SSPs (supply-side platforms / exchanges), and writes billing and play records into the archive (used for the play-data endpoints in this guide).

The Vue API (the API_HOST in the examples below) is the control plane for inventory and campaigns: sign catalog, partner keys, create/update/activate campaigns, and links to clients. It stores campaign documents that Pulseflow and related services consume—it is not a substitute for Pulseflow in the auction path.


Test requests with your API key ​

There is no built-in “Try it” code panel inside these docs right now. Safe ways to exercise the API:

  1. Terminal + curl — Copy the examples in this page; export API_HOST and DSP_API_KEY first (see below). Best for quick checks.
  2. Postman, Insomnia, or VS Code REST Client — Create an environment with API_HOST and DSP_API_KEY. For partner routes, set header Authorization to Bearer or X-Vue-Dsp-Api-Key: .
  3. Your own app or script — Same headers from any HTTP client in the language you use.

Avoid pasting production keys into random public “API tester” websites or shared online consoles.

If you need an in-browser explorer later, options include publishing an OpenAPI description of these routes and hosting Swagger UI or Scalar against a staging environment only.


Set these once (replace with your real host and key):

bash
export API_HOST="https://api.vuebillboards.com"
export DSP_API_KEY="vbdsp.<keyId>.<secret>"

Your Vue Billboards contact issues the DSP_API_KEY. It is tied to your DSP account; if that account changes, the key may stop working.

Send the key on every partner request using either:

  • Authorization: Bearer $DSP_API_KEY, or
  • X-Vue-Dsp-Api-Key: $DSP_API_KEY

Suggested order: search inventory first (calls below use API_HOST only — no partner key). From each screen you want, copy adunit_id or programmatic_platform_key; those strings are what you send as platformKeys when you create a campaign. After that, use DSP_API_KEY for create, get, edit, activate, and PulseFlow Bid Archive billing reads (see Play and spend data (PulseFlow Bid Archive)).

Look up inventory (screens) ​

Sign record shape (reference) ​

Real rows vary by publisher and import source; many fields can be null or missing. Typical keys you need for campaigns are adunit_id, programmatic_platform_key, and sometimes openRTBUUID.

json
{
  "id": "internal-row-id",
  "asset_name": "UNIT-5021-NYC",
  "adunit_id": "hs-screen-adunit-abc123",
  "programmatic_platform_key": "hivestack-platform-key-xyz",
  "openRTBUUID": "550e8400-e29b-41d4-a716-446655440000",
  "openrtb_uuid": null,
  "screen_id": "SCR-8899",
  "screen_name": "7th Ave @ 42nd St",
  "name": "Times Square Digital",
  "adunit_name": "TSQ-LED-01",
  "address": "1 Times Square",
  "city": "New York",
  "state": "NY",
  "region": "New York",
  "latitude": 40.758,
  "longitude": -73.9855,
  "format": "2880x960",
  "width": 2880,
  "height": 960,
  "preferred_ad_format": "2880x960",
  "media_type": "Digital Billboard",
  "media_type_id": "101",
  "seller_id": "hs-na-1234",
  "publisher": "Example SSP Name",
  "media_owner": "Example Media Owner",
  "floorCpm": 18.5,
  "status": "active",
  "asset_image_url": "https://cdn.example.com/preview.jpg",
  "markets": "New York NY",
  "dma": "501"
}

GET /api/signs

Returns every screen whose latitude / longitude falls within a circle around your point.

QueryRequiredDescription
latYesCenter latitude (decimal degrees, WGS84)
lngYesCenter longitude (decimal degrees, WGS84)
radiusYesRadius in kilometers (not miles)
seller_idNoKeep only signs for this seller / publisher id
includeperionNo1 to include inventory that is otherwise gated for some publishers
includelinknycNo1 to include extra LinkNYC-related units in NYC
includeoutfrontrestrictedmediatypesNo1 for “advanced” inventory (additional Outfront / restricted media types)

Miles → km: multiply miles by 1.609 (e.g. 5 miles ≈ 8.05 km).

curl — 15 km around Midtown Manhattan

bash
curl -sS "$API_HOST/api/signs?lat=40.7580&lng=-73.9855&radius=15"

Success (200)

json
{
  "success": true,
  "data": {
    "signs": [
      {
        "id": "internal-row-id",
        "asset_name": "UNIT-5021-NYC",
        "adunit_id": "hs-screen-adunit-abc123",
        "programmatic_platform_key": "hivestack-platform-key-xyz",
        "openRTBUUID": "550e8400-e29b-41d4-a716-446655440000",
        "latitude": 40.758,
        "longitude": -73.9855,
        "address": "1 Times Square",
        "city": "New York",
        "seller_id": "hs-na-1234",
        "publisher": "Example SSP Name",
        "format": "2880x960",
        "floorCpm": 18.5,
        "asset_image_url": "https://cdn.example.com/preview.jpg"
      }
    ],
    "total": 1,
    "radius": 15,
    "center": { "lat": 40.758, "lng": -73.9855 }
  }
}

Errors

HTTPExample body
400{ "error": "Missing required parameters: lat, lng, radius" }
400{ "error": "Invalid parameters: lat, lng, and radius must be valid numbers" }

One screen by id / name / uuid ​

GET /api/signs/match

Use when you already have a single id string (asset name, ad unit id, platform key, or OpenRTB UUID).

curl — exact match on a known unit id

bash
curl -sS "$API_HOST/api/signs/match?asset_name=hs-screen-adunit-abc123&fuzzy=false"

Success (200) — data is one sign object (same field idea as the reference above).

json
{
  "success": true,
  "data": {
    "id": "internal-row-id",
    "asset_name": "UNIT-5021-NYC",
    "adunit_id": "hs-screen-adunit-abc123",
    "programmatic_platform_key": "hivestack-platform-key-xyz",
    "openRTBUUID": "550e8400-e29b-41d4-a716-446655440000",
    "latitude": 40.758,
    "longitude": -73.9855,
    "address": "1 Times Square",
    "city": "New York",
    "seller_id": "hs-na-1234",
    "publisher": "Example SSP Name",
    "format": "2880x960",
    "floorCpm": 18.5,
    "asset_image_url": "https://cdn.example.com/preview.jpg"
  }
}

Not found (404)

json
{
  "error": "No sign found for asset_name",
  "asset_name": "hs-screen-adunit-abc123",
  "availableFields": [
    "asset_name",
    "adunit_name",
    "screen_id",
    "screen_name",
    "id",
    "adunit_id",
    "programmatic_platform_key",
    "openRTBUUID",
    "openrtb_uuid",
    "openRtbUuid"
  ],
  "totalSigns": 12345
}

Add fuzzy=true for partial / substring-style matching.


Many screens at once ​

GET /api/signs/match/bulk

bash
curl -sS "$API_HOST/api/signs/match/bulk?asset_names=hs-screen-adunit-abc,key-two,key-three"

Success (200) — each data is either a full sign object or null.

json
{
  "success": true,
  "data": [
    {
      "asset_name": "hs-screen-adunit-abc",
      "found": true,
      "data": {
        "adunit_id": "hs-screen-adunit-abc",
        "programmatic_platform_key": "hivestack-platform-key-xyz",
        "latitude": 40.758,
        "longitude": -73.9855
      }
    },
    {
      "asset_name": "key-two",
      "found": false,
      "data": null
    }
  ],
  "summary": { "total": 2, "found": 1, "notFound": 1 }
}

By platform keys (batch) ​

POST /api/signs/by-platform-keys

bash
curl -sS -X POST "$API_HOST/api/signs/by-platform-keys" \
  -H "Content-Type: application/json" \
  -d '{"platformKeys":["KEY1","KEY2"]}'

Success (200)

json
{
  "success": true,
  "data": [
    {
      "adunit_id": "KEY1",
      "programmatic_platform_key": "KEY1",
      "latitude": 25.7617,
      "longitude": -80.1918,
      "asset_name": "MIA-001"
    }
  ]
}

By seller id ​

GET /api/signs/by-seller-id

bash
curl -sS "$API_HOST/api/signs/by-seller-id?seller_id=hs-na-1234"

Success (200)

json
{
  "success": true,
  "seller_id": "hs-na-1234",
  "total": 2,
  "data": [ { "seller_id": "hs-na-1234", "adunit_id": "..." } ]
}

Create a campaign ​

Use your DSP_API_KEY on this request.

POST /api/partner/v1/campaigns

curl

bash
curl -sS -X POST "$API_HOST/api/partner/v1/campaigns" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Spring promo",
    "description": "Optional",
    "clientSeatId": "my-brand-seat",
    "startDate": "2026-04-01",
    "endDate": "2026-04-30",
    "totalBudget": 5000,
    "maxCPM": 12.5,
    "weeklyTimeSegments": [
      { "day": 1, "startTime": "09:00", "endTime": "17:00" },
      { "day": 2, "startTime": "09:00", "endTime": "17:00" }
    ],
    "platformKeys": ["SCREEN_OR_ADUNIT_KEY_1", "SCREEN_OR_ADUNIT_KEY_2"]
  }'

Replace SCREEN_OR_ADUNIT_KEY_* with values from the inventory responses (adunit_id or programmatic_platform_key).

Required JSON fields

FieldTypeNotes
namestringCampaign name
clientSeatIdstringYour client / seat id (must already exist on your account)
startDate, endDatestringISO dates
totalBudgetnumberMust be > 0
maxCPMnumberMust be > 0
weeklyTimeSegmentsarrayEach item: day (1=Mon … 7=Sun), startTime, endTime (HH:MM)
platformKeys or signsarrayAt least one screen — see below

Screens — two styles

  1. Simple list of ids (matched the same way as the map):

    "platformKeys": ["key1", "key2"]

  2. Per-screen daily plays (default plays per day is 300 if omitted):

    "signs": [ { "platformKey": "key1", "dailyPlays": 400 }, { "platformKey": "key2" } ]

Optional JSON fields

description, defaultDailyPlays, perSignBudgetEnabled, ungroupedSignBudgets, dealIds, deals, dealSelectionMode, signScheduleOverrides, linkToClient (default true links the campaign to the client record).

Success response (201)

json
{
  "success": true,
  "campaignId": "generated_campaign_id",
  "dspInstanceId": "yourInstanceId",
  "message": "Campaign created. Pre-submit creatives via the partner API or the web app, then call activation when ready."
}

Typical errors

HTTPBody (example)
400{ "success": false, "error": "Sign not found for platform key: ..." }
401{ "success": false, "error": "Missing partner API key ..." }
403{ "success": false, "error": "API key disabled" }

After creation, pre-submit creatives (below) or attach existing creative document ids, then call activation when ready.


Pre-submit a creative (Hivestack) ​

Registers a creative with Hivestack through perion-api-bridge (same path as the dashboard) and creates a Firestore creatives document you can reference from PATCH .../campaigns/:id (creativeDocIds / assignedCreatives).

IAB categories required

iab_categories is required on POST /api/partner/v1/creatives (non-empty array). Use valid codes from the taxonomy, e.g. the Kayzen IAB category list.

POST /api/partner/v1/creatives

JSON fieldRequiredDescription
nameYesCreative name (max 100 chars sent upstream)
advertiserDomainYesAdvertiser domain string (Hivestack)
publisherIdYesSeller / publisher id for approval (e.g. HS-NA-123; seller_id accepted as alias)
mediaUrlYesPublic HTTPS URL to the asset (image, video, zip, or html) — must be fetchable by Hivestack
width, heightYesPixel dimensions
campaignIdNoIf set, campaign must belong to this partner key; used to build the default VUE-... dsp creative id
formatKeyNoSuffix in generated dsp id (e.g. sign format key)
creativeIdNoProvide to reuse a fixed dsp id; otherwise generated like the web app
mediaTypeNoimage, video, or html (default inferred from mediaUrl extension)
format / mimeFormatNoMIME type for media_files[] (default inferred from extension)
iab_categoriesYesRequired. Non-empty array of IAB Content Category codes (e.g. IAB3, IAB19-1). Each entry must look like IAB + digits, with optional - segments (e.g. IAB2-10). The API returns 400 if this field is missing or empty.
dealIdNoOptional deal id passed through to the bridge

Use valid codes from the taxonomy (same family as Hivestack / industry lists), for example the Kayzen IAB category reference.

bash
curl -sS -X POST "$API_HOST/api/partner/v1/creatives" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Partner line 1",
    "advertiserDomain": "example.com",
    "publisherId": "HS-NA-123",
    "mediaUrl": "https://cdn.example.com/creatives/line1-1920x1080.jpg",
    "width": 1920,
    "height": 1080,
    "campaignId": "OPTIONAL_CAMPAIGN_ID",
    "iab_categories": ["IAB3"]
  }'

Success (201)

json
{
  "success": true,
  "creativeDocId": "firestore_creatives_doc_id",
  "dspCreativeId": "VUE-...",
  "publisherId": "HS-NA-123",
  "bridge": { },
  "message": "Creative submitted to Hivestack via perion-api-bridge. Poll GET ... for approval status."
}

Poll approval state (returns the bridge / Hivestack payload — same endpoint the web app polls):

GET /api/partner/v1/creatives/:dspCreativeId?publisherId=HS-NA-123

bash
curl -sS "$API_HOST/api/partner/v1/creatives/VUE-YOUR_DSP_ID" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -G --data-urlencode "publisherId=HS-NA-123"

Interpret approval_state / publisher_approval_state like the dashboard: 1 pending, 2 approved, 3 rejected (see NewCampaignDetailView polling logic).


Retrieve a specific campaign ​

Returns the stored campaign document (budget, signs, deals, creative links, etc.) for one campaign id.

GET /api/partner/v1/campaigns/:campaignId

bash
curl -sS "$API_HOST/api/partner/v1/campaigns/CAMPAIGN_ID" \
  -H "Authorization: Bearer $DSP_API_KEY"

Success (200)

json
{
  "success": true,
  "campaignId": "CAMPAIGN_ID",
  "data": { "...": "stored campaign fields" }
}

Errors: 401 bad key, 403 not your campaign, 404 unknown id.


Edit a campaign ​

PATCH /api/partner/v1/campaigns/:campaignId

Send only fields you want to change.

curl (rename + budget)

bash
curl -sS -X PATCH "$API_HOST/api/partner/v1/campaigns/CAMPAIGN_ID" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated name",
    "totalBudget": 6000,
    "maxCPM": 15
  }'

Other supported fields (all optional in the patch body)

FieldPurpose
descriptionText
startDate, endDateISO strings
weeklyTimeSegmentsReplaces schedule; also refreshes legacy activeDays / activeHours on the server
signsFull replacement array of sign objects
clientSeatIdSwitch client (must exist for your user)
dealIds, deals, dealId, dealName, publisherIdDeal fields

Attach creatives by id (use POST /api/partner/v1/creatives above to pre-submit, or create creatives in the web app):

Option A — map each sign targeting key to a list of creative document ids:

bash
curl -sS -X PATCH "$API_HOST/api/partner/v1/campaigns/CAMPAIGN_ID" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assignedCreatives": {
      "SIGN_OR_ADUNIT_KEY": ["creativeDocId1"],
      "OTHER_KEY": ["creativeDocId1"]
    }
  }'

Option B — same creatives on every sign the campaign already has:

bash
curl -sS -X PATCH "$API_HOST/api/partner/v1/campaigns/CAMPAIGN_ID" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "applyCreativeIdsToAllSigns": true,
    "creativeDocIds": ["creativeDocId1", "creativeDocId2"]
  }'

Success (200)

json
{
  "success": true,
  "campaignId": "CAMPAIGN_ID",
  "updatedFields": ["name", "totalBudget", "maxCPM", "baseCpmCents", "updatedAt"]
}

Turn a campaign on or off (DSP) ​

POST /api/partner/v1/campaigns/:campaignId/activation

bash
curl -sS -X POST "$API_HOST/api/partner/v1/campaigns/CAMPAIGN_ID/activation" \
  -H "Authorization: Bearer $DSP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"activated": true}'

Deactivate:

bash
-d '{"activated": false}'

Success (200) — example shape:

json
{
  "success": true,
  "message": "Campaign ... has been activated",
  "dspResponse": null,
  "dspError": null,
  "locallyStored": false,
  "pacingInitialized": true,
  "pacingData": { },
  "biddingConfigured": false,
  "biddingConfig": null
}

Activation usually requires screens (adunitIds) and creatives to be valid; if activation fails, the JSON may include dspError or a 400 error message.


Play and spend data (PulseFlow Bid Archive) ​

PulseFlow Bid Archive is the product for billing and play history: the dashboard is at https://archive.vuebillboards.com. The JSON aggregate APIs below (summary, daily, hourly, signs, records) are the same data the dashboard loads; they are served from Pulseflow at PULSEFLOW_HOST, not from API_HOST — see also the Bid Archive HTTP page for routes on archive.vuebillboards.com (for example GET /campaign-info/:campaignId).

Same DSP_API_KEY as the partner API

Use the exact same DSP_API_KEY and headers as the rest of this guide: Authorization: Bearer $DSP_API_KEY or X-Vue-Dsp-Api-Key: $DSP_API_KEY on every request to $PULSEFLOW_HOST/api/archive/billing/....

(Internal only: a signed-in platform-admin browser session on .vuebillboards.com also works without repeating the header.)

bash
export PULSEFLOW_HOST="https://pulseflow.vuebillboards.com"
export DSP_API_KEY="vbdsp.<keyId>.<secret>"

Query params are optional unless noted. Dates are typically YYYY-MM-DD. Include one of the auth headers on every curl example below.

Totals for a campaign ​

bash
curl -sS \
  -H "Authorization: Bearer $DSP_API_KEY" \
  "$PULSEFLOW_HOST/api/archive/billing/summary?campaignId=CAMPAIGN_ID&startDate=2026-04-01&endDate=2026-04-30"

Example success

json
{
  "enabled": true,
  "totalRecords": 1200,
  "totalBilledCents": 450000,
  "totalBilledDollars": 4500,
  "totalImpressions": 900000,
  "avgCpmCents": 500,
  "avgCpmDollars": 5,
  "uniqueCampaigns": 1,
  "uniqueSigns": 12,
  "uniqueDsps": 1
}

If archive Postgres is off, you may see "enabled": false.

By day ​

bash
curl -sS \
  -H "Authorization: Bearer $DSP_API_KEY" \
  "$PULSEFLOW_HOST/api/archive/billing/daily?campaignId=CAMPAIGN_ID&startDate=2026-04-01&endDate=2026-04-30"

By hour and screen (playouts / impressions / spend) ​

bash
curl -sS \
  -H "Authorization: Bearer $DSP_API_KEY" \
  "$PULSEFLOW_HOST/api/archive/billing/hourly?campaignId=CAMPAIGN_ID&startDate=2026-04-01&endDate=2026-04-07&limit=50000"

Row shape (example)

json
{
  "enabled": true,
  "rows": [
    {
      "date": "2026-04-01",
      "hour": 14,
      "signId": "some-sign-id",
      "playouts": 42,
      "impressions": 42,
      "billedCents": 2100,
      "billedDollars": 21
    }
  ]
}

Per sign (rollup) ​

bash
curl -sS \
  -H "Authorization: Bearer $DSP_API_KEY" \
  "$PULSEFLOW_HOST/api/archive/billing/signs?campaignId=CAMPAIGN_ID&startDate=2026-04-01&endDate=2026-04-30"

Raw rows (paginated) ​

bash
curl -sS \
  -H "Authorization: Bearer $DSP_API_KEY" \
  "$PULSEFLOW_HOST/api/archive/billing/records?campaignId=CAMPAIGN_ID&startDate=2026-04-01&endDate=2026-04-30&page=1&limit=100"

Optional filters (most archive GETs) ​

QueryMeaning
dspInstanceIdLimit to one DSP
campaignIdOne campaign
signIdOne screen
startDate, endDateInclusive date range

Note: Even with a valid key, scope queries with dspInstanceId / campaignId so integrations only pull their own data. If you expose these URLs to external partners, prefer routing through your own gateway and enforcing those filters server-side.


Quick reference ​

GoalMethodPath
Screens near a pointGET/api/signs?lat=&lng=&radius= (radius in km)
Find one screenGET/api/signs/match
Find many screensGET/api/signs/match/bulk
Screens by keysPOST/api/signs/by-platform-keys
Screens by sellerGET/api/signs/by-seller-id
Create campaignPOST/api/partner/v1/campaigns
Pre-submit creative (Hivestack)POST/api/partner/v1/creatives
Creative status (Hivestack)GET/api/partner/v1/creatives/:dspCreativeId?publisherId=
Retrieve a campaignGET/api/partner/v1/campaigns/:id
Edit campaignPATCH/api/partner/v1/campaigns/:id
Activate / pausePOST/api/partner/v1/campaigns/:id/activation
Bid Archive billing summaryGEThttps://pulseflow.vuebillboards.com/api/archive/billing/summary
Bid Archive plays by hourGEThttps://pulseflow.vuebillboards.com/api/archive/billing/hourly

Support ​

Please email brian@vuebillboards.com with any questions.

Vue Billboards