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=active means the campaign is enabled,

  • status=suspended means 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:

  • playlist means that every media file will be read in the given order on every screen.
    With this type, all broadcasts should have the same screens.
  • cloned means that each media file will be considered like a separate campaign.
    With this type, all broadcasts should have the same screens.
  • stretched means each media file must have different screens. The default value is cloned

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 high
  • quota_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 as date but 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:

  • id
  • name
  • slug
  • players__barcode
  • players__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 ID
  • name : Filter screens by exact name
  • slug : Filter screens by exact slug
  • barcode : Filter screens by exact player barcode
  • sticker : Filter screens by exact player sticker
  • active : Filter screens based on activation status (true/false)
  • deactivation_reason : Filter screens by deactivation reason
  • display_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 address
  • street_number : Filter screens by street number
  • address : Filter screens by address
  • postalcode : Filter screens by postal code
  • city : Filter screens by city
  • county : Filter screens by county
  • region : Filter screens by region
  • country : Filter screens by country
  • google_place_id : Filter screens by Google Place ID
  • custom_slug : Filter screens by exact custom_slug
  • online_modified_date__gt : Filter screens whose online status changed after a given datetime
  • online_modified_date__gte : Filter screens whose online status changed at or after a given datetime
  • online_modified_date__lt : Filter screens whose online status changed before a given datetime
  • online_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 page
  • offset=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 name and slug
  • 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:

  • stock
  • to_replace
  • replacement
  • has_been_replaced
  • lost
  • maintenance
  • site_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:

  • 1 or "lessor": Lessor
  • 2 or "advertiser": Advertiser
  • 3 or "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_times can 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:

  • name
  • address, street_number, city, postalcode, country, county, region
  • location (with latitude and longitude)
  • google_place_id
  • Optional: exclusive_fleet and private_fleet_key if 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 address
  • address, city, country, county, region: exact match on each corresponding field
  • google_place_id: exact match
  • private_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.