> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rivi.software/llms.txt
> Use this file to discover all available pages before exploring further.

# Now-playing ingest

> Send reported track metadata from external automation to RadioTools.

Use now-playing ingestion to send **reported tracks** from your external automation or encoder. Listener websites and apps can then read now-playing and track history through the [public delivery APIs](/radiotools/api-reference/public-delivery).

## Before you start

1. A station administrator creates a dedicated playback integration credential in the RadioTools dashboard with **`ingest:write`**.
2. Copy the secret immediately — it is shown only once.
3. Store it securely in your automation configuration.

## Send an observation

```http theme={null}
POST /api/v1/integrations/playback?stationSlug=<slug>
Authorization: Bearer <integration credential>
Content-Type: application/json
```

Identify the station with exactly one of `stationSlug` (the public slug, as in listener URLs) or `stationId`. It must be the station the credential belongs to. A payload cannot select a different station.

| Source style | Example payload |
| - | - |
| Stream title | `{ "streamTitle": "The Riverside Sessions - Morning Light" }` |
| Separate fields | `{ "artist": "The Riverside Sessions", "title": "Morning Light" }` |

### Complete request example

This server-side request uses fictional track metadata and a source event ID:

```sh theme={null}
curl --fail-with-body -sS \
  "https://radiotools.rivi.software/api/v1/integrations/playback?stationSlug=$STATION_SLUG" \
  -H "Authorization: Bearer $RADIOTOOLS_INGEST_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "eventId": "automation-rivier-20261005-080000",
    "observedAt": 1791187200000,
    "artist": "The Riverlights",
    "title": "Morning Sun",
    "album": "Open Water",
    "durationSeconds": 210,
    "isPlaying": true
  }'
```

Set `STATION_SLUG` to the public slug of the station associated with the credential. Use the actual event timestamp; `observedAt` is Unix time in **milliseconds**. Listener reads return that timestamp as an ISO 8601 string. The request body is limited to **8 KiB**.

| Optional field | Purpose |
| - | - |
| `eventId` | Stable identifier for retries of the same source event |
| `observedAt` | Source observation timestamp in Unix milliseconds |
| `sourceTrackId` | Encoder library identifier; excluded from listener responses |
| `album` | Reported album title |
| `durationSeconds` | Duration in seconds, or `null` when unknown |
| `duration` | Legacy duration alias; must match `durationSeconds` if both are sent |
| `artworkUrl` | Artwork URI when available |
| `isPlaying` | Whether the observation reports playback; defaults to `true` |

Send `artist` and `title` together, or `streamTitle`. A stream title without the `Artist - Title` separator is stored entirely as the title. The OpenAPI schema shows length bounds and accepted values; the operation description captures these cross-field rules.

### Interpret the result

An illustrative successful outcome:

```json theme={null}
{ "accepted": true, "outcome": "recorded" }
```

There can also be a successful HTTP response when no live broadcast is available for rundown following:

```json theme={null}
{ "accepted": false, "outcome": "no-live-broadcast" }
```

Valid observations are persisted before rundown following. Inspect `accepted` **and** `outcome`; a `200` alone does not mean a track matched a rundown item. This endpoint reports metadata and follow outcomes, not audio control. Other documented outcomes include `not-playing`, `rejected`, `matched`, `uncertain`, `ignored` (recorded, but not matched to the rundown), and `on-air-not-following-rundown`.

When artwork lookup is enabled and `artworkUrl` is omitted, artwork may be filled asynchronously after the ingest response. Do not assume the response waits for it.

Include stable event IDs and timestamps when your encoder supports them. See the **Ingest** operation in the sidebar for the complete request schema and responses.

When retrying the same observation, reuse its `eventId` and payload. Use a new event ID for a new observation, even if the artist and title repeat. Legacy requests without event IDs may collapse within a short adjacent window.

The ingest endpoint may return legacy `{ "error": "string" }` responses for encoder compatibility.

## Read reported tracks

Use a separate **`listener:read`** credential to read:

* `GET /api/v1/stations/{stationSlug}/now-playing` — the latest observation with freshness.
* `GET /api/v1/stations/{stationSlug}/track-history` — paginated reported track history.

These observations describe metadata reported by external automation. They do not control audio playback.

## Confirm the observation arrived

Check the latest reported track and its timestamp in the station workspace. Then use a separate listener credential to confirm the listener-facing now-playing response. Keep ingestion and listener tokens separate so either consumer can be revoked independently.

A successful ingest request records metadata; it does not prove that audio played or that a rundown advanced. Automation following also needs an eligible live broadcast, matching music metadata, and enabled station and presenter settings.

If observations stop, check credential expiry, the configured station, HTTP response, payload format, and reporting timestamps. Preserve stable event IDs when retrying the same observation; do not turn one retry into a new reported event accidentally.


## Related topics

- [Authentication](/radiotools/api-reference/authentication.md)
- [API overview](/radiotools/api-reference/overview.md)
- [TypeScript SDK](/radiotools/api-reference/sdks/typescript.md)
- [Integrations](/radiotools/station-admin/integrations.md)
- [Other languages](/radiotools/api-reference/sdks/other-languages.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.