About
This document presents our Management API, its features and how to use them and their specifications.
This API enables developers to:
- create, share and transform medias,
- create, read, update and delete campaigns,
- read and update screens information.
The examples will use the requests Python library.
Display statistics
To know what was broadcasted on which screen and when, you can access our statistics API.
It provides raw detailed display data as well as elaborate aggregates over screens, campaigns and dates in real time.
Reference
See the reference for detailed information about all API views. It includes the technical description of requests, such as input parameters, expected formats, and detailed information about responses and their structures.
Authentication
The authentication mechanisms are the same across all our APIs.
There are 2 possible types of authentication for our APIs : JWT (JSON Web Token) and OAuth 2.0.
JWT
Create a token
Requesting a token requires executing a POST request on https://manage.cenareo.com/api/get_jwt_token/ with username and password parameters.
Example with curl:
curl --request POST \
--url https://manage.cenareo.com/api/get_jwt_token/ \
--header 'content-type: application/json' \
--data '{
"username": "username",
"password": "password"
}'
The response is a JSON containing the token.
{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwidXNlcl9pZCI6MSwiZW1haWwiOiJqYW1lc0BjaXR5bWVvLmZyIiwiZXhwIjoxNTMyNzEwMDQ1fQ.1iqpwY1U-zdRMKdnmMa3P-zJknxxgVVtoxrQUV4olDw"
}
Use the token
This token must then be sent in the Authorization header of subsequent requests.
If it expires, you can simply ask for a new one.
Example to get the list of your screens:
curl --request GET \
--url https://manage.cenareo.com/api/management/v2/screens \
--header 'Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImphbWVzIiwidXNlcl9pZCI6MSwiZW1haWwiOiJqYW1lc0BjaXR5bWVvLmZyIiwiZXhwIjoxNTMyNzEwMjgzfQ.swAJp-9OWXO2EFL1zKqbXenwhAVYoa56OyFMRw5GK90'
OAuth 2.0
Create an application
To authenticate via OAuth 2.0 you must first create an application linked to your account. To create one go to this page https://manage.cenareo.com/api/oauth/applications/:
- Click on New Application
- Enter an application name
- The fields Client id and Client secret are pre-filled with random values, but it is possible to manually set them.
- Choose Confidential for the Client type
- Choose Client credentials for the Authorization grant type
- Save
Create a token
Once you have created the application, you can ask for an authentication token. Our code examples will use the requests Python library.
import requests
data = {'grant_type': 'client_credentials'}
credentials = ('foo', 'bar') # ('<client_id>', '<client_secret>')
r = requests.post('https://manage.cenareo.com/api/oauth/token/', data=data, auth=credentials)
token_data = r.json()
token = token_data['access_token']
With the CLI:
# Via HTTPie (https://httpie.org/)
http --form 'https://manage.cenareo.com/api/oauth/token/' \
grant_type=client_credentials \
--auth client_id:client_secret
# via cURL
curl -X POST -d "grant_type=client_credentials" \
-u"client_id:client_secret" https://manage.cenareo.com/api/oauth/token/
The complete response from the server is in this format:
{
"access_token": "OknIg4rskTEDnmvXSPizcjNwMIFjWx",
"token_type": "Bearer",
"expires_in": 36000,
"scope": "read write groups"
}
The field expires_in indicates how long - in seconds - the token will be valid once emitted. You can however get a new token with the same method.
Use the token
From now on we will consider the token is stored in a variable named token. Here is an example to get the list of your screens.
Example to get the list of your screens:
# ...
headers = {'authorization': 'JWT {}'.format(token), 'content-type': 'application/json'}
r = requests.get('https://manage.cenareo.com/api/management/v2/screens/', headers=headers)
Or with the CLI:
# via HTTPie
http 'https://manage.cenareo.com/api/management/v2/screens/' "Authorization:JWT $token"
# via cURL
curl -X GET --header "Authorization: JWT $token" \
'https://manage.cenareo.com/api/management/v2/screens/'
Rate Limiting
The API enforces rate limits to ensure service stability. Exceeding these limits results in a 429 Too Many Requests response.
Endpoints Rate Limits
| Endpoint | Method | Daily Limit | Hourly Limit |
|---|---|---|---|
/screens/ |
GET | 100000 | 100000 |
/broadcasts/ |
POST | 50000 | 50000 |
/broadcasts/ |
PATCH | 50000 | 50000 |
/broadcasts/ |
DELETE | 50000 | 50000 |
/medias/ |
POST | 300 | 300 |
/medias/ |
GET/PATCH/DELETE | 200 | 200 |
/ads/ |
ALL | 20000 | 20000 |
| Other endpoints | ALL | 100000 | 100000 |
Response on Limit Exceeded
When a rate limit is exceeded, the API returns a 429 status with the following JSON:
{
"code": "throttled",
"message": "Request was throttled. Expected available in XXXX seconds."
}
Media
Create a media
All your different types of media are accessible on a single endpoint: /medias/. When creating a media of another type
than video you have to provide its display_time that describes for how long it will be displayed on screens.
Upload a file
Warning, for file uploads only, the headers must now use the content-type application/json.
headers = {'authorization': 'JWT %s' % TOKEN}
# Send a picture
with open('campagne-vacances.jpg', 'rb') as f:
file_data = {'file': f}
payload = {'display_time': 15}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/",
headers=headers, files=file_data, data=payload
)
# Send a video
with open('pub-soda.mp4', 'rb') as f:
file_data = {'file': f}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/",
headers=headers, files=file_data
)
Details of a media will always contain a type field with a value in picture, video or pdf. HTML pages are shown as picture.
Media generated with the content creation module and added to campaigns are accessible on /medias/ but it is not possible to generate them from the API.
Provide an URL
Instead of directly uploading a file, it is possible to create a media from an existing URL.
The content type is then deduced from the Content-Type header if present. For application/octet-stream content types, the first 1024 bytes of the file are analyzed.
If the Content-Type is text/html, the targeted media won't be downloaded, a screenshot will be taken instead, and the media type will be seen as picture.
headers = {'authorization': 'JWT %s' % TOKEN}
# Send a picture
payload = {'display_time': 15, 'url_origin': "https://url.to/myimage"}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/",
headers=headers, data=payload
)
# Send a video
payload = {'url_origin': "https://url.to/myvideo"}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/",
headers=headers, data=payload
)
Automatic updates
Our API offers the possibility to update media provided through a URL at regular intervals. The file type (image, video, pdf) returned by this URL must not change over time.
To use this feature you must provide the refresh_time field (in minutes). Our platform will then check every refresh_time for changes, if a change is detected the media is updated.
The ETag and Last-Modified headers are taken into account when checking for changes.
It is also possible to provide a validity duration using the validity_time field (in minutes). This value is used to determine how long a player continues to display a media after its last successful update. This means that if and an update fails - for example with an error 403, 404 or 502 - the media will not longer be displayed after the date of the last successful update + the validity duration.
headers = {'authorization': 'JWT %s' % TOKEN}
payload = {
'display_time': 15,
'url': "https://url.to/myfile",
# Check for update every 2 hours
'refresh_time': 120,
# Stop displaying the media on players disconnected
# for more than 4 hours
'validity_time': 240,
}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/",
headers=headers, data=payload
)
Share a Media (with External Storage)
In case your account has a linked external storage (Digital Asset Management: option available, contact us if you are interested), it is possible to upload a medium which will be available to all your users to create campaigns.
You can optionally specify the destination path where the media should be stored with folder. This path can only contain letters (lowercase, uppercase, no accents) and numbers, as well as the special characters: - _ and space, and the folders are separated by /, i.e : main-folder 2/medias/my_images
headers = {'authorization': 'JWT %s' % TOKEN}
# Share a picture with external storage path
with open('campagne-vacances.jpg', 'rb') as f:
file_data = {'file': f}
payload = {"folder": "my_folder/my-pictures"}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/file_to_external_storage/",
headers=headers, files=file_data, data=payload
)
Share an existing media
When a media already exists on our platform, you can make it available in your external storage by adding to its url /to_external_storage/ and making a request on the url thus obtained.
You can also specify a folder.
headers = {'authorization': 'JWT %s' % TOKEN}
# Share a picture with external storage path
payload = {"folder": "my_folder/my-pictures"}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/<media_uid>/to_external_storage/",
headers=headers, data=payload
)
Transform a media (cropping)
You can crop an existing media by adding to its url /transform/ and making a request on the url thus obtained. In the parameters of your request, specify the position of the resizing frame with the x and y positions ("north west" position, top left corner of the frame), as well as the dimensions of the frame.
The image will be cropped to the size of the frame according to the angle chosen.
headers = {'authorization': 'JWT %s' % TOKEN}
# Share a picture with external storage path
payload = {
"width": 100,
"height": 100,
"x": 20,
"y": 20,
"rotation": 45,
}
r = requests.post(
"https://manage.cenareo.com/api/management/v2/medias/<media_uid>/transform/",
headers=headers, data=payload
)
Campaigns
List campaigns
In order to list your campaigns, you can perform a get request on :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/ads/",
headers=headers
)
Also be aware that you can perform a search on:
- the campaign's id
- the campaign's name
- a campaign's screen name
- a campaign's screen slug
- a campaign's screen group name
by using the query parameter "search" like below :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/ads/?search=<your search pattern>",
headers=headers
)
The search pattern must be an exact match
List current campaigns
You can filter campaigns that are currently running by using the query parameter current=true.
A campaign is considered current if:
campaign_startdate is before or equal to today, and
campaign_enddate is either null or greater than or equal to today.
Note: Do not confuse this with the Ad status field:
status=activemeans the campaign is enabled,status=suspendedmeans the campaign is paused.
Example request
Assume we are on 2025-09-08.
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/ads/?current=true&fields=id,name,campaign_startdate,campaign_enddate",
headers=headers
)
Example response
{
"count": 5,
"next": null,
"previous": null,
"results": [
{
"url": "http://localhost/api/management/v2/ads/b9ebda67-a298-44e3-bf54-4680e1e6c8a2/",
"id": "b9ebda67-a298-44e3-bf54-4680e1e6c8a2",
"name": "Campaign A",
"campaign_startdate": "2025-09-05",
"campaign_enddate": "2025-09-17"
},
{
"url": "http://localhost/api/management/v2/ads/475f9691-842f-423d-acbf-a26d82d8f49f/",
"id": "475f9691-842f-423d-acbf-a26d82d8f49f",
"name": "Campaign B",
"campaign_startdate": "2025-09-01",
"campaign_enddate": null
}
]
}
Only campaigns satisfying the conditions above are included. Use current=false (or omit the parameter) to list all campaigns without this restriction.
Create a simple campaign
To link a campaign to its content and screens we use objects called broadcasts. These objects allow you to precisely associate a content and its screens. A multimedia campaign is simply a campaign with multiple broadcasts, each associated with a single content.
This is how broadcasts are displayed in the API :
{
"url":
"https://manage.cenareo.com/api/management/v2/broadcasts/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ad": "https://manage.cenareo.com/api/management/v2/ads/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"screens": [
"https://manage.cenareo.com/api/management/v2/screens/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
],
"order": 0,
"display_periods": {},
"media": "https://manage.cenareo.com/api/management/v2/medias/video-508/"
}
This is a complete Broadcast, as displayed on the /broadcasts endpoint. When creating a broadcast the only required fields are screens, media and ad, except if you create a broadcast at the same time as a campaign. When creating a campaign you can provide a list of broadcasts.
Here is the example of a POST request on the /ads endpoint that will create a campaign with only one content displayed on two screens:
{
"name": "Single content campaign",
"campaign_startdate": "2017-08-01",
"broadcasts": [
{
"media": "<url media>",
"screens": ["<url screen1>", "<url screen2>"]
}
]
}
It is possible to not specify any broadcast when creating a campaign and add them later using a PATCH request. Another method it to create broadcasts independently using POST requests on /broadcasts.
NB 1: Changing the broadcasts of a campaign can cause broadcasts to be deleted and created, which in turn can trigger downloads on the players.
NB 2: Once the campaign has been created or updated, its attribute screens will contain all the screens for its broadcasts. This attribute is not meant to be modified. To change the screens a campaign is displayed on, you should change its broadcasts (see below).
Create a multimedia campaign
To create a multimedia campaign, you need to create a campaign with multiple broadcasts, each with its own media.
Here's an example of payload to POST on /ads :
{
"name": "Campaign with 2 media",
"campaign_startdate": "2017-08-01",
"multimedia_type": "cloned",
"broadcasts": [
{
"media": "<url media1>",
"screens": ["<url screen1>", "<url screen2>"]
},
{
"media": "<url media2>",
"screens": ["<url screen1>", "<url screen2>"]
}
]
}
You can choose between 3 types of multimedia campaigns using the multimedia_type field:
playlistmeans that every media file will be read in the given order on every screen.
With this type, all broadcasts should have the same screens.clonedmeans that each media file will be considered like a separate campaign.
With this type, all broadcasts should have the same screens.stretchedmeans each media file must have different screens. The default value iscloned
Update a campaign
To update a campaign's screens or media files you can PATCH the broadcasts fields of the campaign.
Here's the campaign we will be updating (we consider it has already been created):
{
"name": "Example name",
"multimedia_type": "cloned",
"url": "https://manage.cenareo.com/api/management/v2/ads/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"broadcasts": [
{
"screens": [
"<url screen1>",
"<url screen2>"
],
"media": "<url media1>"
},
{
"screens": [
"<url screen3>"
],
"media": "<url media2>"
}
]
}
What we want to change:
- screen2 should broadcast media2 and no longer broadcast media1
- screen4, a new screen for this campaign, should broadcast media2 and media1
We will do a PATCH on the campaign's URL with this payload:
{
"broadcasts": [
{
"screens": [
"<url screen1>",
"<url screen4>"
],
"media": "<url media1>"
},
{
"screens": [
"<url screen2>",
"<url screen3>",
"<url screen4>"
],
"media": "<url media2>"
}
]
}
NB: When changing the broadcasts of a campaign, the new list replaces the old one. Please make sure to provide all broadcasts when changing the broadcasts of a campaign.
For a minor change to a broadcast you can patch the broadcast directly.
Create a solo or event campaign
To create Event or Solo campaign, use the campaign_type field:
- For a solo campaign:
{
"name": "Campagne média unique",
"campaign_startdate": "2017-08-01",
"campaign_type": "solo",
"..."
}
- For an event campaign:
{
"name": "Campagne média unique",
"campaign_startdate": "2017-08-01",
"campaign_type": "event",
"..."
}
See the reference for more detailed information.
Create an HTML campaign
To create an HTML campaign using screenshots, you must provide the additional information in the adupdater field of the campaign.
To specify the screens on which the screenshot should be broadcast, you must provide a single broadcast with no media.
Here's an example payload to POST on /ads:
{
"name": "Mon site",
"broadcasts": [
{
"screens": ["<url screen1>", "<url screen2>"]
}
],
"adupdater": {
"category": "screenshot",
"refresh_time": 3600,
"parameters": {
"url": "https://example.com",
"geometry": [1024, 768],
"duration": 20
}
}
}
This will create a campaign showing a screenshot of [https://example.com] for 20 seconds each time. The screenshot will be updated every 3600 seconds and will have a resolution of 1024x768.
See the reference for more information.
Create a share of voice campaign
Share of voice campaign are created as any other campaign. You must provide an author_origin value, so the platform knows on which quota it should be counted.
However, the platform will check that there is sufficient screen time quota available on the concerned screens.
It is also possible to create "sliding" campaigns. These campaigns will be displayed on a different screen every day, only one screen at a time and always in the same order. They must have at least 2 screens and last at least 2 days. This type of campaign allows for more campaigns with the same quota.
If the screen time quota is insufficient, an error will be returned to the related field.
Global errors and errors relative to multiple fields are returned to the __all__ field.
The response will contain 3 fields:
- code: the error code (see below)
- details: details about the error
- message: an HTML formatted error
The existing error codes are:
duration_error: the duration is too highquota_exceeded: the campaign would exceed the screen time quotas
The details field is a dictionary associating fields to the corresponding errors.
In the case of quota_exceeded error the details will have an additional quota key with a specific format:
{
"__all__": {
"message": [
"..."
],
"code": "quota_exceeded",
"details": {
"quota": {
"49040363-5108-4563-a29b-42138922438c": {
"duration": 50.0,
"date": "2018-02-06T14:19:34.635242+00:00",
"date_fmt": "6 february 2018 15:19:34",
"quota": 40.0,
"screen_name": "screen_1"
},
"c1547016-0226-42d4-b3f3-74fde1931674": {
"duration": 40.0,
"date": "2018-02-06T14:19:34.539759+00:00",
"date_fmt": "6 february 2018 15:19:34",
"quota": 30.0,
"screen_name": "screen_2"
}
}
}
}
}
In quota, each key is a screen id for which the quota is insufficient:
duration: how long the new loop would be with this campaign,quota: allocated time on that screen for the author's share of voice,date: date when the quota overflow would start,date_fmt: same asdatebut localized and formatted,screen_name: the screen's slug
Another error for the screens_not_enough error, triggered by a sliding campaign with 1 screen:
{
"screens": {
"message": "...",
"code": "screens_not_enough",
"details": {
"screens": "sliding campaigns must have at least 2 screens"
}
}
}
The screen time quotas are available on the screens API endpoint, under the field allocated_times.
NB : Share of voice campaigns can not have multiple contents.
Giving the campaign to another user
When creating a share of voice campaign, you can "give" it to another user after creation. The new author must have access to the campaign's share of voice.
NB: Giving a campaign is an irreversible operation.
Delete a campaign
Send a DELETE request on that ads url:
# Url of the ad we want to delete
ad_url = "https://manage.cenareo.com/api/management/v2/ads/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Build headers, payload and send the request
headers = {'content-type': 'application/json', 'authorization': 'JWT %s' % TOKEN}
r = requests.delete(ad_url, headers=headers)
Campaign advanced parameters
A campaign's advanced parameters can be edited via the API:
# Url of the ad we want to change
ad_url = "https://manage.cenareo.com/api/management/v2/ads/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Build headers, payload and send the request
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Here we want the ad to be displayed on-demand only:
payload = {"main_loop": False, "on_demand": True}
r = requests.patch(ad_url, headers=headers, data=json.dumps(payload))
Create a campaign with screens loop indexes
In the following example, the broadcast will be shown in the positions 0, 4 and 8 of the loop of screen xxx and in the positions 1, 5 and 10 for screen yyy. Loop indexes with no associated broadcasts for a screen are skipped.
{
"name": "My campaign",
"broadcasts": [
{
"screens_loop_indexes": {
"https://manager.cenareo.com/api/management/v2/screens/xxx": [1, 5, 10],
"https://manager.cenareo.com/api/management/v2/screens/yyy": [0, 4, 8],
},
"screens": [
"https://manager.cenareo.com/api/management/v2/screens/xxx",
"https://manager.cenareo.com/api/management/v2/screens/yyy"
],
"media": "https://manager.cenareo.com/api/management/v2/media/xxx"
}
]
}
NB : If a loop index is missing for a screen, is interpreted as [+∞].
If multiple broadcasts have the same loop index, an order not configurable and inherent to the medias is chosen.
Broadcasts
List broadcasts
To list all your broadcasts:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/broadcasts/",
headers=headers
)
You can search broadcasts by:
- broadcast's id
- ad's id
by using the query parameter search:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/broadcasts/?search=<your search pattern>",
headers=headers
)
The search pattern must be an exact match.
Create a broadcast
To create a broadcast, you need to provide the ad (campaign) it belongs to, the media (content) it will display and the list of screens on which it will be displayed.
Example POST request:
{
"ad": "<ad_url>",
"media": "<media_url>",
"screens": ["<screen1_url>", "<screen2_url>"]
}
You can create broadcasts independently or at the same time as creating a campaign.
Response example:
{
"url": "https://manage.cenareo.com/api/management/v2/broadcasts/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ad": "https://manage.cenareo.com/api/management/v2/ads/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
"screens": [
"https://manage.cenareo.com/api/management/v2/screens/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
],
"order": 0,
"display_periods": {},
"media": "https://manage.cenareo.com/api/management/v2/medias/video-508/"
}
Retrieve a broadcast
Send a GET request to the broadcast URL:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/broadcasts/<broadcast_id>/",
headers=headers
)
Update a broadcast
Send a PATCH request to the broadcast URL:
{
"screens": ["<new_screen_url1>", "<new_screen_url2>"]
}
Only allowed if you can edit the broadcast's ad.
Delete a broadcast
Send a DELETE request to the broadcast URL:
broadcast_url = "https://manage.cenareo.com/api/management/v2/broadcasts/<broadcast_id>/"
headers = {
'content-type': 'application/json',
'authorization': 'JWT %s' % TOKEN
}
r = requests.delete(broadcast_url, headers=headers)
Only allowed if you can edit the broadcast's ad.
Screens
List screens
See the screens reference for detailed information about screens.
In order to list your screens, you can perform a get request on :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/",
headers=headers
)
If you want to search for a specific screen that you know the id : xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, you can perform a get request :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
headers=headers
)
Search, filter, paginate and select specific fields for screens
Search fields
You can perform a search on the following screen attributes using the search query parameter.
This allows a partial match search, meaning you can retrieve all screens containing the search term in the specified fields.
Searchable Fields:
idnameslugplayers__barcodeplayers__sticker
For example, to search for a screen with the name My screen :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?search=My%20screen",
headers=headers
)
replace the space between two words with %20 in the request
This will return all screens where "My Screen" appears anywhere in the name, slug, barcode, or sticker fields.
Filter fields
Filtering allows for exact matching on specific attributes using query parameters.
Available filters :
id: Filter screens by exact IDname: Filter screens by exact nameslug: Filter screens by exact slugbarcode: Filter screens by exact player barcodesticker: Filter screens by exact player stickeractive: Filter screens based on activation status (true/false)deactivation_reason: Filter screens by deactivation reasondisplay_is_a_led_panel: Filter screens that are LED panels (true/false)display_is_an_epaper: Filter screens that are E-Paper (true/false)remote: Filter screens where remote access is enabled (true/false)full_address: Filter screens by full addressstreet_number: Filter screens by street numberaddress: Filter screens by addresspostalcode: Filter screens by postal codecity: Filter screens by citycounty: Filter screens by countyregion: Filter screens by regioncountry: Filter screens by countrygoogle_place_id: Filter screens by Google Place IDcustom_slug: Filter screens by exact custom_slugonline_modified_date__gt: Filter screens whose online status changed after a given datetimeonline_modified_date__gte: Filter screens whose online status changed at or after a given datetimeonline_modified_date__lt: Filter screens whose online status changed before a given datetimeonline_modified_date__lte: Filter screens whose online status changed at or before a given datetime
Here some examples of filters.
Filter by exact name: For example, to search for a screen with the name "My screen" :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?name=My%20screen",
headers=headers
)
Filter by active status:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?active=true",
headers=headers
)
Filter by LED Panel screens:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?display_is_a_led_panel=true",
headers=headers
)
Filter by E-paper screens:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?display_is_an_epaper=true",
headers=headers
)
Filter by city:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?city=Paris",
headers=headers
)
Filter by multiple barcodes: You can filter by a single barcode or multiple barcodes by providing a comma-separated list.
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?barcode=XXXXXXXXXXXXX1,XXXXXXXXXXXXX2,XXXXXXXXXXXXX3",
headers=headers
)
Filter by multiple stickers: You can filter by a single sticker or multiple stickers by providing a comma-separated list.
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?sticker=XXXXXXXXXXXXX1,XXXXXXXXXXXXX2,XXXXXXXXXXXXX3",
headers=headers
)
Combining filters: You can filter with more than one fields. For example, if you want to filter the screens that are in Paris and that are active, you can perform this request:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?city=Paris&active=true",
headers=headers
)
Filter by online status change date: Use onlinemodifieddate comparison filters to retrieve screens whose connection status changed within a specific time window. This is useful to avoid re-fetching all screens and only retrieve those with recent connectivity changes. Datetimes must be in ISO 8601 format (e.g. 2026-01-22T12:59:39Z). Note that : must be URL-encoded as %3A.
pythonheaders = {'authorization': 'JWT %s' % TOKEN}
# Screens whose online status changed after a given datetime
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?online_modified_date__gt=2026-01-22T12%3A59%3A39Z",
headers=headers
)
python# Screens whose online status changed within a time window
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?online_modified_date__gte=2026-01-22T00%3A00%3A00Z&online_modified_date__lte=2026-01-22T23%3A59%3A59Z",
headers=headers
)
Encoding special characters in queries: When using query parameters, certain characters must be URL-encoded:
- Spaces (
" ") should be replaced with%20 - Commas (
",") should be replaced with%2C - Colons (
":") should be replaced with%3A
Paginate response
Limiting the number of results:
By default, the API returns a paginated list of screens. You can control the number of screens returned per request using the limit parameter:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?limit=10",
headers=headers
)
This request will return only 10 screens.
Using offset for pagination:
If you need to paginate through screens, you can combine limit and offset:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?limit=5&offset=10",
headers=headers
)
limit=5→ Returns 5 screens per pageoffset=10→ Skips the first 10 screens and returns the next 5
Select specific fields
By default, the API returns a full screen object. If you only need certain fields, use the fields parameter:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?fields=name,slug",
headers=headers
)
This request will return only the name and slug fields for each screen.
You can combine this with filtering and pagination:
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screens/?active=true&fields=name,slug&limit=10",
headers=headers
)
- Returns only active screens
- Includes only
nameandslug - Limits the response to 10 screens
Manage Screen Characteristics
Edit screen's name
Here's an example of request to update the name of a screen:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# New name of the screen
payload = {"name": "New screen name"}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
Edit screen's opening hours
Here's an example of request to update the opening hours of a screen:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Example of opening hours. The screen will be ON:
# - 9:00 to 18:00 on Monday, Tuesday and Wednesday
# - 9:00 to 12:00, 14:00 to 18:00 on Thursday and Friday
# - Whole day on Saturday and Sunday
payload = {
"opening_hours": {
"1": [["09:00", "18:00"]],
"2": [["09:00", "18:00"]],
"3": [["09:00", "18:00"]],
"4": [["09:00", "12:00"],["14:00","18:00"]],
"5": [["09:00", "12:00"],["14:00","18:00"]],
"6": [],
"7": [],
}
}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
To reset a screen's opening hours, simply provide an empty object:
{
"opening_hours": {}
}
NB: The download_hours and opening_hours of a screen use the same format.
Edit screen's active status
You can change the active status of a screen by updating the active field.
If you want to deactivate a screen, you can provide a deactivation_reason, which is a string among the following values:
stockto_replacereplacementhas_been_replacedlostmaintenancesite_is_closed
Here's an example of request to change an active screen to inactive with a deactivation reason:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Deactivation reasons :
# deploy, has_been_replaced, lost, maintenance, replacement, site_is_closed, stock, to_replace
payload = {
"active": False,
"deactivation_reason": "maintenance"
}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
In the opposite, to change an inactive screen to active, simply provide the payload (deactivation_reason must be null):
payload = {
"active": True,
"deactivation_reason": None
}
Enable screen's LED Panel feature
Here's an example of request to set a screen to be a LED Panel:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Set the LED Panel feature to True
payload = {
"display_is_a_led_panel": True
}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
Enable screen's E-Paper feature
Here's an example of request to set a screen to be an E-Paper:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Set the E paper feature to True
payload = {
"display_is_an_epaper": True
}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
Edit screen's place
Here's an example of request to set a place for a screen:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Specify the url of the place you want to assign to your screen
payload = {
"place": "https://manage.cenareo.com/api/management/v2/places/xxxxx/"
}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
Edit screen's custom_slug
Here's an example of request to set a custom_slug for a screen:
# Url of the screen you want to update
screen_url = "https://manage.cenareo.com/api/management/v2/screens/xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/"
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Specify the custom slug you want to assign to your screen
payload = {
"custom_slug": "My custom slug"
}
# PATCH request
r = requests.patch(screen_url, headers=headers, data=json.dumps(payload))
The custom_slug field is optional, but must be unique across all screens in the platform.
If you try to assign a custom_slug that is already used by another screen, the request will fail with a 400 Bad Request error and the following message:
{
"custom_slug": [
{
"message": "This custom slug is already used by another screen.",
"code": "invalid"
}
]
}
Edit the allocated times
If your fleet has share of voice configured, you can modify the distribution of screen time between different advertiser types using the allocated_times field. This field allows you to define how much time (in seconds) is allocated to each advertiser type.
Keys can be specified in two ways:
- Numeric: using advertiser type IDs (1 = Lessor, 2 = Advertiser)
- Text: using advertiser type names ("lessor", "advertiser")
Advertiser type correspondences:
1or"lessor": Lessor2or"advertiser": Advertiser3or"agency": Agency- ...
Example with numeric keys:
headers = {'authorization': 'JWT %s' % TOKEN}
payload = {
"allocated_times": {
"1": 30, # 30 seconds for lessors
"2": 20 # 20 seconds for advertisers
}
}
r = requests.patch(
"https://manage.cenareo.com/api/management/v2/screens/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
headers=headers,
json=payload
)
Example with text keys:
headers = {'authorization': 'JWT %s' % TOKEN}
payload = {
"allocated_times": {
"lessor": 30, # 30 seconds for lessors
"advertiser": 20 # 20 seconds for advertisers
}
}
r = requests.patch(
"https://manage.cenareo.com/api/management/v2/screens/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/",
headers=headers,
json=payload
)
Important Notes:
allocated_timescan only be modified if your fleet has share of voice enabled- Only advertiser types available in your fleet can be used
- Durations are expressed in seconds
- Text keys are not Case-insensitive ("Lessor", "LESSOR", "lessor" are equivalent)
In case of error, you will receive an error message indicating the available advertiser types:
{
"allocated_times": [
{
"message": "Invalid author type 'agency'. Valid choices are: Lessor, Advertiser or 1, 2",
"code": "invalid"
}
]
}
or
{
"allocated_times": [
{
"message": "You don't have any share of voice",
"code": "invalid"
}
]
}
Use the remote
There are two ways to access remote URLs through the API:
- Get the URLs to the remote page of our web UI on
/screens/{id}/remote_url/. See here for the documentation, - Get the URLs to directly display a campaign on its compatible screens on
/ads/{id}/remote_call_urls/. See here for the documentation.
Manage place
The system allows fleets to manage places, which represent physical locations where screens are installed. Each place can be shared among multiple fleets or be private to a single fleet depending on its configuration.
Public vs Exclusive (Private) Places
There are two types of places: public and exclusive (private).
A public place is shared across multiple fleets. It cannot be modified or deleted, as screens from other fleets may be attached to it. These places are typically created and maintained automatically based on shared location data, and they are immutable to prevent conflicts between users.
An exclusive place (also referred to as a private place) belongs to a single fleet and can be freely modified or deleted by users from that fleet.
These places are identified by setting the exclusive_fleet field during creation.
Because they are private, only your screens can be linked to them, and you can safely customize them without impacting others.
You may also provide a private_fleet_key to attach custom metadata specific to your fleet’s usage.
List places
To list the places of your screens, you can perform a get request on :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/places/",
headers=headers
)
You will get a list of all your places, with the following fields for each place:
nameaddress,street_number,city,postalcode,country,county,regionlocation(with latitude and longitude)google_place_id- Optional:
exclusive_fleetandprivate_fleet_keyif the place is exclusive to your fleet.
Create a public place
Here's an example of request to create a new place.
The required fields are street_number, address, postalcode, and city:
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Set the four fields required
payload = {
"street_number": "xx",
"address": "xxxx xxxxxxxxxxxxxxxxx xxxx",
"postalcode": "xxxxx",
"city": "xxxxxxxxx"
}
# POST request
r = requests.post("https://manage.cenareo.com/api/management/v2/places/",headers=headers, data=json.dumps(payload))
You can assign a custom name to a place by adding the name field in the payload. If the name field is not provided, the place name will default to the Google Maps place name or, if unavailable, the provided address.
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# Set the four fields required with your custom name
payload = {
"street_number": "xx",
"address": "xxxx xxxxxxxxxxxxxxxxx xxxx",
"postalcode": "xxxxx",
"city": "xxxxxxxxx",
"name": "My Favorite Place"
}
# POST request
r = requests.post("https://manage.cenareo.com/api/management/v2/places/",headers=headers, data=json.dumps(payload))
Create a private place (exclusive to your fleet)
To create a place exclusively associated with your fleet, include the exclusive_fleet field with the ID of your fleet.
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
payload = {
"street_number": "xx",
"address": "xxxx xxxxxxxxxxxxxxxxx xxxx",
"postalcode": "xxxxx",
"city": "xxxxxxxxx",
"name": "My Favorite Place",
"exclusive_fleet": FLEET_ID, # Replace with your actual fleet ID
"private_fleet_key": "my-fleet-custom-key",
}
# POST request
r = requests.post("https://manage.cenareo.com/api/management/v2/places/",headers=headers, data=json.dumps(payload))
Only users with access to the specified fleet can create exclusive places. If a place with the same address already exists for that fleet, it will be reused. Otherwise, a new one is created.
Update a private place
Only places created with an exclusive_fleet can be updated.
To update such a place, you must include the same exclusive_fleet ID in your PATCH request, otherwise it will be rejected.
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
payload = {
"exclusive_fleet": FLEET_ID,
"city": "Lyon",
"address": "Rue de la République",
"postalcode": "69002",
"street_number": "20"
}
r = requests.patch(
f"https://manage.cenareo.com/api/management/v2/places/{place_id}/",
headers=headers,
data=json.dumps(payload)
If your update results in a duplicate of an existing place already associated with your fleet, the update will be rejected and you will receive the existing place in the response.
If you're only updating the name or the private_fleet_key, the process is lighter and skips address validation:
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
payload = {
"exclusive_fleet": FLEET_ID,
"name": "New Name",
"private_fleet_key": "new-key"
}
# PATCH request
r = requests.patch(
f"https://manage.cenareo.com/api/management/v2/places/{place_id}/",
headers=headers,
data=json.dumps(payload))
Delete a private place
Only places created with an exclusive_fleet can be deleted, and only if no screen is attached to the place.
The deletion request must include the matching exclusive_fleet ID:
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
payload = {
"exclusive_fleet": FLEET_ID
}
# DELETE request
r = requests.delete(f"https://manage.cenareo.com/api/management/v2/places/{place_id}/",headers=headers, data=json.dumps(payload))
If the place has screens attached, the deletion will fail.
Filter places
You can filter the list of places using various fields by providing query parameters in your GET request.
The supported filters include:
name: partial match on the place name (case-insensitive)full_address: exact match on the full addressaddress,city,country,county,region: exact match on each corresponding fieldgoogle_place_id: exact matchprivate_fleet_key: exact match on your custom key (useful to retrieve your own exclusive places)exclusive_fleet: filter places tied to a specific fleet ID
Example usage:
# Headers, with authentication token
headers = {"content-type": "application/json", "authorization": "JWT %s" % TOKEN}
# GET request
r = requests.get(
"https://manage.cenareo.com/api/management/v2/places/?city=Toulouse&exclusive_fleet=123",
headers=headers
)
This would return all places located in Toulouse that are exclusive to fleet ID 123.
Note: Filtering on exclusive_fleet or private_fleet_key is especially useful to retrieve only the places created by your fleet, particularly when managing private places across multiple fleets.
List groups of your screens
To list the groups of your screens, you can perform a get request on :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screen_groups/",
headers=headers
)
You will get a list of all the groups of your screens, with the name, the description and the screens of each group.
List screen's type of your screens
To list the type of your screens, you can perform a get request on :
headers = {'authorization': 'JWT %s' % TOKEN}
r = requests.get(
"https://manage.cenareo.com/api/management/v2/screen_types/",
headers=headers
)
You will get a list of all the types of your screens, with the width, the height and the orientation.