Displays On Demand asset lifecycle
Recipes for creating, listing, summarizing, reading, refining, and archiving Displays On Demand assets, with identifier semantics and the related_parties object shape.
This page covers the CRUD lifecycle of a Displays On Demand asset: create, list, filter, summarize, read, refine, and archive. For the credit costs associated with each operation, see Purchase flow and credits. For the measurement queue and poll contract, see Measurements.
All endpoints are on the v2 base https://api2.mworks.com under the /v2/displays/* path. The base per-endpoint cost is 1 credit (displays_asset_crud); measurement purchases carry additional credits. Full request and response schemas are in the Displays API reference.
Identifier semantics
Every asset carries two identifiers with distinct issuance rules:
asset_id: a Supabase-issued String (for examplevcaJDVDYWSSMHAXVEFYW1DVD2PPFD). It is assigned at creation and is stable for the life of the asset. All CRUD and measurement endpoints key offasset_id.display_id: a Motionworks-issued String. It is minted only when the asset reachesfinal_measured. Before that state,display_idis absent. Once minted, it makes the asset joinable to the broader Displays inventory surface (including Viewcast Profiles).
This split exists because an asset in draft or draft_measured state is not yet a published display. The display_id is the join key to the published inventory world.
The request shape
The create request body is a strict object with classification, anchor, and related_parties at the top level (ADR-0043 D4). Optional fields are display (the display descriptor) and user_reference (free-text caller reference). Unknown fields return 400.
| Field | Type | Meaning |
|---|---|---|
classification | String | Roadside or Place-Based (immutable after creation). Required. |
anchor | Object | { "lat": number, "lon": number } reference point for the face, used for viewshed construction. Both lat and lon are NUMERIC. Required. |
related_parties | Array | Caller-side identifiers. related_parties[owner] carries party_display_id (from the face's face_id) and spots[0].spot_id (from the face's spot_id). Required. |
display | Object | Display descriptor (for example height_in, width_in, digital, orientation, media_name). Sparse; all sub-fields optional. |
user_reference | String | Free-text caller reference; null is accepted. Optional. |
The anchor and display attributes can be refined after creation (subject to the 100 m guardrail below). The classification field is immutable: once set at creation, a PATCH that changes it returns 400.
Create an asset
POST /v2/displays/assets
Creation is synchronous. The endpoint returns 200 OK with the created asset. There is no Idempotency-Key header (ADR-0042): a plain retry creates a second asset, subject to the 409 duplicate-face guard below.
curl -X POST https://api2.mworks.com/v2/displays/assets \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"classification": "Roadside",
"anchor": { "lat": 34.0901, "lon": -118.3839 },
"related_parties": [
{
"owner": {
"party_display_id": "my-face-4471",
"spots": [ { "spot_id": "my-spot-9902" } ]
}
}
],
"display": {
"orientation": 180.0,
"height_in": 120.0,
"width_in": 240.0,
"digital": false
}
}'Response (200):
{
"asset_id": "vcaJDVDYWSSMHAXVEFYW1DVD2PPFD",
"state": "draft",
"row_version": 1
}Duplicate-face guard: if your organization already has an asset with the same party_display_id and spot_id (carried in related_parties[owner]), the create returns 409. Both fields are optional; if you omit them, the guard cannot fire and a retry creates a second asset. Send both identifiers whenever you have them.
No create-time enrichment: asset creation writes the asset and its ledger row and returns. The enrichment dispatch fires from the measurement purchase, not from create (ADR-0042).
List and filter assets
GET /v2/displays/assets
Supports filtering by state and classification. Returns a paginated list of assets.
curl https://api2.mworks.com/v2/displays/assets?state=draft \
-H "Authorization: Bearer ***"Common query parameters include state (draft, draft_measured, final_measured) and classification (Roadside, Place-Based). Verified filters: state and classification both correctly narrow the result set (confirmed in E2E testing, MA-99, 2026-08-30).
Summary counts
GET /v2/displays/assets/summary
Returns aggregate counts of assets by state. Useful for dashboards and for confirming that create and archive operations moved counts as expected. Verified: summary counts track create and archive operations in real time (MA-99).
Read a single asset
GET /v2/displays/assets/{asset_id}
Returns the full asset record, including current state, display attributes, row_version, and (if present) the display_id.
curl https://api2.mworks.com/v2/displays/assets/vcaJDVDYWSSMHAXVEFYW1DVD2PPFD \
-H "Authorization: Bearer ***"Refine an asset
PATCH /v2/displays/assets/{asset_id}
You can update the anchor location and display attributes while the asset is in draft state (and before a final measurement is purchased). Two guardrails apply:
- 100 m anchor-move limit: the new anchor must be within 100 m (haversine distance) of the existing anchor. A PATCH that moves the anchor farther returns 400.
- Classification immutability:
classificationcannot be changed after creation. A PATCH that attempts to change it returns 400.
The PATCH uses optimistic concurrency via row_version: include the current row_version in the request. If the asset was modified by another caller since your last read, the PATCH returns 409 (stale version).
curl -X PATCH https://api2.mworks.com/v2/displays/assets/$ASSET_ID \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"row_version": 1,
"anchor": { "lat": 34.0902, "lon": -118.3840 },
"display": { "height_in": 130.0 }
}'Response (200): the updated asset record with an incremented row_version.
Verified (MA-99): a PATCH within 100 m returns 200; a PATCH that changes classification returns 400; a PATCH that toggles publication state while in draft returns 400.
Archive an asset
DELETE /v2/displays/assets/{asset_id}
Archives the asset. After deletion, a GET on the same asset_id returns 404. The archive is reflected immediately in summary counts.
curl -X DELETE https://api2.mworks.com/v2/displays/assets/$ASSET_ID \
-H "Authorization: Bearer ***"Response: 204 No Content. A subsequent GET returns 404.
Verified (MA-99): DELETE returns 204 and an immediate GET returns 404; summary counts decrement accordingly.
What to read next
Updated about 1 hour ago
