Viewcast Select
Motionworks Viewcast Select measures a campaign rather than a single display, reporting deduplicated reach, frequency, and impressions for the actual dates a campaign ran.
Motionworks Viewcast Select measures an out-of-home campaign rather than an individual display. Given the displays a campaign buys and the dates they run, Viewcast Select reports how many distinct people the campaign reached at a range of effective frequency thresholds, how often they were reached, and how many impressions were delivered on each day of the flight. Each person is counted once across the whole schedule, regardless of how many of the campaign's displays they passed.
Viewcast Select is date-specific, and that is the main thing separating it from Viewcast Profiles. Profiles publishes a great deal of history for a display: headline circulation, Opportunity to See (OTS), and Likelihood to See (LTS) as an average week over the release's forecast window, plus a month-by-month series in history, a week-by-week series in by_week, and a day-of-week and hourly distribution in by_day_hour. Its finest dated grain is the week — by_day_hour describes a typical week as percentages, so every Wednesday resolves to the same share. Viewcast Select reports the actual calendar dates a campaign ran: a value for each day of the flight, following each display's own in-market windows. The same weekday in different weeks of one flight carries different numbers, which a day-of-week distribution cannot produce.
Display-level inputs are taken from Viewcast Profiles — each roadside and place-based display carries its circulation, OTS, and LTS from its Profiles record. Viewcast Select adds the date-level resolution, the deduplication across the schedule, and the frequency distribution that Profiles does not report.
For details on viewshed creation, traffic measurement, and how circulation is converted to Opportunity to See metrics, see Viewcast Methodology.
Displays and Spots
Viewcast Select distinguishes two things that are easy to conflate:
- A display is the physical advertising surface — one face on one structure. It is identified by
display_id, a Motionworks-issued UUID. - A spot is one advertisement running on one display. It is identified by your own
spot_id, an integer, alongside thedisplay_idit runs on.
Several spots may run on the same display — the share-of-voice slots on a digital face, for example. A campaign schedule is therefore a list of spots, and every spot names the display it runs on. Both identifiers are required on every entry of the spots array.
Data Dimensions
- Frequency On demand. One analysis per campaign scenario.
- Geographic coverage United States. Roadside and place-based inventory.
- Geographic resolution County (
STCO), DMA (XDMA), and national (NATN) markets, plus a market group when more than one geography is requested. - Temporal resolution Daily. Every calendar date in the flight carries its own value, so the same weekday in different weeks of a flight will generally differ.
- Date availability Bounded by measured trip history. A flight extending beyond measured history cannot be analyzed.
- Measurement states
final,preliminary, andprojected, with the boundaries between them reported on every analysis. - Latency A campaign still in flight is remeasured weekly; a completed flight produces a final analysis.
- Scenario scale Up to 2,000 spots per campaign.
Scenario Lifecycle
A campaign scenario is created as an asset, measured on request, and read back by asset identifier. The asset carries one of five states.
| State | Description | Editable | Report available |
|---|---|---|---|
draft | Only the asset exists. No measurement has been requested | Yes | No |
queued | A measurement has been purchased; results pending | Yes | No |
in_flight | The flight is still active. Results are re-pulled periodically and are not yet final | No | Yes — interim |
completed | Every measure is final and the flight has ended. The asset and its analysis are retained permanently | No | Yes — final |
failed | A problem occurred with the measurement. The purchase is refunded | Yes | No |
completed is the only terminal state. A failed scenario stays editable and deletable, so a problem is corrected and the measurement retried in place under the same asset identifier rather than on a new scenario. An edit is accepted while the asset is draft, queued, or failed, and rejected with 400 once it is in_flight or completed.
Asset identifiers are prefixed camp, as in camp23QRRPB214V7J575MPGQAFSG0B.
Request Structure
{
"campaign": {
"name": "Launch Burst - Prior Year",
"reference": "launch_burst_prior_year_20250312_20250321"
},
"flight": {
"start_date": "2025-03-12",
"end_date": "2025-03-21"
},
"spots": [
{
"display_id": "36897d4e-1f46-4a90-8f28-2545d50295db",
"spot_id": 30614537,
"customer_spot_id": "LB-0001",
"customer_operator_name": "OUTFRONT",
"windows": [
{ "start_date": "2025-03-12", "end_date": "2025-03-21" }
]
},
{
"display_id": "ca747987-f5d1-4e08-a0a4-7ebeb8ba2ad4",
"spot_id": 30711220
}
],
"cohort": {
"geography_ids": ["US2020STCO25025", "US2020XDMA506", "XX2020NATNUS"],
"motionworks_segment_ids": ["ff97c081bf5f37d3"],
"motionworks_base_segment_id": "21d529097c81af04"
},
"customer": {
"customer_id": 30,
"customer_name": "Motionworks",
"customer_scenario_id": "launch_burst_prior_year_20250312_20250321",
"customer_scenario_name": "Launch Burst - Prior Year",
"customer_scenario_info": "Boston flight, posters and bulletins"
}
}Request Schema
flight, spots, cohort, and customer are required. campaign is optional.
| Name | Description | Type | Example |
|---|---|---|---|
| campaign. name | Your own name for the campaign, echoed back unchanged. | string | Launch Burst - Prior Year |
| campaign. reference | Your own reference for the campaign, echoed back unchanged. | string | launch_burst_prior_year_20250312_20250321 |
| flight | Required. The campaign's overall active period. A spot with no windows inherits this envelope in full. | object | See example above |
| flight. start_date | Required. Flight start date. | string (YYYY-MM-DD) | 2025-03-12 |
| flight. end_date | Required. Flight end date. | string (YYYY-MM-DD) | 2025-03-21 |
| spots | Required. The spots in the campaign schedule. Between 1 and 2,000 entries. | array of objects | See example above |
| spots[]. display_id | Required. Motionworks-issued identifier for the physical display this spot runs on, as a UUID. Only the format is validated at creation; there is no existence check. See Validation Rules. | string (UUID) | 36897d4e-1f46-4a90-8f28-2545d50295db |
| spots[]. spot_id | Required. Your own identifying number for this spot, required alongside display_id. | integer | 30614537 |
| spots[]. customer_spot_id | Your own reference for this spot, echoed back unchanged. | string | LB-0001 |
| spots[]. customer_operator_name | Your own plant or operator reference for this spot, echoed back unchanged. | string | OUTFRONT |
| spots[]. windows | This spot's own active periods. Omit to inherit the campaign flight. Several entries model a rotating flight. At least one entry when present. | array of objects | See example above |
| spots[]. windows[].start_date | Required within a window. Window start date. | string (YYYY-MM-DD) | 2025-03-12 |
| spots[]. windows[].end_date | Required within a window. Window end date. | string (YYYY-MM-DD) | 2025-03-21 |
| cohort | Required. The market geography and audience to measure. | object | See example above |
| cohort. geography_ids | Required. Geography ids for the requested market geography. More than one element defines a market group. See the Geography reference. | array of strings | ["US2020XDMA506"] |
| cohort. motionworks_segment_ids | Required. One or more target audience segment ids. Only the first is measured; additional ids are recorded for reference only. See the Population Segments reference. | array of strings | ["ff97c081bf5f37d3"] |
| cohort. motionworks_base_segment_id | Required. Id of the base segment every target segment is measured against. | string | 21d529097c81af04 |
| cohort. measures_release | Version pin for reproducible results across runs. Reserved for future use — not currently applied to measurement. | integer | 20260515 |
| customer | Required. Your own scenario identifiers, echoed back unchanged. | object | See example above |
| customer. customer_id | Required. Motionworks customer identification number. Note this is an integer on the request and is echoed as a string on the analysis. | integer | 30 |
| customer. customer_name | Required. Customer organization name. | string | Motionworks |
| customer. customer_scenario_id | Required. Your own identifier for the scenario. | string | launch_burst_prior_year_20250312_20250321 |
| customer. customer_scenario_name | Required. Human-readable scenario name for your own use. | string | Launch Burst - Prior Year |
| customer. customer_scenario_info | Free-form notes or metadata about the scenario. | string | Boston flight, posters and bulletins |
Per-spot windows are what distinguish a campaign from a spot list. Spots may start and stop on different dates within one flight, and the measurement follows each spot's own dates rather than the flight envelope.
Validation Rules
- Spot cap: A campaign carries between 1 and 2,000 spots. Larger schedules must be split across scenarios.
- Display identifiers are not checked at creation:
display_idis validated for UUID format only. There is no existence check against your own or the public display inventory at creation time. A UUID that does not correspond to a real, measurable display is caught once a purchased analysis runs; the scenario moves tofailedand the purchase is refunded. Verify display identifiers against Displays Search before purchasing. - Flight bound: A flight extending beyond the trip history Motionworks has measured cannot be analyzed.
- Editable states: Any field may change while the asset is
draft,queued, orfailed. An edit to anin_flightorcompletedasset is rejected with400. - Single measured segment: Only the first entry in
motionworks_segment_idsis measured. Additional entries are recorded for reference and do not produce additional slices.
Submission
Viewcast Select is served from the Motionworks API v2 base, https://api2.mworks.com. The scenario asset is managed under /v2/scenarios/campaigns; its measurement is purchased and read under /v2/viewcast.
| Operation | Endpoint |
|---|---|
| Create a scenario | POST /v2/scenarios/campaigns |
| List scenarios | GET /v2/scenarios/campaigns |
| Scenario counts by state | GET /v2/scenarios/campaigns/summary |
| Read a scenario | GET /v2/scenarios/campaigns/{asset_id} |
| Edit a scenario | PATCH /v2/scenarios/campaigns/{asset_id} |
| Delete a scenario | DELETE /v2/scenarios/campaigns/{asset_id} |
| Purchase an analysis | POST /v2/viewcast/measurements/purchase/select |
| Poll scenario status | GET /v2/viewcast/measurements/{asset_id} |
| Retrieve the analysis | GET /v2/viewcast/select/{asset_id} |
Per-operation pricing is listed in the Motionworks console.
Viewcast's other measurement purchases share the /v2/viewcast/measurements/purchase/ namespace and are documented separately: direct buys a measurement straight from a display_id, and batch buys up to 100 in one call.
Purchasing an analysis
{
"asset_id": "camp23QRRPB214V7J575MPGQAFSG0B",
"measurement_request_id": "7f1c9f2e-3b44-4d5a-9c21-0a8e6f4b1d33"
}measurement_request_id is an idempotency key you mint, carried in the request body rather than an Idempotency-Key header. A retry with the same key and an identical body replays the original result; the same key with a different body is rejected with 409.
The call returns 202 with the analysis run's own record:
| Field | Description |
|---|---|
measurement_id | The analysis run. Null only in the brief window between polling and queueing |
asset_id | The campaign scenario measured |
status | The run's own status — see below |
queued_at | When the run was accepted |
completed_at | When the run resolved |
refund_credits | Present on a failed run |
failure_reason | Present on a failed run |
The analysis run carries its own status, distinct from the scenario's state:
| Status | Meaning |
|---|---|
queued | Accepted, not started |
running | The analysis is being produced |
completed | The scenario's analysis is retrievable |
failed | failure_reason is populated and the purchase is refunded |
Polling
GET /v2/viewcast/measurements/{asset_id} returns the scenario's asset_id, state, and updated_at. On a failed scenario it also returns refund_credits and failure_reason:
{
"asset_id": "campH2RDW6W4XVMXZGA8S6JKSKCAYQ",
"state": "failed",
"updated_at": "2026-10-27T09:59:50.996071+00:00",
"refund_credits": 4999,
"failure_reason": "the campaign scenario was rejected by the measurement provider; the purchase was refunded"
}Retrieving the analysis
GET /v2/viewcast/select/{asset_id} serves the analysis while the scenario is in_flight (interim, updated weekly) or once it is completed (final, permanent). A draft, queued, or failed scenario returns 404, as does an unknown asset or one belonging to another organization.
| Parameter | Description |
|---|---|
delivery | full (the default) serves each spot's per-day delivery as daily_delivery cells. compact serves the same data as runs of consecutive days — see Compact delivery |
spot_ids | Comma-separated spot_id values, at most 500 per call. Returns the analysis with only those spots, in report order |
Errors:
| Status | Code | Cause |
|---|---|---|
400 | VALIDATION_ERROR | delivery is not full or compact, or spot_ids is empty, not a list of integers, has more than 500 ids, or names a spot not in this analysis |
400 | SELECTION_TOO_LARGE | The selected spots together exceed the response limit. The message gives their size |
404 | — | Unknown asset, an asset belonging to another organization, or one in draft, queued, or failed |
502 | UPSTREAM_UNAVAILABLE | The analysis could not be read right now. Retry shortly |
The 20 MB response limit
A response carries at most 20 MB of analysis. When the analysis is larger than that in the requested form, it is served without spots. In their place it carries spot_index, and meta.spots_omitted is true alongside meta.response_cap_bytes.
Each spot_index entry gives:
| Field | Description |
|---|---|
spot_id | The spot's id — pass it in spot_ids |
viewshed_id | The display the spot runs on |
customer_spot_id | Your own spot reference |
customer_operator_name | Your own plant or operator reference |
full_bytes | The spot's size in the full form |
compact_bytes | The spot's size in the compact form |
Request the spots you need with spot_ids, in batches sized against those per-spot byte counts. Asking for delivery=compact is usually enough to bring a large analysis under the limit on its own.
Response Structure
The top level carries thirteen blocks.
| Name | Description |
|---|---|
| run_id | Identifier of the measurement run that produced this analysis |
| report_id | Identifier of the analysis. Not guaranteed to equal your own scenario identifier |
| campaign | Campaign name and reference, echoed from the request |
| flight | Flight start and end. Note these are start/end on the analysis, where the request uses start_date/end_date |
| published | When the analysis was generated |
| runtime_version | The Viewcast Select version that produced it, as 2.4 |
| cohort | The segments and geographies measured. National is reported on its own and is removed from a requested group |
| customer | Your own tracking fields, echoed. customer_id is echoed as a string |
| measurement_state_boundaries | final_through, preliminary_through, and projected_from. Measurement is final through 14 days before the analysis was published, and preliminary from there to the publication date |
| markets | One entry per geography measured, plus national |
| metric_slices | The campaign answer. One slice per market and segment |
| spots | One entry per spot: identity, windows run, and per-spot performance |
| debug | Run messages and release version |
markets
| Name | Description | Type | Example |
|---|---|---|---|
| geography_id | The market's geography identifier. | string | US2020STCO25025 |
| level | county, dma, or national. | string | county |
| name | Human-readable market name. | string | Suffolk County |
| population.base | Base population of the market. | integer | 762300 |
| population. by_segment | One entry per measured segment, each with segment_id and population. | array of objects | [{"segment_id": "ff97c081bf5f37d3", "population": 643292}] |
| population. universe_source | The population view and release the figures were read from. | string | mworks-product.popcast.at_home_v2 |
metric_slices
A slice is one market crossed with one segment. Three kinds of market appear.
| Kind | When it appears |
|---|---|
market | Once per geography named in the request |
market_group | Only when more than one geography was requested. Its id joins the member geographies with +, as in US2020STCO25025+US2020XDMA506 |
national | Always, as XX2020NATNUS. It is reported on its own and is not folded into a requested group |
| Name | Description | Type | Example |
|---|---|---|---|
| market.kind | market, market_group, or national. | string | market |
| market.id | The slice's geography identifier. | string | US2020STCO25025 |
| market. member_geography_ids | The geographies this slice covers. More than one entry on a group. | array of strings | ["US2020STCO25025"] |
| market.name | Human-readable market name. | string | Suffolk County |
| segment.segment_id | The segment measured in this slice. | string | ff97c081bf5f37d3 |
| segment.role | The segment's role in the analysis. | string | primary |
| reach.ots | Reach built on Opportunity to See. | object | See below |
| reach.lts | Reach built on Likelihood to See. | object | See below |
| impressions.daily | One entry per date, each with a date and an ots and lts cell. | array of objects | See example below |
| impressions. cumulative | Running total through each date, in the same shape as daily. | array of objects | See example below |
Each reach basis carries four quantities plus a daily series:
| Name | Description |
|---|---|
thresholds | One entry per effective frequency, each with effective_frequency and a reach cell |
unique_exposure | Distinct people reached at least once |
total_exposures | Total exposure events across the campaign |
average_frequency | Exposures per person reached |
daily_reach | One entry per date, each with a date and a reach cell |
{
"market": {
"kind": "market",
"id": "US2020STCO25025",
"member_geography_ids": ["US2020STCO25025"],
"name": "Suffolk County"
},
"segment": { "segment_id": "ff97c081bf5f37d3", "role": "primary" },
"reach": {
"ots": {
"thresholds": [
{
"effective_frequency": 1,
"reach": {
"value": 131790,
"ci": [89959, 162959],
"basis": "modeled",
"num_observed_devices": 70,
"data_quality_status": "ok",
"measurement_state": "final",
"abstained": false,
"reason": null
}
}
],
"unique_exposure": { "value": 131790, "ci": [89959, 162959], "basis": "modeled", "...": "..." },
"total_exposures": { "value": 331538, "ci": [331538, 331538], "basis": "measured", "...": "..." },
"average_frequency": { "value": 2.52, "ci": [2.03, 3.69], "basis": "modeled", "...": "..." },
"daily_reach": [
{ "date": "2025-03-12", "reach": { "value": 31333, "ci": [20787, 31333], "...": "..." } }
]
},
"lts": { "...": "..." }
},
"impressions": {
"daily": [
{
"date": "2025-03-12",
"ots": { "value": 31333, "ci": [31333, 31333], "basis": "measured", "...": "..." },
"lts": { "value": 20426, "ci": [20426, 20426], "basis": "measured", "...": "..." }
}
],
"cumulative": [ { "date": "2025-03-12", "ots": { "...": "..." }, "lts": { "...": "..." } } ]
}
}Metric Cells
Every number in the analysis is a cell rather than a bare value, and every cell carries the same eight fields.
| Name | Description | Type | Example |
|---|---|---|---|
| value | The published number. Null on an abstention. | number | 131790 |
| ci | Confidence interval as [low, high]. A measured cell carries [value, value] — a zero-width placeholder, not a real range. | array of numbers | [89959, 162959] |
| basis | measured for directly observed quantities, modeled for constructed ones. Null on some abstentions. | string | modeled |
| num_observed_devices | Observed device count behind the estimate, so a stricter threshold than the engine's own can be applied. Null where not applicable. | integer | 70 |
| data_quality_status | Quality verdict for the cell. | string | ok |
| measurement_state | final, preliminary, or projected. Null on some abstentions. | string | final |
| abstained | Whether the cell abstained rather than publishing a value. | boolean | false |
| reason | Why the cell abstained. Null on an estimate. | string | not_yet_implemented: contribution |
Modeled cells — reach at each threshold, unique_exposure, average_frequency, and daily reach — carry a 90% band built from 20 replicates of the construction. Each replicate re-draws the start dates the curves are fitted on and re-samples the panel's devices. The band runs from roughly the 5th to the 95th percentile of the replicates and always contains the published value.
Measured cells — the impressions series, per-spot daily delivery, and total_exposures — carry ci as [value, value]. Read basis before interpreting ci as a range.
data_quality_status values
| Value | Meaning |
|---|---|
ok | The reads behind the cell met the quality floor |
below_floor | Too few devices or too few passes stood behind the cell |
structural_zero | A measured zero. The spot genuinely delivered nothing on that date |
below_resolution | A modeled zero. The campaign reached too few people at that threshold to publish at this precision. This is an estimate, not an abstention |
abstained | The cell abstained; read reason |
A measured 0 and a missing value are distinguishable: 0 with abstained false is a real zero; a missing value is null and carries an abstention reason.
Abstention reasons
| Reason | Meaning |
|---|---|
not_yet_implemented: contribution | Per-spot contribution to campaign reach is not yet published, so that cell always abstains |
Other reasons appear as the pipeline encounters them. Read reason as free text rather than a closed set.
spots
| Name | Description | Type | Example |
|---|---|---|---|
| viewshed_id | The display this spot runs on. Carries the same UUID submitted as display_id on the request. | string (UUID) | 36897d4e-1f46-4a90-8f28-2545d50295db |
| spot_id | Your own spot number, echoed from the request. | integer | 30614537 |
| customer_spot_id | Your own spot reference, echoed from the request. Null when not supplied. | string | LB-0001 |
| customer_operator_name | Your own plant or operator reference, echoed from the request. Null when not supplied. | string | OUTFRONT |
| data_quality_status | What the run could determine about this spot. | string | ok |
| spot_performance. windows | The dates this spot ran, as start and end. | array of objects | [{"start": "2025-03-12", "end": "2025-03-21"}] |
| spot_performance. daily_delivery | One entry per market per date, each with market, date, and an ots and lts cell. Served in the full form. | array of objects | See below |
| spot_performance. daily_delivery_series | The same data in runs of consecutive days. Served in the compact form. | array of objects | See Compact delivery |
| spot_performance. contribution | This spot's contribution to campaign reach. Currently abstains with reason not_yet_implemented: contribution. | object | — |
Note the analysis names the display viewshed_id, where the request names it display_id. The value is the same UUID.
{
"market": { "kind": "market", "id": "US2020STCO25025" },
"date": "2025-03-12",
"ots": { "value": 1915, "ci": [1915, 1915], "basis": "measured", "data_quality_status": "ok", "measurement_state": "final", "abstained": false, "reason": null },
"lts": { "value": 1222, "ci": [1222, 1222], "basis": "measured", "data_quality_status": "ok", "measurement_state": "final", "abstained": false, "reason": null }
}Compact delivery
delivery=full is the default. delivery=compact returns the same analysis with one substitution: each spot's daily_delivery array is replaced by daily_delivery_series. Nothing else changes, and no measurement is lost — the compact form is a re-encoding, not a summary.
Per-day delivery is the bulk of a large analysis, so the saving is substantial: on a 25-spot, 10-day campaign the full form is roughly four times the size of the compact one, and the gap widens with longer flights. Use compact when you want campaign totals, reach, or frequency and do not need to read individual days; use full when you intend to walk day by day and would rather not decode runs yourself.
How a series is encoded
daily_delivery carries one entry per market per date. daily_delivery_series carries one entry per run — a stretch of consecutive days that share a market and a measurement state — with the per-day numbers held in parallel arrays.
Element i of each array is the date start plus i days.
{
"market": { "kind": "market", "id": "US2020STCO25025" },
"start": "2025-03-12",
"measurement_state": "final",
"ots": {
"value": [1915, 4092, 7981, 1212, 336, 1009, 5480, 2231, 5067, 2080],
"ci_lo": null,
"ci_hi": null,
"exceptions": []
},
"lts": {
"value": [1222, 2611, 5093, 773, 214, 644, 3496, 1424, 3233, 1327],
"ci_lo": null,
"ci_hi": null,
"exceptions": []
}
}| Field | Description |
|---|---|
market | The market this run covers, as a kind/id pair |
start | The run's first date, YYYY-MM-DD |
measurement_state | final, preliminary, or projected, shared by every day in the run |
ots, lts | One entry per metric, each carrying the arrays below |
ots.value[] | One number or null per day, in date order |
ots.ci_lo[], ots.ci_hi[] | Per-day confidence-interval ends, or null when no day in the run has a real interval |
ots.exceptions[] | Day ranges whose attributes differ from the run's — see below |
Reading a series back
Walk each run day by day:
- The date is
startplus the array index. - The metric's value is
value[i]. - If the day falls inside an
exceptionsentry, take its other attributes from that entry. - Otherwise the day is an ordinary day of its run: it carries no exception attributes and takes the run's
measurement_state.
Two things to watch:
- A null value means unmeasured, not zero. A measured zero appears as
0. A day with no measurement for that metric appears asnull. - A null entry in
ci_lo/ci_himarks a day whose interval has no width. For a measured value, the interval is the value itself.
An exceptions entry covers first_day through last_day, given as inclusive day offsets from start, and groups days whose recorded attributes are identical:
{
"first_day": 2,
"last_day": 2,
"data_quality_status": "abstained",
"abstained": true,
"reason": "below_floor",
"basis": null,
"measurement_state": null,
"num_observed_devices": null
}A covered day takes those attributes from the exception and its value from value[i] — so the example above marks day 2 of the run as abstained, while its neighbours remain ordinary measured days.
Updated about 2 hours ago
