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