# Partner Device Monetization Requirements

Every partner device registration and lookup response (`POST /v1/partner/device`,
`POST /v1/partner/devices/batch`, `GET /v1/partner/device/:id`,
`GET /v1/partner/devices`) carries a `monetization_status` and a structured
`requirements` object — modeled directly on Stripe's
`account.requirements.currently_due`. Instead of silently accepting a
registration that can never actually serve ads, the response tells you exactly
what's missing and how to fix it.

```json
{
  "monetization_status": "blocked",
  "requirements": {
    "currently_due": [
      {
        "code": "location_awaiting_device_gps",
        "field": "location",
        "reason": "awaiting_device_gps",
        "message": "This mobile screen has no location yet. Send a GPS fix in the heartbeat (location.lat/lng) and it resolves automatically.",
        "doc_url": "https://api.trillboards.com/docs/monetization-requirements#location_awaiting_device_gps"
      }
    ],
    "eventually_due": []
  }
}
```

**`monetization_status` values**

- **`ready`** — the screen has its own coordinates AND its venue is classified.
  Fully monetizable.
- **`limited`** — servable now, but not on every rail: the venue is not yet
  classified (missing venue taxonomy / floor-price refinement), or the screen
  has no position of its own yet and is listed in inventory feeds at your
  fleet's market location (see [Location capability](#location-capability)).
- **`blocked`** — no rail can serve it: no usable location for any rail, or
  venue classification permanently failed.

**`currently_due`** entries block (or limit) monetization right now.
**`eventually_due`** entries don't block serving — they improve creative
selection / targeting quality and are worth fixing, but there's no rush.

A device is never refused registration for missing location: it is created,
and the response says which rails can serve it until a position arrives.

## Location capability

Every response also carries `location_capability`: where the screen's location
comes from, and, per rail, whether that rail has the location it needs.

```json
{
  "location_capability": {
    "position": "fleet_market",
    "rails": {
      "inventory_feed": { "status": "ready", "location_source": "fleet_market", "reason": null },
      "geo_targeted_demand": { "status": "blocked", "location_source": null, "reason": "awaiting_device_gps" },
      "visit_attribution": { "status": "blocked", "location_source": null, "reason": "awaiting_device_gps" }
    }
  }
}
```

**`position`** — `precise` (the screen or its device has coordinates),
`fleet_market` (no coordinates of its own; your account's declared fleet market
location lists it), or `none`.

| Rail | What it reads | Ready from |
| --- | --- | --- |
| `inventory_feed` | The programmatic player feed, which accepts a fleet market location | The screen's or device's own coordinates (`screen`, `device`), or your fleet's declared market location (`fleet_market`) when it is complete and its state can be expressed as an ISO 3166-2 code |
| `geo_targeted_demand` | Geo-targeted programmatic demand, and the marketplace placement registries that publish a screen's own coordinate | The screen's or device's own coordinates (`screen`, `device`) |
| `visit_attribution` | Visit-attribution location data | The screen's own coordinates and a supported country (`screen`) |

`monetization_status` is computed over `inventory_feed` and
`geo_targeted_demand`; `visit_attribution` is a measurement capability and does
not change it.

A blocked rail's **`reason`**:

- `awaiting_device_gps` — a mobile screen with no fix yet. Send a GPS fix in the
  heartbeat (`location.lat` / `location.lng`); every rail that needs the
  screen's own position becomes ready.
- `no_location_provided`, `city_without_state`, `geocode_failed` — a static
  screen whose location could not be resolved (see
  [location_unresolvable](#location_unresolvable)).
- `screen_position_pending` — the device has reported a position and the
  screen's own coordinates have not been filled from it yet; the next GPS fix
  fills them.
- `country_unknown` — the rail needs the screen's country. Send
  `location.country` (ISO 3166-1 alpha-2).
- `country_not_supported` — visit-attribution data is not available in the
  screen's country.

---

### location_awaiting_device_gps

- **Field**: `location`
- **Blocks monetization**: yes (`blocked`), or `limited` when your account has
  declared its fleet market location, which lists the screen in inventory feeds
  until its own fix arrives
- **Summary**: This is a mobile or semi-mobile screen (`mobility_type`) with no
  usable coordinates yet, on either the screen record or the device record.

**How to fix**: this is self-healing — no separate API call needed. Include a
`location` object with a real GPS fix in the device's heartbeat body:

```json
{
  "location": {
    "lat": 45.5017,
    "lng": -73.5673,
    "accuracy_m": 12,
    "heading": 180,
    "speed_mps": 8.3,
    "source": "gps",
    "timestamp": 1784800000
  }
}
```

`lat`/`lng` (or `latitude`/`longitude`) are the only required fields; the rest
are optional. The very next heartbeat that includes a valid fix resolves this
requirement automatically. Note this is heartbeat-only — `location` is not
read from the device registration/PATCH body's `location.lat/lng` fields for
this particular gate on mobile screens, since a one-time registration-time
coordinate goes stale as a moving screen travels.

### location_unresolvable

- **Field**: `location`
- **Blocks monetization**: yes (`blocked`), or `limited` when your account has
  declared its fleet market location (see
  [Location capability](#location-capability))
- **Summary**: This is a static (non-mobile) screen with no usable
  coordinates, and what was provided couldn't be geocoded. The `reason`
  sub-field distinguishes three cases:
  - `no_location_provided` — no `location.lat/lng`, street address, or
    city+state was sent at all.
  - `city_without_state` — only a `city` was sent; a city alone cannot be
    geocoded.
  - `geocode_failed` — an address was sent but couldn't be resolved to
    coordinates.

**How to fix**: on `POST`/`PATCH /v1/partner/device`, send `location.lat` /
`location.lng` directly, or a full street address, or at minimum
`location.city` + `location.state`.

### enrichment_failed

- **Field**: `venue`
- **Blocks monetization**: yes (`blocked`)
- **Summary**: Automated venue classification permanently failed for this
  screen (`enrichment_status: "permanently_failed"`).

**How to fix**: verify the location and venue details are accurate and
re-save the device. If it fails again, contact support with the device's
`external_device_id` and `screen_id`.

### display_size_missing

- **Field**: `display`
- **Blocks monetization**: no (`eventually_due` — servable, just not ideal)
- **Summary**: `display.width` / `display.height` (pixels) were never sent, so
  creatives can't be sized precisely for this screen.

**How to fix**: send `display.width` and `display.height` in pixels on
`POST /v1/partner/device` (registration) or `PATCH /v1/partner/device/:id`
(update anytime after registration). This is not an automatic/heartbeat field
— the SDK must report it explicitly.
