Devices
Listing devices
Section titled “Listing devices”POST /v1alpha/inventory/devices returns the devices in your project, paginated. All request fields are optional and combine (AND) to narrow the result:
POST /v1alpha/inventory/devices HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "page": 1, "pageSize": 25, "locationId": "store-123", "externalId": "screen-a", "name": "Screen A", "modifiedSince": "2026-09-01T00:00:00Z"}page/pageSize—pageSizedefaults to 50 and is capped at 100. Omit both to get the first page at the default size.locationId— only devices assigned to the location with thisexternalId(e.g.store-123) — the same value you set on the device’slocation.externalId, not the location’smetadata.id.externalId— only devices whoseexternalIdcontains this value (case-insensitive substring match).name— only devices whosemetadata.namecontains this value (case-insensitive substring match).modifiedSince— only devices whosemetadata.lastUpdatedAtis strictly after this instant (RFC 3339). See Listing only what changed below.
Omit every filter to list all devices in the project (still paginated). The response is a flat list of devices plus pagination metadata:
{ "devices": [ { "metadata": { "id": "01HHXTNJ3K1QZ8V6X6Y7Z8A9B0", "name": "Screen A" }, "identifier": { "key": "acme", "playerId": "screen-a" }, "status": "STATUS_ACTIVE", "location": { "externalId": "store-123" } } ], "totalCount": 1, "totalPages": 1, "currentPage": 1}totalCount— total devices matching the filters across all pages.totalPages— number of pages at the requestedpageSize.currentPage— the page echoed back (1-based).
Listing only what changed
Section titled “Listing only what changed”modifiedSince narrows the listing to devices modified strictly after the instant you give, as an RFC 3339 timestamp compared against metadata.lastUpdatedAt. It is for incremental sync: record the highest lastUpdatedAt you have seen, then send it back on the next poll instead of walking every device again.
{ "modifiedSince": "2026-09-01T00:00:00Z" }Paging a changed set is safe here without any extra care. This listing is always ordered by an identifier fixed when the device is registered, so a device cannot move between pages while you walk them — unlike an order based on a field an edit can change.
Each entry is a full Device — see the API Reference for the complete request and response schemas.
How to reference locations in devices
Section titled “How to reference locations in devices”A device’s location reference points to a location by its externalId — set it directly on location.externalId (e.g. "location": { "externalId": "store-123" }). This is the location’s own external id, the same value you set on the location’s externalId field. A device’s location reference must point to an existing location — whether one you created or one that already existed.
Assigning a location to a device
Section titled “Assigning a location to a device”A device is not created with an inline location — you assign an existing location to it. Check whether the location you need already exists; if it doesn’t, create it first via locations:upsert. Then reference it from the device’s location field on a device upsert.
Reference the location by its external id, set directly on location.externalId:
POST /v1alpha/inventory/devices:upsert HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "metadata": { "name": "Screen A" }, "identifier": { "key": "acme", "playerId": "screen-a" }, "status": "STATUS_ACTIVE", "location": { "externalId": "store-123" }}The request body of a devices:upsert is the bare device object — there is no device wrapper. Here location.externalId is the location’s external id (store-123). The reference is resolve-or-reject: the platform looks up the location by that external id, and if no such location exists in the project the device upsert fails with FAILED_PRECONDITION — it never creates the location for you.
Venue, zones and aisles
Section titled “Venue, zones and aisles”A device’s physical placement describes where the screen sits: the kind of venue it’s in, the zone within that venue, and — for grocery stores — the product aisles it faces. All three live on the device, grouped under a single placement object on a devices:upsert:
placement.venue— the DOOH venue classification.placement.retailGroceryZone/placement.retailMallZone— the zone within the venue (aoneof, so set at most one).placement.aisles— the product aisles the device serves (grocery only).
placement.venue classifies the screen against the OpenOOH venue taxonomy (v1.2.1) — a parent tier (VENUE_TRANSIT_*, VENUE_RETAIL_*, …) with more specific child values. Retail media typically uses the retail parents, for example:
| Venue | Value |
|---|---|
| Grocery | VENUE_RETAIL_GROCERY |
| Mall | VENUE_RETAIL_MALL |
| Convenience store | VENUE_RETAIL_CONVENIENCE_STORE |
| Pharmacy | VENUE_RETAIL_PHARMACY |
| Fueling station | VENUE_RETAIL_FUELING_STATION |
See the Inventory API reference for the full Venue enum.
A zone pinpoints the device within a typed venue, as a grandchild of the venue classification. Zones are only defined for venues that have a typed subtype — today grocery and mall — and each has its own field in the zone oneof:
Grocery zones (placement.retailGroceryZone):
| Zone | Value |
|---|---|
| Entrance | VENUE_RETAIL_GROCERY_ENTRANCE |
| Check-out | VENUE_RETAIL_GROCERY_CHECK_OUT |
| Aisles | VENUE_RETAIL_GROCERY_AISLES |
| Exterior | VENUE_RETAIL_GROCERY_EXTERIOR |
Mall zones (placement.retailMallZone):
| Zone | Value |
|---|---|
| Concourse | VENUE_RETAIL_MALLS_CONCOURSE |
| Food court | VENUE_RETAIL_MALLS_FOOD_COURT |
| Spectacular | VENUE_RETAIL_MALLS_SPECTACULAR |
Set the zone field that matches the venue: retailGroceryZone for VENUE_RETAIL_GROCERY, retailMallZone for VENUE_RETAIL_MALL. Venues without a typed subtype carry no zone — leave both fields unset.
Aisles
Section titled “Aisles”For a grocery device positioned in the aisles, placement.aisles lists the product aisles it faces, as a repeated set of Aisle values (e.g. AISLE_PRODUCE, AISLE_DAIRY, AISLE_BAKERY, AISLE_FROZEN, AISLE_BEVERAGE, AISLE_MEAT). Aisles apply to grocery venues only; leave the list empty for any other venue. See the Inventory API reference for the full Aisle enum.
Example
Section titled “Example”A grocery screen in the entrance that faces the produce and dairy aisles:
POST /v1alpha/inventory/devices:upsert HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "metadata": { "name": "Screen A" }, "identifier": { "key": "acme", "playerId": "screen-a" }, "status": "STATUS_ACTIVE", "location": { "externalId": "store-123" }, "placement": { "venue": "VENUE_RETAIL_GROCERY", "retailGroceryZone": "VENUE_RETAIL_GROCERY_ENTRANCE", "aisles": ["AISLE_PRODUCE", "AISLE_DAIRY"] }}Declaring what a device can render
Section titled “Declaring what a device can render”A device advertises which creatives it can play through supportedAdTypes. Each entry pairs an ad type with the ad-format ids the device can render, so the exchange serves a device only the formats it declares:
POST /v1alpha/inventory/devices:upsert HTTP/1.1
{ "metadata": { "name": "Screen A" }, "supportedAdTypes": [ { "adType": "AD_TYPE_DISPLAY", "formats": [ "IMAGE-HORIZONTAL_16:9_3840x2160_JPG+PNG", "HTML5-HORIZONTAL_16:9_1920x1080_ZIP" ] } ]}To let a device receive HTML5 creatives, include an HTML5 ad-format id (delivered as an application/zip bundle) such as HTML5-HORIZONTAL_16:9_1920x1080_ZIP or HTML5-VERTICAL_9:16_1080x1920_ZIP. A device that declares only video or image formats is served only those; the exchange withholds HTML5 from it. See Creative formats for how a player renders each format, and Ad types & formats for the full catalog of format ids, dimensions, and file formats.
Screen size
Section titled “Screen size”Where supportedAdTypes says what a device can render in pixels, panel says how big the screen physically is — the active display area in millimetres:
POST /v1alpha/inventory/devices:upsert HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "metadata": { "name": "Screen A" }, "panel": { "widthMm": 1085, "heightMm": 610 }}Measure the lit area of the screen, not the enclosure or the bezel. panel describes each of the screenCount panels at this position, which are assumed identical.
Both dimensions are required together. A panel carrying only one of them describes nothing and is read as absent, rather than as a screen with a zero side.
Send millimetres; the diagonal is derived. Supply-side platforms that register inventory ahead of the auction ask for a screen’s diagonal in whole inches. That is computed from these two values rather than sent separately, so the two can never disagree — a 1085 × 610 mm panel registers as 49″.
A device with no panel still serves ads. Nothing in the internal auction depends on it. What it cannot do is be listed with an external SSP that requires a physical size: such a device is held back rather than registered with a guess.
Sensors
Section titled “Sensors”A device declares the audience sensors attached to it through sensors, a list of sensor capabilities. Each sensor enables a specific inline audience signal on GET /v1alpha2/ads and tells the exchange how to interpret it.
| Sensor | Meaning |
|---|---|
SENSOR_DEMOGRAPHICS |
The device reports the audience it detects via the seg parameter, using IAB Audience Taxonomy 1.1 segment ids. seg values are read as IAB segments directly (no vendor mapping). |
SENSOR_ADVERTIMA |
The device’s audience detection is provided by Advertima. seg carries Advertima’s own segment ids, which the exchange maps to IAB before selection. See SENSOR_ADVERTIMA. |
SENSOR_BASKET |
The device reports the shopper’s basket via the products parameter, matched against flights that target cart contents. |
SENSOR_LOCATION |
The device reports where it currently is inside the store, via the zone and aisle parameters on the ad request. For moving players — a handheld or cart-mounted device — whose position changes between requests. A fixed screen carries its position in placement instead (see Venue, zones and aisles) and does not declare this sensor. |
POST /v1alpha/inventory/devices:upsert HTTP/1.1
{ "metadata": { "name": "Screen A" }, "sensors": ["SENSOR_DEMOGRAPHICS", "SENSOR_BASKET"]}A device without the matching sensor cannot interpret that signal; such a request is rejected (400) — seg without SENSOR_DEMOGRAPHICS, products without SENSOR_BASKET, a zone or aisle without SENSOR_LOCATION. Additional sensor types (for other vendors) are added as those integrations land — each brings its own interpretation of the values.
A device can hold at most one sensor of each type. Sensors that drive the same signal are of the same type and are therefore mutually exclusive: a device cannot declare both SENSOR_DEMOGRAPHICS and SENSOR_ADVERTIMA, since both are demographic sensors feeding the seg signal. An upsert that lists two sensors of the same type is rejected (400).
Ad networks
Section titled “Ad networks”An ad network is a demand source that a device’s inventory is marketed in. Ad networks are defined per tenant; each device declares the networks it belongs to as part of its deployment, and the exchange only markets a device’s inventory in the networks it lists.
The available ad networks
Section titled “The available ad networks”Ad networks are configured per tenant. The Verve tenant defines these:
| Ad network | Marketed inventory |
|---|---|
| Retail Media Alliance (National Advertisement) | National advertising demand via the Retail Media Alliance. |
| Regional Advertisement | Regional advertising demand. |
The set is tenant-specific, so a device can only reference a network that exists for its tenant. List the networks for your tenant — the response gives each network’s id, which is the value you put in a device’s deployment:
POST /v1alpha/inventory/adNetworks HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "tenantId": "<TENANT_ID>" }Set a device’s ad networks
Section titled “Set a device’s ad networks”A device’s ad networks live on its deployment, in deployment.adNetworks — a list of ad-network ids (each network’s metadata.id from the list above, not its name). Set them on a device upsert:
POST /v1alpha/inventory/devices:upsert HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "metadata": { "name": "Screen A" }, "deployment": { "adNetworks": [ "01KMDH2GRKP9ZXZW242G9C8FBB", "01KTKMSW7D349VKBZBXBZRG76S" ] }}The two ids used above map to the Verve tenant’s networks like this:
| Ad network id | Ad network |
|---|---|
01KMDH2GRKP9ZXZW242G9C8FBB |
Retail Media Alliance (National Advertisement) |
01KTKMSW7D349VKBZBXBZRG76S |
Regional Advertisement |
Ids are tenant-specific, so always take them from the adNetworks list rather than hard-coding.
Allocation targets
Section titled “Allocation targets”An ad server is a system that supplies ads or content to a device. Allocation targets divide the device’s playtime between ad servers. This is how you set the content distribution of a device.
A static target gives a fixed percentage (0 to 100) of the playtime to the ad servers it names. The auction fills the rest of the playtime.
This device gets 50% of its playtime from the RMA CMS. It has a programmatic share of 30% and accepts guaranteed deals.
POST /v1alpha/inventory/devices:upsert HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{ "identifier": { "key": "acme", "playerId": "screen-a" }, "metadata": { "name": "Screen A" }, "status": "STATUS_ACTIVE", "deployment": { "adNetworks": ["01KMDH2GRKP9ZXZW242G9C8FBB"], "guaranteed": "GUARANTEED_ACCEPTED", "allocationTargets": [ { "static": { "percentage": 50, "adServers": [{ "reference": { "id": "verve-rma-cms" } }] } }, { "nonStatic": { "programmaticPercentage": 30 } } ] }}To get content from the RMA CMS, add a static target. Set its percentage. Take the CMS id from the ad servers list.
For the full Deployment schema, see the Inventory API reference.
Find the ad server ids
Section titled “Find the ad server ids”To get the ad server ids, send a list request. Set X-Verve-Namespace to project:<PROJECT_ID> or organization:<ORGANIZATION_ID>. The response lists the ad servers that the project or organization can use.
POST /v1alpha/inventory/adServers HTTP/1.1Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWXContent-Type: application/json
{}Each entry has an id, a name and a sourceType. Use an ad server with SOURCE_TYPE_CMS in a static target.
{ "adServers": [ { "id": "verve-px-sandbox", "name": "Verve PlaceExchange Sandbox", "sourceType": "SOURCE_TYPE_SSP" }, { "id": "verve-rma-cms", "name": "Verve RMA CMS", "sourceType": "SOURCE_TYPE_CMS" } ]}The programmatic share
Section titled “The programmatic share”An SSP (supply-side platform) is an outside system that bids for ad space. To get SSP demand, set programmaticPercentage above 0 in the nonStatic entry. Then every SSP in the ad servers list can bid for the device.
The value is a percent of the device’s playtime, like a static percentage. The shares of one device must not total more than 100. The default is 0, which turns SSPs off.
Do not send adServers in the nonStatic entry. The API refuses a non-empty list.
Guaranteed deals
Section titled “Guaranteed deals”To accept guaranteed deals, set deployment.guaranteed to GUARANTEED_ACCEPTED. To refuse them, set it to GUARANTEED_DECLINED.
An upsert replaces the deployment, so send guaranteed on every upsert. If you leave it out, the device sells guaranteed deals.
Device status
Section titled “Device status”Every device carries a status, and it is mandatory — a device upsert that leaves it unset (STATUS_UNSPECIFIED, the default) is rejected. Set it explicitly on every write:
| Status | Value | In the audience builder | Playouts & impressions | Receives content |
|---|---|---|---|---|
| Active | STATUS_ACTIVE |
Included | Counted | Yes |
| Inactive | STATUS_INACTIVE |
Excluded | Not counted | Yes |
An inactive device is not offline: it can still request and play ads — it receives content like any other device. What changes is that it is left out of the audience builder (not counted or targetable there) and its plays do not increase playout or impression totals. Marking a device inactive therefore removes it from measured, bookable inventory without stopping it from receiving content.
Playout impression multiplier
Section titled “Playout impression multiplier”A device’s playout impression multiplier sets how many impressions a single playout counts as. On DOOH screens one play can represent several contacts, so the multiplier is a fractional value, configured per weekday and optional time window through the device’s playoutImpressionFactors.
No windows at all means one. A device with an empty playoutImpressionFactors counts every playout as 1 impression. An unconfigured device is not a device measured at zero, so it still counts — and still competes for flights priced per 1,000 impressions.
Once you send any window, the list is the whole picture. As soon as playoutImpressionFactors contains a single rule, it is read as a complete specification: any weekday or time of day no rule covers resolves to a factor of 0 for that playout. Send every window the screen should be measured in, not just the busy ones.
To count zero, state it explicitly or leave the window out. Both a rule with factor: 0 and an hour no rule covers count as zero impressions — provided at least one rule exists.
Whether the store is open is a separate setting, configured on the location under Opening hours. Do not use a zero factor to express that a store is closed.
For full request and response schemas, see the Inventory API reference.