Sign In

Metadata

Push "now playing" information to sonicast and read the current state of a program.

sonicast keeps a now playing state for every program. A playout system (or any script) pushes what is currently on air, and sonicast turns it into the in-stream text shown to listeners in their players.

Prerequisites

API keys are created in the dashboard. The examples below read the key from the $SONICAST_API_KEY environment variable.

export SONICAST_API_KEY=sk_***

Every request is authenticated with the key as a Bearer token. The whoami endpoint returns the account the key belongs to, which makes it a quick way to check a key:

curl \  -H "Accept: application/json" \  -H "Authorization: Bearer $SONICAST_API_KEY" \  https://sonicast.io/api/v1/whoami/

Programs are addressed as BRAND_KEY:PROGRAM_KEY, for example /api/v1/programs/acme:default/. The program uid (for example /api/v1/programs/W3D4J4PV/) works as well. Unlike the keys, the uid never changes, so use it where a URL must keep working after a key is renamed.

Now playing

Get now playing

Returns the current state of the program. Expired layers are returned as null, and stream_text is the text currently used in the stream.

Get now playing
curl \  -H "Accept: application/json" \  -H "Authorization: Bearer $SONICAST_API_KEY" \  https://sonicast.io/api/v1/programs/BRAND_KEY:PROGRAM_KEY/now-playing/
{
  "item": {
    "type": "music",
    "title": "Enjoy the Silence",
    "artist": "Depeche Mode",
    "duration": 60,
    "started_at": "2026-09-28T08:00:00Z",
    "image_url": null
  },
  "show": null,
  "stream_text": "Depeche Mode - Enjoy the Silence"
}

Update the current item

An item is sent whenever a new element starts playing. With a duration (in seconds), the item expires on its own if the playout stops sending updates. started_at defaults to the time of the request.

Update now playing
curl \  -X POST \  -H "Content-Type: application/json" \  -H "Accept: application/json" \  -H "Authorization: Bearer $SONICAST_API_KEY" \  -d '{    "item": {      "type": "music",      "title": "Enjoy the Silence",      "artist": "Depeche Mode",      "duration": 60    }  }' \  https://sonicast.io/api/v1/programs/BRAND_KEY:PROGRAM_KEY/now-playing/

Only layers present in the payload are changed. Sending an item leaves the current show as it is, and the other way round.

Clear the current item

Sending null removes a layer right away, e.g. when the playout stops. The stream text then falls back to the show or the program name.

curl \  -X POST \  -H "Content-Type: application/json" \  -H "Accept: application/json" \  -H "Authorization: Bearer $SONICAST_API_KEY" \  -d '{"item": null}' \  https://sonicast.io/api/v1/programs/BRAND_KEY:PROGRAM_KEY/now-playing/

Update the show

Sets the show that is on air. Its title is used as stream text whenever no music item is playing (e.g. during talk, news or jingles). With ends_at, the show expires at the end of its slot.

curl \  -X POST \  -H "Content-Type: application/json" \  -H "Accept: application/json" \  -H "Authorization: Bearer $SONICAST_API_KEY" \  -d '{    "show": {      "title": "Morning Show",      "description": "Morning programme"    }  }' \  https://sonicast.io/api/v1/programs/BRAND_KEY:PROGRAM_KEY/now-playing/

Playout integrations

Playout systems that can only call a URL template, or send their own format, are covered in Playout integrations.

Update now playing
from django.db.models import QuerySetfrom django.http import Http404from django.shortcuts import get_object_or_404from tenancy.models import BrandMember, Programfrom tenancy.selectors import BRAND_MANAGER_ROLESfrom api_pub.models import ApiKeydef api_key_list(*, brand):    return (        ApiKey.objects.filter(brand=brand)        .select_related("created_by")        .prefetch_related("restricted_programs")    )def api_key_get_for_manager(*, user, uid: str) -> ApiKey:    return get_object_or_404(        ApiKey.objects.select_related("brand", "created_by").prefetch_related(            "restricted_programs"        ),        uid=uid,        brand__memberships__user=user,        brand__memberships__role__in=BRAND_MANAGER_ROLES,    )def brand_is_manager(*, brand, user) -> bool:    return BrandMember.objects.filter(        brand=brand,        user=user,        role__in=BRAND_MANAGER_ROLES,    ).exists()

On this page