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 the display_id it 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, and projected, 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.

StateDescriptionEditableReport available
draftOnly the asset exists. No measurement has been requestedYesNo
queuedA measurement has been purchased; results pendingYesNo
in_flightThe flight is still active. Results are re-pulled periodically and are not yet finalNoYes — interim
completedEvery measure is final and the flight has ended. The asset and its analysis are retained permanentlyNoYes — final
failedA problem occurred with the measurement. The purchase is refundedYesNo

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.

NameDescriptionTypeExample
campaign.
name
Your own name for the campaign, echoed back unchanged.stringLaunch Burst - Prior Year
campaign.
reference
Your own reference for the campaign, echoed back unchanged.stringlaunch_burst_prior_year_20250312_20250321
flightRequired. The campaign's overall active period. A spot with no windows inherits this envelope in full.objectSee 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
spotsRequired. The spots in the campaign schedule. Between 1 and 2,000 entries.array of objectsSee 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.integer30614537
spots[].
customer_spot_id
Your own reference for this spot, echoed back unchanged.stringLB-0001
spots[].
customer_operator_name
Your own plant or operator reference for this spot, echoed back unchanged.stringOUTFRONT
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 objectsSee 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
cohortRequired. The market geography and audience to measure.objectSee 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.string21d529097c81af04
cohort.
measures_release
Version pin for reproducible results across runs. Reserved for future use — not currently applied to measurement.integer20260515
customerRequired. Your own scenario identifiers, echoed back unchanged.objectSee 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.integer30
customer.
customer_name
Required. Customer organization name.stringMotionworks
customer.
customer_scenario_id
Required. Your own identifier for the scenario.stringlaunch_burst_prior_year_20250312_20250321
customer.
customer_scenario_name
Required. Human-readable scenario name for your own use.stringLaunch Burst - Prior Year
customer.
customer_scenario_info
Free-form notes or metadata about the scenario.stringBoston 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_id is 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 to failed and 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, or failed. An edit to an in_flight or completed asset is rejected with 400.
  • Single measured segment: Only the first entry in motionworks_segment_ids is 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.

OperationEndpoint
Create a scenarioPOST /v2/scenarios/campaigns
List scenariosGET /v2/scenarios/campaigns
Scenario counts by stateGET /v2/scenarios/campaigns/summary
Read a scenarioGET /v2/scenarios/campaigns/{asset_id}
Edit a scenarioPATCH /v2/scenarios/campaigns/{asset_id}
Delete a scenarioDELETE /v2/scenarios/campaigns/{asset_id}
Purchase an analysisPOST /v2/viewcast/measurements/purchase/select
Poll scenario statusGET /v2/viewcast/measurements/{asset_id}
Retrieve the analysisGET /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:

FieldDescription
measurement_idThe analysis run. Null only in the brief window between polling and queueing
asset_idThe campaign scenario measured
statusThe run's own status — see below
queued_atWhen the run was accepted
completed_atWhen the run resolved
refund_creditsPresent on a failed run
failure_reasonPresent on a failed run

The analysis run carries its own status, distinct from the scenario's state:

StatusMeaning
queuedAccepted, not started
runningThe analysis is being produced
completedThe scenario's analysis is retrievable
failedfailure_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.

ParameterDescription
deliveryfull (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_idsComma-separated spot_id values, at most 500 per call. Returns the analysis with only those spots, in report order

Errors:

StatusCodeCause
400VALIDATION_ERRORdelivery 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
400SELECTION_TOO_LARGEThe 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
502UPSTREAM_UNAVAILABLEThe 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:

FieldDescription
spot_idThe spot's id — pass it in spot_ids
viewshed_idThe display the spot runs on
customer_spot_idYour own spot reference
customer_operator_nameYour own plant or operator reference
full_bytesThe spot's size in the full form
compact_bytesThe 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.

NameDescription
run_idIdentifier of the measurement run that produced this analysis
report_idIdentifier of the analysis. Not guaranteed to equal your own scenario identifier
campaignCampaign name and reference, echoed from the request
flightFlight start and end. Note these are start/end on the analysis, where the request uses start_date/end_date
publishedWhen the analysis was generated
runtime_versionThe Viewcast Select version that produced it, as 2.4
cohortThe segments and geographies measured. National is reported on its own and is removed from a requested group
customerYour own tracking fields, echoed. customer_id is echoed as a string
measurement_state_boundariesfinal_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
marketsOne entry per geography measured, plus national
metric_slicesThe campaign answer. One slice per market and segment
spotsOne entry per spot: identity, windows run, and per-spot performance
debugRun messages and release version

markets

NameDescriptionTypeExample
geography_idThe market's geography identifier.stringUS2020STCO25025
levelcounty, dma, or national.stringcounty
nameHuman-readable market name.stringSuffolk County
population.baseBase population of the market.integer762300
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.stringmworks-product.popcast.at_home_v2

metric_slices

A slice is one market crossed with one segment. Three kinds of market appear.

KindWhen it appears
marketOnce per geography named in the request
market_groupOnly when more than one geography was requested. Its id joins the member geographies with +, as in US2020STCO25025+US2020XDMA506
nationalAlways, as XX2020NATNUS. It is reported on its own and is not folded into a requested group
NameDescriptionTypeExample
market.kindmarket, market_group, or national.stringmarket
market.idThe slice's geography identifier.stringUS2020STCO25025
market.
member_geography_ids
The geographies this slice covers. More than one entry on a group.array of strings["US2020STCO25025"]
market.nameHuman-readable market name.stringSuffolk County
segment.segment_idThe segment measured in this slice.stringff97c081bf5f37d3
segment.roleThe segment's role in the analysis.stringprimary
reach.otsReach built on Opportunity to See.objectSee below
reach.ltsReach built on Likelihood to See.objectSee below
impressions.dailyOne entry per date, each with a date and an ots and lts cell.array of objectsSee example below
impressions.
cumulative
Running total through each date, in the same shape as daily.array of objectsSee example below

Each reach basis carries four quantities plus a daily series:

NameDescription
thresholdsOne entry per effective frequency, each with effective_frequency and a reach cell
unique_exposureDistinct people reached at least once
total_exposuresTotal exposure events across the campaign
average_frequencyExposures per person reached
daily_reachOne 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.

NameDescriptionTypeExample
valueThe published number. Null on an abstention.number131790
ciConfidence interval as [low, high]. A measured cell carries [value, value] — a zero-width placeholder, not a real range.array of numbers[89959, 162959]
basismeasured for directly observed quantities, modeled for constructed ones. Null on some abstentions.stringmodeled
num_observed_devicesObserved device count behind the estimate, so a stricter threshold than the engine's own can be applied. Null where not applicable.integer70
data_quality_statusQuality verdict for the cell.stringok
measurement_statefinal, preliminary, or projected. Null on some abstentions.stringfinal
abstainedWhether the cell abstained rather than publishing a value.booleanfalse
reasonWhy the cell abstained. Null on an estimate.stringnot_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

ValueMeaning
okThe reads behind the cell met the quality floor
below_floorToo few devices or too few passes stood behind the cell
structural_zeroA measured zero. The spot genuinely delivered nothing on that date
below_resolutionA modeled zero. The campaign reached too few people at that threshold to publish at this precision. This is an estimate, not an abstention
abstainedThe 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

ReasonMeaning
not_yet_implemented: contributionPer-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

NameDescriptionTypeExample
viewshed_idThe display this spot runs on. Carries the same UUID submitted as display_id on the request.string (UUID)36897d4e-1f46-4a90-8f28-2545d50295db
spot_idYour own spot number, echoed from the request.integer30614537
customer_spot_idYour own spot reference, echoed from the request. Null when not supplied.stringLB-0001
customer_operator_nameYour own plant or operator reference, echoed from the request. Null when not supplied.stringOUTFRONT
data_quality_statusWhat the run could determine about this spot.stringok
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 objectsSee below
spot_performance.
daily_delivery_series
The same data in runs of consecutive days. Served in the compact form.array of objectsSee 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": []
  }
}
FieldDescription
marketThe market this run covers, as a kind/id pair
startThe run's first date, YYYY-MM-DD
measurement_statefinal, preliminary, or projected, shared by every day in the run
ots, ltsOne 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:

  1. The date is start plus the array index.
  2. The metric's value is value[i].
  3. If the day falls inside an exceptions entry, take its other attributes from that entry.
  4. 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 as null.
  • A null entry in ci_lo/ci_hi marks 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.


Did this page help you?