MENU navbar-image

Introduction

The Spot API lets you programmatically manage your digital signage network — control players, manage content, query analytics, and receive real-time events via webhooks.

Authentication

All endpoints require a Bearer token. Create API keys in Settings → API Keys.

curl -H "Authorization: Bearer {YOUR_API_KEY}" https://yourapp.com/api/v1/players

Scopes

Each key has one or more scopes that control access:

Scope Description
read:players List players and groups
write:players Send commands to players
read:content Read layouts, playlists, assets
write:content Create/modify content
read:analytics Access analytics and reports
webhooks Manage webhook registrations

Rate limits

Plan Requests/min
Free 60
Pro 300
Business 1,000

Rate limit status is returned in every response via X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers.

Errors

The API uses standard HTTP status codes. Error responses follow this shape:

{ "message": "Descriptive error message" }

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_API_KEY}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Create and manage API keys in Settings → API Keys.

Analytics

Query playback, uptime, and impression data for your network.

Summary

requires authentication

High-level snapshot: active players, plays today, and average uptime.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/analytics/summary" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/analytics/summary"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/analytics/summary';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/analytics/summary'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/analytics/summary

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Playback events

requires authentication

Returns aggregated playback data for the specified date range.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/analytics/playback?from=2026-01-01T00%3A00%3A00Z&to=2026-01-31T23%3A59%3A59Z&player_id=1&layout_id=1&granularity=day" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-05-01T18:20:08\",
    \"to\": \"2052-05-24\",
    \"player_id\": 16,
    \"layout_id\": 16,
    \"granularity\": \"hour\"
}"
const url = new URL(
    "http://localhost/api/v1/analytics/playback"
);

const params = {
    "from": "2026-01-01T00:00:00Z",
    "to": "2026-01-31T23:59:59Z",
    "player_id": "1",
    "layout_id": "1",
    "granularity": "day",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24",
    "player_id": 16,
    "layout_id": 16,
    "granularity": "hour"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/analytics/playback';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'from' => '2026-01-01T00:00:00Z',
            'to' => '2026-01-31T23:59:59Z',
            'player_id' => '1',
            'layout_id' => '1',
            'granularity' => 'day',
        ],
        'json' => [
            'from' => '2026-05-01T18:20:08',
            'to' => '2052-05-24',
            'player_id' => 16,
            'layout_id' => 16,
            'granularity' => 'hour',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/analytics/playback'
payload = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24",
    "player_id": 16,
    "layout_id": 16,
    "granularity": "hour"
}
params = {
  'from': '2026-01-01T00:00:00Z',
  'to': '2026-01-31T23:59:59Z',
  'player_id': '1',
  'layout_id': '1',
  'granularity': 'day',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload, params=params)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/analytics/playback

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string     

Start date (ISO 8601). Example: 2026-01-01T00:00:00Z

to   string     

End date (ISO 8601). Example: 2026-01-31T23:59:59Z

player_id   integer  optional    

Filter by player. Example: 1

layout_id   integer  optional    

Filter by layout. Example: 1

granularity   string  optional    

Aggregation: hour, day, week. Default: day. Example: day

Body Parameters

from   string     

Must be a valid date. Example: 2026-05-01T18:20:08

to   string     

Must be a valid date. Must be a date after or equal to from. Example: 2052-05-24

player_id   integer  optional    

Example: 16

layout_id   integer  optional    

Example: 16

granularity   string  optional    

Example: hour

Must be one of:
  • hour
  • day
  • week

Export playback data

requires authentication

Generates a CSV export of playback events. Returns a signed download URL valid for 1 hour.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/analytics/playback/export?from=2026-01-01T00%3A00%3A00Z&to=2026-01-31T23%3A59%3A59Z" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-05-01T18:20:08\",
    \"to\": \"2052-05-24\"
}"
const url = new URL(
    "http://localhost/api/v1/analytics/playback/export"
);

const params = {
    "from": "2026-01-01T00:00:00Z",
    "to": "2026-01-31T23:59:59Z",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/analytics/playback/export';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'from' => '2026-01-01T00:00:00Z',
            'to' => '2026-01-31T23:59:59Z',
        ],
        'json' => [
            'from' => '2026-05-01T18:20:08',
            'to' => '2052-05-24',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/analytics/playback/export'
payload = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24"
}
params = {
  'from': '2026-01-01T00:00:00Z',
  'to': '2026-01-31T23:59:59Z',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload, params=params)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/analytics/playback/export

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string     

Start date (ISO 8601). Example: 2026-01-01T00:00:00Z

to   string     

End date (ISO 8601). Example: 2026-01-31T23:59:59Z

Body Parameters

from   string     

Must be a valid date. Example: 2026-05-01T18:20:08

to   string     

Must be a valid date. Must be a date after or equal to from. Example: 2052-05-24

Player uptime

requires authentication

Returns online/offline percentage per player for the date range.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/analytics/players/uptime?from=2026-01-01T00%3A00%3A00Z&to=2026-01-31T23%3A59%3A59Z" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-05-01T18:20:08\",
    \"to\": \"2052-05-24\"
}"
const url = new URL(
    "http://localhost/api/v1/analytics/players/uptime"
);

const params = {
    "from": "2026-01-01T00:00:00Z",
    "to": "2026-01-31T23:59:59Z",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/analytics/players/uptime';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'from' => '2026-01-01T00:00:00Z',
            'to' => '2026-01-31T23:59:59Z',
        ],
        'json' => [
            'from' => '2026-05-01T18:20:08',
            'to' => '2052-05-24',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/analytics/players/uptime'
payload = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24"
}
params = {
  'from': '2026-01-01T00:00:00Z',
  'to': '2026-01-31T23:59:59Z',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload, params=params)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/analytics/players/uptime

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string     

Start date (ISO 8601). Example: 2026-01-01T00:00:00Z

to   string     

End date (ISO 8601). Example: 2026-01-31T23:59:59Z

Body Parameters

from   string     

Must be a valid date. Example: 2026-05-01T18:20:08

to   string     

Must be a valid date. Must be a date after or equal to from. Example: 2052-05-24

Impressions

requires authentication

Returns ad impression counts and revenue breakdown.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/analytics/impressions?from=2026-01-01T00%3A00%3A00Z&to=2026-01-31T23%3A59%3A59Z&campaign_id=1" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"from\": \"2026-05-01T18:20:08\",
    \"to\": \"2052-05-24\",
    \"campaign_id\": 16
}"
const url = new URL(
    "http://localhost/api/v1/analytics/impressions"
);

const params = {
    "from": "2026-01-01T00:00:00Z",
    "to": "2026-01-31T23:59:59Z",
    "campaign_id": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24",
    "campaign_id": 16
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/analytics/impressions';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'from' => '2026-01-01T00:00:00Z',
            'to' => '2026-01-31T23:59:59Z',
            'campaign_id' => '1',
        ],
        'json' => [
            'from' => '2026-05-01T18:20:08',
            'to' => '2052-05-24',
            'campaign_id' => 16,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/analytics/impressions'
payload = {
    "from": "2026-05-01T18:20:08",
    "to": "2052-05-24",
    "campaign_id": 16
}
params = {
  'from': '2026-01-01T00:00:00Z',
  'to': '2026-01-31T23:59:59Z',
  'campaign_id': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, json=payload, params=params)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/analytics/impressions

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

from   string     

Start date (ISO 8601). Example: 2026-01-01T00:00:00Z

to   string     

End date (ISO 8601). Example: 2026-01-31T23:59:59Z

campaign_id   integer  optional    

Filter by campaign. Example: 1

Body Parameters

from   string     

Must be a valid date. Example: 2026-05-01T18:20:08

to   string     

Must be a valid date. Must be a date after or equal to from. Example: 2052-05-24

campaign_id   integer  optional    

Example: 16

Assets

List assets and generate signed upload URLs.

List assets

requires authentication

Returns all assets for the authenticated team.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/assets?type=image&folder_id=1" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/assets"
);

const params = {
    "type": "image",
    "folder_id": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/assets';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'type' => 'image',
            'folder_id' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/assets'
params = {
  'type': 'image',
  'folder_id': '1',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/assets

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

type   string  optional    

Filter by type: image, video, audio. Example: image

folder_id   integer  optional    

Filter by folder. Example: 1

Get upload URL

requires authentication

Returns a signed URL for direct upload to storage. Upload the file via HTTP PUT to the upload_url, then reference asset_key when confirming the upload in your workflow.

Example request:
curl --request POST \
    "http://localhost/api/v1/assets/upload-url" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"filename\": \"promo-banner.jpg\",
    \"mime_type\": \"image\\/jpeg\"
}"
const url = new URL(
    "http://localhost/api/v1/assets/upload-url"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "filename": "promo-banner.jpg",
    "mime_type": "image\/jpeg"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/assets/upload-url';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'filename' => 'promo-banner.jpg',
            'mime_type' => 'image/jpeg',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/assets/upload-url'
payload = {
    "filename": "promo-banner.jpg",
    "mime_type": "image\/jpeg"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Request      

POST api/v1/assets/upload-url

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

filename   string     

The original filename. Example: promo-banner.jpg

mime_type   string     

The file MIME type. Example: image/jpeg

Delete asset

requires authentication

Permanently deletes an asset. This cannot be undone.

Example request:
curl --request DELETE \
    "http://localhost/api/v1/assets/16" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/assets/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/assets/16';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/assets/16'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('DELETE', url, headers=headers)
response.json()

Request      

DELETE api/v1/assets/{asset_id}

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

asset_id   integer     

The ID of the asset. Example: 16

asset   integer     

The asset ID. Example: 1

Layouts

Read layouts and assign them to players or groups.

List layouts

requires authentication

Returns all layouts for the authenticated team.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/layouts" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/layouts"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/layouts';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/layouts'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/layouts

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Assign layout

requires authentication

Assigns a layout to one or more players or player groups.

Example request:
curl --request PATCH \
    "http://localhost/api/v1/layouts/16/assign" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"player_ids\": [
        1,
        2
    ],
    \"group_ids\": [
        1
    ]
}"
const url = new URL(
    "http://localhost/api/v1/layouts/16/assign"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "player_ids": [
        1,
        2
    ],
    "group_ids": [
        1
    ]
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/layouts/16/assign';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'player_ids' => [
                1,
                2,
            ],
            'group_ids' => [
                1,
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/layouts/16/assign'
payload = {
    "player_ids": [
        1,
        2
    ],
    "group_ids": [
        1
    ]
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('PATCH', url, headers=headers, json=payload)
response.json()

Request      

PATCH api/v1/layouts/{layout_id}/assign

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

layout_id   integer     

The ID of the layout. Example: 16

layout   integer     

The layout ID. Example: 1

Body Parameters

player_ids   integer[]  optional    

Assign to specific players.

group_ids   integer[]  optional    

Assign to all players in these groups.

Player Groups

Manage groups and dispatch bulk commands.

List player groups

requires authentication

Returns all player groups with player counts.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/player-groups" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/player-groups"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/player-groups';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/player-groups'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/player-groups

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Send command to group

requires authentication

Dispatches a command to every player in the group.

Example request:
curl --request POST \
    "http://localhost/api/v1/player-groups/16/commands" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"command\": \"reboot\"
}"
const url = new URL(
    "http://localhost/api/v1/player-groups/16/commands"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "command": "reboot"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/player-groups/16/commands';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'command' => 'reboot',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/player-groups/16/commands'
payload = {
    "command": "reboot"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Request      

POST api/v1/player-groups/{group_id}/commands

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

group_id   integer     

The ID of the group. Example: 16

group   integer     

The group ID. Example: 1

Body Parameters

command   string     

The command. Allowed: reboot, reload_content, take_screenshot. Example: reboot

Players

Manage and monitor your display network.

List players

requires authentication

Returns all players belonging to your team with their current status.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/players?status=online" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/players"
);

const params = {
    "status": "online",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/players';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'status' => 'online',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/players'
params = {
  'status': 'online',
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers, params=params)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/players

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filter by status: online, offline. Example: online

Get player

requires authentication

Returns a single player with full detail including current content assignment.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/players/16" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/players/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/players/16';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/players/16'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/players/{player_id}

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

player_id   integer     

The ID of the player. Example: 16

player   integer     

The player ID. Example: 1

Get latest screenshot

requires authentication

Returns the URL of the most recent screenshot captured from this player.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/players/16/screenshot" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/players/16/screenshot"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/players/16/screenshot';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/players/16/screenshot'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/players/{player_id}/screenshot

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

player_id   integer     

The ID of the player. Example: 16

player   integer     

The player ID. Example: 1

Send command

requires authentication

Dispatches a command to a player via WebSocket. Commands are fire-and-forget; track acknowledgement via the command_id returned.

Example request:
curl --request POST \
    "http://localhost/api/v1/players/16/commands" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"command\": \"reboot\"
}"
const url = new URL(
    "http://localhost/api/v1/players/16/commands"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "command": "reboot"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/players/16/commands';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'command' => 'reboot',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/players/16/commands'
payload = {
    "command": "reboot"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Request      

POST api/v1/players/{player_id}/commands

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

player_id   integer     

The ID of the player. Example: 16

player   integer     

The player ID. Example: 1

Body Parameters

command   string     

The command to send. Allowed: reboot, reload_content, take_screenshot. Example: reboot

Playlists

Create and manage playlists.

List playlists

requires authentication

Returns all playlists for the authenticated team.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/playlists" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/playlists"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/playlists';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/playlists'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/playlists

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Create playlist

requires authentication

Creates a new empty playlist.

Example request:
curl --request POST \
    "http://localhost/api/v1/playlists" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Summer Campaign\",
    \"description\": \"Q3 promotions\"
}"
const url = new URL(
    "http://localhost/api/v1/playlists"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Summer Campaign",
    "description": "Q3 promotions"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/playlists';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Summer Campaign',
            'description' => 'Q3 promotions',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/playlists'
payload = {
    "name": "Summer Campaign",
    "description": "Q3 promotions"
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Request      

POST api/v1/playlists

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

The playlist name. Example: Summer Campaign

description   string  optional    

optional. Example: Q3 promotions

Update playlist items

requires authentication

Replaces the items in a playlist with a new ordered list of asset IDs.

Example request:
curl --request PATCH \
    "http://localhost/api/v1/playlists/16/items" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"items\": [
        {
            \"asset_id\": 1,
            \"duration\": 10
        }
    ]
}"
const url = new URL(
    "http://localhost/api/v1/playlists/16/items"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "items": [
        {
            "asset_id": 1,
            "duration": 10
        }
    ]
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/playlists/16/items';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
            $o = [
                clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
            ],
            null,
            [
                'stdClass' => [
                    'asset_id' => [
                        1,
                    ],
                    'duration' => [
                        10,
                    ],
                ],
            ],
            [
                'items' => [
                    $o[0],
                ],
            ],
            []
        ),
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/playlists/16/items'
payload = {
    "items": [
        {
            "asset_id": 1,
            "duration": 10
        }
    ]
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('PATCH', url, headers=headers, json=payload)
response.json()

Request      

PATCH api/v1/playlists/{playlist_id}/items

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

playlist_id   integer     

The ID of the playlist. Example: 16

playlist   integer     

The playlist ID. Example: 1

Body Parameters

items   string[]     

Array of item objects.

asset_id   integer     

Example: 16

duration   integer  optional    

Must be at least 1. Example: 22

Webhooks

Register endpoints to receive push notifications for events on your network.

Verifying webhook signatures

Every webhook delivery includes an X-Spot-Signature header. Verify it:

$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $webhookSecret);
if (!hash_equals($expected, $request->header('X-Spot-Signature'))) {
    abort(401);
}

List webhooks

requires authentication

Returns all registered webhooks. The secret is never returned after creation.

Example request:
curl --request GET \
    --get "http://localhost/api/v1/webhooks" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/webhooks';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/webhooks'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('GET', url, headers=headers)
response.json()

Example response (401):

Show headers
cache-control: no-cache, private
content-type: application/json
x-api-version: 1.2
access-control-allow-origin: *
 

{
    "message": "Unauthenticated."
}
 

Request      

GET api/v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Create webhook

requires authentication

Registers a new webhook endpoint. The secret is shown only once — store it securely.

Example request:
curl --request POST \
    "http://localhost/api/v1/webhooks" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"url\": \"https:\\/\\/example.com\\/webhooks\\/spot\",
    \"events\": [
        \"player.online\",
        \"player.offline\"
    ]
}"
const url = new URL(
    "http://localhost/api/v1/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "https:\/\/example.com\/webhooks\/spot",
    "events": [
        "player.online",
        "player.offline"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/webhooks';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'url' => 'https://example.com/webhooks/spot',
            'events' => [
                'player.online',
                'player.offline',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/webhooks'
payload = {
    "url": "https:\/\/example.com\/webhooks\/spot",
    "events": [
        "player.online",
        "player.offline"
    ]
}
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('POST', url, headers=headers, json=payload)
response.json()

Request      

POST api/v1/webhooks

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

The HTTPS URL to send events to. Example: https://example.com/webhooks/spot

events   string[]     

Events to subscribe to.

Delete webhook

requires authentication

Permanently removes a webhook registration.

Example request:
curl --request DELETE \
    "http://localhost/api/v1/webhooks/16" \
    --header "Authorization: Bearer {YOUR_API_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "http://localhost/api/v1/webhooks/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'http://localhost/api/v1/webhooks/16';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_API_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json

url = 'http://localhost/api/v1/webhooks/16'
headers = {
  'Authorization': 'Bearer {YOUR_API_KEY}',
  'Content-Type': 'application/json',
  'Accept': 'application/json'
}

response = requests.request('DELETE', url, headers=headers)
response.json()

Request      

DELETE api/v1/webhooks/{webhook_id}

Headers

Authorization        

Example: Bearer {YOUR_API_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

webhook_id   integer     

The ID of the webhook. Example: 16

webhook   integer     

The webhook ID. Example: 1