Partner API v1

Create and monitor CTV campaign orders, upload creative and audiences, and retrieve delivery reports. Everything in this document is also what the partner portal uses.

AuthenticationRate limits & test modeWorkflowUploadsOrdersReportsErrorsWhat stays manual

Authentication

Base URL: https://topstreammedia.com/api/v1. Every request carries a partner API key as a bearer token. Keys are created and revoked by you in the portal; they are shown once and never emailed.

Authorization: Bearer tsm_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

A key only reaches orders and reports that belong to your partner account. Revoking a key or suspending a partner takes effect on the next request.

Rate limits & test mode

60 requests per minute per key. Responses include x-ratelimit-limit and x-ratelimit-remaining; over the limit you get 429 with retry-after: 60.

Test mode. Either create a test key (prefix tsm_test_) or send "test": true on an order. Test orders are validated and stored, appear in listings only with include_test=true, and are never forwarded, reviewed, or billed. No files are moved to Drive for test orders.

Workflow

  1. Upload files first (optional): POST /uploads returns one upload URL per file. PUT each file's bytes to its URL. Keep the returned upload_token.
  2. Create the order: POST /orders with the campaign fields and the upload_token. You get back an order_id like TS-20261027-K7QX with status received.
  3. Top Stream reviews every live order personally before anything launches. Status moves received → under_review → approved → live → completed (or paused / cancelled). Poll GET /orders/:id for status.
  4. Mid-flight changes: PATCH /orders/:id with the fields to change. Each change is stored as a numbered revision and sent to Top Stream for review. You may also submit a new order for the same campaign and reference it with external_campaign_id.
  5. Reports: when a monthly report is published, it appears in GET /reports with headline metrics and a download link for the PDF.

Uploads

POST /uploads

Declares files and returns direct-upload URLs. Up to 50 files per request; any size. File bytes never pass through the API — you PUT them straight to the storage URL.

{
  "upload_token": null,            // omit on first call; reuse to add files to the same batch
  "files": [
    { "name": "spot-30.mp4",  "size": 48211233, "type": "video/mp4",  "group": "campaign" },
    { "name": "300x250.png",  "size": 51233,    "type": "image/png",  "group": "retargeting" },
    { "name": "audience.csv", "size": 8250042,  "type": "text/csv",   "group": "audience" }
  ]
}

Response:

{
  "upload_token": "A1b2…",
  "uploads": [ { "name": "spot-30.mp4", "upload_url": "https://…", "method": "PUT" }, … ],
  "expires_in": 3600
}

Then, for each file: PUT {upload_url} with header Content-Type: {type} and the raw bytes as the body. A 2xx response means the file is stored. Upload URLs expire after one hour.

group is one of campaign (video spots, customer data), retargeting (display images), audience (household/IP lists such as a GrōVault export).

Orders

POST /orders

Creates an order. Fields mirror the Top Stream order form.

FieldRequiredNotes
company_nameyesAdvertiser's company name. Creates/links an advertiser under your account.
contact_nameyesFirst and last name.
emailyesAdvertiser or agency contact email.
phonenoAt least 10 digits if sent.
campaign_nameyes
start_dateyesYYYY-MM-DD; at least 3 business days from today.
end_dateyesYYYY-MM-DD; on or after start_date.
spot_lengthsnoArray or comma list of 15, 30, 60.
geographyyesZIP codes, DMAs, cities, geo-fence descriptions. Free text.
target_audienceyesWho the buyer is. Free text.
platforms_inventoryyesApps, channels, programming, sports, shows. Free text.
audience_refnoIdentifier or URL of a supplied audience (e.g. GrōVault audience id). Upload the file under group audience.
campaign_budgetyesUSD, positive number. Total CTV spend for the flight.
top_stream_cpm_rateyesUSD. The CPM agreed with Top Stream for this order.
frequency_capnoWhole number. Caps are limits, not goals.
frequency_cap_periodsif cap setArray of Daily, Weekly, Monthly, Campaign.
retargetingnotrue to add search & social retargeting.
retargeting_budgetif retargetingUSD, separate from campaign_budget.
tracking_pixels_needednoboolean
first_party_datanoboolean — you are supplying first-party or target data.
campaign_notesnoLanding page URL, offers, anything else.
external_campaign_idnoYour own campaign id, returned on every read. Use it to tie several orders to one campaign.
upload_tokennoFrom /uploads; attaches the uploaded files.
testnotrue for a test order.
POST /api/v1/orders
{
  "company_name": "Precision Garage Door of Buffalo",
  "contact_name": "Jane Smith",
  "email": "jane@example.com",
  "phone": "716-555-0100",
  "campaign_name": "November garage door install",
  "start_date": "2026-11-03",
  "end_date": "2026-11-30",
  "spot_lengths": [15, 30],
  "geography": "ZIPs 14221, 14225, 14227, 14224, 14127; Buffalo DMA",
  "target_audience": "Homeowners 36–64, HHI $75K+, homes 20+ years old",
  "platforms_inventory": "Roku, Fire TV, Hulu, Peacock, Paramount+, YouTube TV; sports and news",
  "audience_ref": "grovault:buffalo-ctv-2026-10-15",
  "campaign_budget": 7500,
  "top_stream_cpm_rate": "",
  "frequency_cap": 10,
  "frequency_cap_periods": ["Monthly"],
  "retargeting": true,
  "retargeting_budget": 500,
  "external_campaign_id": "adc-88213",
  "upload_token": "A1b2…"
}

Response 201 is the order object:

{
  "order_id": "TS-20261027-K7QX",
  "status": "received",
  "is_test": false,
  "revision": 1,
  "source": "api",
  "external_campaign_id": "adc-88213",
  "advertiser": { "company_name": "…", "contact_name": "…", "email": "…", "phone": "…" },
  "campaign":   { "campaign_name": "…", "start_date": "2026-11-03", "end_date": "2026-11-30", "spot_lengths": "15, 30" },
  "targeting":  { "geography": "…", "target_audience": "…", "platforms_inventory": "…", "audience_ref": "…" },
  "budget":     { "campaign_budget": 7500, "top_stream_cpm_rate": "", "frequency_cap": 10, "frequency_cap_periods": ["Monthly"] },
  "retargeting":{ "enabled": true, "budget": 500 },
  "creative":   { "tracking_pixels_needed": false, "first_party_data": false, "campaign_notes": null },
  "files":      [ { "id": "…", "group": "campaign", "name": "spot-30.mp4", "size": 48211233, "mime": "video/mp4" } ],
  "revisions":  [],
  "created_at": "2026-10-27T19:12:03Z",
  "updated_at": "2026-10-27T19:12:03Z"
}
GET /orders?status=live&limit=50&offset=0&include_test=false

Lists your orders, newest first. Returns { "orders": [...], "limit", "offset" }.

GET /orders/{order_id}

One order with files and revision history.

PATCH /orders/{order_id}

Mid-flight change. Send only the fields that change, plus an optional note and an optional upload_token for new files. Creates revision n+1, sets status to under_review (unless still received) and notifies Top Stream. Not allowed once an order is completed or cancelled. To pause, send {"note": "Pause from 2026-11-10"} together with any field change, or submit end_date; Top Stream confirms and sets the status.

PATCH /api/v1/orders/TS-20261027-K7QX
{ "campaign_budget": 9000, "end_date": "2026-12-07", "note": "Extend one week, add $1,500." }

Order status

StatusMeaning
receivedStored; awaiting Top Stream review.
under_reviewBeing reviewed (also after a revision).
approvedConfirmed; scheduled for the flight dates.
liveDelivering.
pausedDelivery paused at your request or Top Stream's.
completedFlight finished.
cancelledWill not run.

Reports

GET /reports?order_id=&from=YYYY-MM-DD&to=YYYY-MM-DD

Published reports, newest first. Each has metrics with the headline numbers from the monthly CTV report:

{
  "reports": [ {
    "id": "…", "order_id": "TS-20261027-K7QX", "campaign_name": "…", "company_name": "…",
    "period_start": "2026-11-01", "period_end": "2026-11-30", "title": "CTV report — November 2026",
    "metrics": {
      "impressions": 233704, "ctv_impressions": 228732, "ott_impressions": 4972,
      "unique_reach": 17977, "frequency": 13, "vcr": 99.9, "hours_watched": 1945,
      "website_visits": 1104, "retargeting_impressions": 116039, "retargeting_clicks": 413
    },
    "published_at": "2026-12-03T14:00:00Z"
  } ]
}
GET /reports/{id}

Same object plus download_url, a link to the PDF valid for 10 minutes.

Definitions. unique_reach is distinct households served, with each exposure resolved to the streaming device so a household watching across several apps counts once. frequency is average impressions per reached household. Both are observed, not modeled. website_visits are observed visits from the CTV campaign. Reports are monthly and published after the period closes.

Errors

CodeMeaning
400Malformed JSON.
401Missing, invalid or revoked API key.
403Partner account suspended.
404Unknown order or report.
409Order can no longer be changed.
422Validation failed. Body: { "error": "…", "details": [ { "field": "start_date", "message": "…" } ] }
429Rate limit exceeded.
500Internal error; retry with backoff.

What stays manual

Top Stream is a managed service. The API covers ordering, file delivery, change requests, status and reporting. The following are handled by Top Stream on your behalf and are not API-controlled: campaign approval, pacing, pause and resume, creative trafficking into the platform, and line-item structure. Delivery data is monthly; there are no daily or impression-level feeds at this time.

Questions: tim@topstreammedia.com