Skip to content

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-Type: application/json
{
"page": 1,
"pageSize": 25,
"locationId": "store-123",
"externalId": "screen-a",
"name": "Screen A",
"modifiedSince": "2026-09-01T00:00:00Z"
}
  • page / pageSize — pageSize defaults 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 this externalId (e.g. store-123) — the same value you set on the device’s location.externalId, not the location’s metadata.id.
  • externalId — only devices whose externalId contains this value (case-insensitive substring match).
  • name — only devices whose metadata.name contains this value (case-insensitive substring match).
  • modifiedSince — only devices whose metadata.lastUpdatedAt is 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 requested pageSize.
  • currentPage — the page echoed back (1-based).

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.

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.

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-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.

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 (a oneof, 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.

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.

A grocery screen in the entrance that faces the produce and dairy aisles:

POST /v1alpha/inventory/devices:upsert HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-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"]
}
}

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.

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-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.

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).

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.

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-Type: application/json
{ "tenantId": "<TENANT_ID>" }

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-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.

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-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.

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.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6...
X-Verve-Namespace: project:01ABCDEFGHIJKLMNOPQRSTUVWX
Content-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" }
]
}

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.

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.

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.

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.