This document presents our Management API, its features and how to use them and their specifications.
This API enables developers to:

  • create, read, update and delete campaigns,
  • read and update screens information.

The examples will use the requests Python library.

Reference

See the reference for detailed information about all the API views.

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

Creating 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"
}

Using 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

Creating 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

Creating 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.

Using 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/'

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.

Feeds

To manage feeds, you can access our Feeds API.

The feeds service allows you to create dynamic templates based on external data such as APIs.

Creating 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.

Uploading 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.

Providing a 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
)

Sharing 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
    )

sharing 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
)

How to 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

Creating 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).

Creating 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

Updating 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.

Creating 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.

Creating 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.

Creating 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: https://manage.cenareo.com/api/management/v2/screens/.

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.

Deleting 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))

Listing and editing screens

See the screens reference for detailed information about screens.

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.

Creating 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/screens/xxx": [1, 5, 10],
        "https://manager.cenareo.com/api/management/screens/yyy": [0, 4, 8],
      },
      "screens": [
        "https://manager.cenareo.com/api/management/screens/xxx",
        "https://manager.cenareo.com/api/management/screens/yyy"
      ],
      "media": "https://manager.cenareo.com/api/management/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.

Using 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.