-
Notifications
You must be signed in to change notification settings - Fork 26
This API documentation is based on reverse engineering of the Android app. You can use a tool like mitmproxy to intercept the HTTPS traffic.
See Authentication API for an in depth description.
The VTM GO API contains the data you need to browse the catalog and keeps track of what you've watched.
It has two endpoints you can use: https://api.vtmgo.be/vtmgo
and https://api.vtmgo.be/vtmgo-kids
.
It is important to specify the right HTTP headers, or the API will not give a correct reply.
Header | Value |
---|---|
x-app-version | 8 |
x-persgroep-mobile-app | true |
x-persgroep-os | android |
x-persgroep-os-version | 23 |
x-dpp-jwt | jwt token |
The config is used by the Mobile App to fetch some settings, we don't need this.
- GET
https://api.vtmgo.be/config
Example response
{
"liveRefreshMinutes": 60,
"minimumVersion": {
"major": 5,
"minor": 0
},
"player": {
"updateIntervalSeconds": 10
},
"siteSectionId": {
"android_phone": "mdl_vtmgo_phone_android_default",
"android_phone_kids": "mdl_vtmgokids_phone_android_default",
"android_tablet": "mdl_vtmgo_tablet_android_default",
"android_tablet_kids": "mdl_vtmgokids_tablet_android_default",
"ios_phone": "mdl_vtmgo_phone_ios_default",
"ios_phone_kids": "mdl_vtmgokids_phone_ios_default",
"ios_tablet": "mdl_vtmgo_tablet_ios_default",
"ios_tablet_kids": "mdl_vtmgokids_tablet_ios_default"
}
}
The main panel is constructed from highlighted content. These are used to personalize your suggestions.
- GET
https://api.vtmgo.be/{endpoint}/main
Example response
{
"abroad": false,
"accountInfo": {
"familyName": "***",
"givenName": "***",
"name": "***"
},
"rows": [
{
"id": "carousel",
"rowType": "CAROUSEL",
"teasers": [
{
"bannerAltText": null,
"largeImageUrl": "https://images0.persgroep.net/rcs/WPWy963bSv-_Fvqj7zS2-wP_ibE/diocontent/151900468/_fitwidth/2400?appId=038a353bad43ac27fd436dc5419c256b&quality=0.9",
"mediumImageUrl": "https://images4.persgroep.net/rcs/4kI4pGtTmf13WwIUBEcjFynLqBc/diocontent/151900467/_fitwidth/1440?appId=038a353bad43ac27fd436dc5419c256b&quality=0.9",
"mobileImageUrl": "https://images2.persgroep.net/rcs/GPBUYSdaNtEDIufoTAAcF2mxN0g/diocontent/151900469/_fitwidth/768?appId=038a353bad43ac27fd436dc5419c256b&quality=0.9",
"primaryImageUrl": "https://images0.persgroep.net/rcs/WPWy963bSv-_Fvqj7zS2-wP_ibE/diocontent/151900468/_fitwidth/2400?appId=038a353bad43ac27fd436dc5419c256b&quality=0.9",
"tagline": null,
"target": {
"id": "7c4a0659-7fde-4fd9-a745-64b1861cc8ce",
"name": "Vikings",
"type": "PROGRAM"
},
"title": "vikings",
"visualOnly": true
}
/* ... */
]
},
{
"id": "3",
"logoUrl": null,
"metaData": {
"abGroup": "4",
"provider": "avidci",
"requestId": "46c54665-3b23-4741-a1b8-c27a77040c4a",
"routingGroup": "5"
},
"rowType": "SWIMLANE_DEFAULT",
"teasers": [
{
"geoBlocked": false,
"imageUrl": "https://images4.persgroep.net/rcs/wrfNUDSaSv7CTm2uM51DEfAffic/diocontent/150279314/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"primaryImageUrl": "https://images4.persgroep.net/rcs/wrfNUDSaSv7CTm2uM51DEfAffic/diocontent/150279314/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"target": {
"id": "5311df7c-60e5-459d-b768-acdf269a7196",
"name": "Lili en Marleen",
"type": "PROGRAM"
},
"title": "Lili en Marleen",
"type": "SCREEN"
}
/* ... */
],
"title": "Aanbevolen voor jou"
},
{
"id": "790c3d91-b04b-4459-a177-55983efcdf09",
"logoUrl": null,
"metaData": {
"abGroup": "4",
"provider": "marketing",
"requestId": "46c54665-3b23-4741-a1b8-c27a77040c4a",
"routingGroup": "5"
},
"rowType": "SWIMLANE_DEFAULT",
"teasers": [
{
"geoBlocked": false,
"imageUrl": "https://images4.persgroep.net/rcs/sW83PV5vBmBWHeI9v9ylA2J1Cco/diocontent/147689313/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"primaryImageUrl": "https://images4.persgroep.net/rcs/sW83PV5vBmBWHeI9v9ylA2J1Cco/diocontent/147689313/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"target": {
"id": "5b0164b8-e851-46fc-b9f0-959d2eae08ce",
"name": "Blind Getrouwd - Australië",
"type": "PROGRAM"
},
"title": "Blind Getrouwd - Australië",
"type": "SCREEN"
}
/* ... */
],
"title": "Te warm? Bingen maar! "
},
{
"id": "12",
"logoUrl": null,
"metaData": {
"abGroup": "4",
"provider": "avidci",
"requestId": "46c54665-3b23-4741-a1b8-c27a77040c4a",
"routingGroup": "5"
},
"rowType": "SWIMLANE_DEFAULT",
"teasers": [
{
"geoBlocked": false,
"imageUrl": "https://images4.persgroep.net/rcs/wrfNUDSaSv7CTm2uM51DEfAffic/diocontent/150279314/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"primaryImageUrl": "https://images4.persgroep.net/rcs/wrfNUDSaSv7CTm2uM51DEfAffic/diocontent/150279314/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"target": {
"id": "5311df7c-60e5-459d-b768-acdf269a7196",
"name": "Lili en Marleen",
"type": "PROGRAM"
},
"title": "Lili en Marleen",
"type": "SCREEN"
}
/* ... */
],
"title": "Vlaamse Series"
},
{
"id": "13",
"logoUrl": "https://api.vtmgo.be/static/images/logos/walter-presents-d432b68d456ff9457b261fb393653af9.png",
"metaData": {
"abGroup": "4",
"provider": "avidci",
"requestId": "46c54665-3b23-4741-a1b8-c27a77040c4a",
"routingGroup": "5"
},
"rowType": "SWIMLANE_DEFAULT",
"teasers": [
{
"geoBlocked": false,
"imageUrl": "https://images0.persgroep.net/rcs/n_XD5Nv9T3m0f7-Jc06YaujUKrg/diocontent/151620188/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"primaryImageUrl": "https://images0.persgroep.net/rcs/n_XD5Nv9T3m0f7-Jc06YaujUKrg/diocontent/151620188/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"target": {
"id": "2e1b7d82-7a94-46f3-b33b-0a97755fca80",
"name": "Fugitives",
"type": "PROGRAM"
},
"title": "Fugitives",
"type": "SCREEN"
}
/* ... */
],
"title": "Exclusief: Walter Presents"
}
/* ... */
]
}
The catalog can be requested and filters can be applied. The filters are passed with query parameters.
You can specify page
and pageSize
for pagination. You can also filter based on a category with the filter
parameter. If you don't specify a filter, it will show all items.
You can get the filters by using the following endpoint:
- GET
https://api.vtmgo.be/{endpoint}/catalog/filters
Example response
{
"catalogFilters": [
{
"active": false,
"id": "films",
"title": "Films"
},
{
"active": false,
"id": "vlaamse-series",
"title": "Vlaamse Series"
},
{
"active": false,
"id": "kids",
"title": "Kids"
},
{
"active": false,
"id": "exclusief-walter-presents",
"title": "Exclusief: Walter Presents"
},
{
"active": false,
"id": "entertainment",
"title": "Entertainment"
},
{
"active": false,
"id": "internationale-series",
"title": "Internationale Series"
},
{
"active": false,
"id": "nieuws-actua",
"title": "Nieuws & Actua"
},
{
"active": false,
"id": "humor",
"title": "Humor"
},
{
"active": false,
"id": "reality",
"title": "Reality"
},
{
"active": false,
"id": "lifestyle",
"title": "Lifestyle"
},
{
"active": false,
"id": "teens",
"title": "Teens"
}
]
}
You can get the content of the catalog by using this endpoint:
- GET
https://api.vtmgo.be/{endpoint}/catalog?filter=films&page=1&pageSize=20
Example response
{
"abroad": false,
"accountInfo": {
"familyName": "***",
"givenName": "***",
"name": "***"
},
"filters": [
{
"active": true,
"id": "films",
"title": "Films"
}
/* ... */
],
"pagedTeasers": {
"content": [
{
"geoBlocked": false,
"imageUrl": "https://images0.persgroep.net/rcs/u_GhwuGjMO3p6QIoEYOdZjhta2g/diocontent/151392171/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"primaryImageUrl": "https://images0.persgroep.net/rcs/u_GhwuGjMO3p6QIoEYOdZjhta2g/diocontent/151392171/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"target": {
"assetType": "movie",
"contextIdType": "video",
"id": "d0e898a8-5a54-4ef1-aa2c-a094cb91f301",
"name": "40 Below and Falling",
"playableId": "d0e898a8-5a54-4ef1-aa2c-a094cb91f301",
"serviceType": "context",
"type": "MOVIE"
},
"title": "40 Below and Falling",
"type": "SCREEN"
}
/* ... */
],
"first": true,
"last": false,
"numberOfElements": 20,
"pageNumber": 1,
"totalElements": 39,
"totalPages": 2
}
}
The pagedTeasers.content[x].target.type
can be MOVIE
or PROGRAM
. You need the pagedTeasers.content[x].target.playableId
to identify the item in the next steps.
When the content type is MOVIE
, you can use this API to get more information about the movie.
- GET
https://api.vtmgo.be/{endpoint}/movies/a123b4c0-0846-4145-83b7-f26052d0984a
Example response
{
"abroad": false,
"accountInfo": {
"familyName": "***",
"givenName": "***",
"name": "***"
},
"movie": {
"addedToMyList": false,
"assetType": "movie",
"bigPhotoUrl": "https://images0.persgroep.net/rcs/1F8wKpn7BZPxfo3r-MMJA04YsgI/diocontent/150941631/_fitwidth/1400?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"broadcastTimestamp": "2019-06-22T18:25:00Z",
"channelLogoUrl": "https://api.vtmgo.be/static/images/logos/vitaya-412f405c5dbc13e88844d8ba20e2e611.png",
"contextIdType": "video",
"description": "De vijftienjarige Deborah wil ontsnappen uit de verstikkende alledaagsheid van Linkeroever. Samen met de achttienjarige Jennifer duikt ze het Antwerpse nachtleven in. Wanneer ze ontdekt dat Jennifer als escorte werkt, is Deborah geprikkeld door wat voor haar een eenvoudige weg naar geld lijkt. Onder de naam Bo maakt ze kennis met een wereld waar ze duidelijk niet klaar voor is.",
"durationSeconds": 5366,
"geoBlocked": false,
"id": "a123b4c0-0846-4145-83b7-f26052d0984a",
"legalIcons": [
"PG16"
],
"mediumPhotoUrl": "https://images4.persgroep.net/rcs/ZsO9GDku4JG42Zzkjm6rjAjuiyw/diocontent/150941631/_fitwidth/1280?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"name": "Bo",
"playableId": "a123b4c0-0846-4145-83b7-f26052d0984a",
"playerPositionSeconds": 0,
"primaryPlatform": "VTM_GO",
"productionYear": 2009,
"remainingDaysAvailable": 16,
"serviceType": "context",
"smallPhotoUrl": "https://images0.persgroep.net/rcs/IZAymOiWkl0p6TA5Bcdnse_9620/diocontent/150941631/_fitwidth/426?appId=038a353bad43ac27fd436dc5419c256b&quality=0.8",
"watching": false
}
}
TODO
TODO
The app stores it's current playing position every 10 seconds. This allows you to continue on the same point on a different device.
- PUT
https://api.vtmgo.be/{endpoint}/userData/playerPosition/MOVIE/a123b4c0-0846-4145-83b7-f26052d0984a
Example request
{
"duration": 5362,
"position": 1
}
The Anvato Streaming API is used to request a stream to watch. You need a stream type and a stream id to select the stream you want. You can get this information from the VTM GO API.
First, you need to get stream info.
It is important to specify the right HTTP headers, or the API will not give a correct reply.
Header | Value |
---|---|
x-api-key | zTxhgTEtb055Ihgw3tN158DZ0wbbaVO86lJJulMl |
Popcorn-SDK-Version | 1 |
- GET
https://videoplayer-service.api.persgroep.cloud/config/movies/a123b4c0-0846-4145-83b7-f26052d0984a?startPosition=0.0&autoPlay=true
Example response
{
"video": {
"ads": {
"freewheel": {
"assetId": "************************************_vod",
"networkId": "******",
"profileId": "******:medialaan_VTMGO_SSAI_android",
"serverSide": true,
"serverUrl": "https://5e124.v.fwmrm.net/ad/g/1"
},
"provider": "freewheel"
},
"analytics": {
"cim": {
"contentType": "ce/tv",
"identifier": "**********************************************",
"linkTv": "************",
"materialId": "************************************",
"sourceType": "vid.tvi.ep.vod.free"
}
},
"duration": 5364,
"metadata": {
"assetType": "video",
"availability": 17,
"createdAt": "2019-06-21T15:17:30.529Z",
"creator": "whatson import service",
"geoBlocked": false,
"id": "a123b4c0-0846-4145-83b7-f26052d0984a",
"legalTags": [
"PG_16"
],
"markers": {
"endCredits": 5192
},
"title": "Bo",
"videoType": "movie"
},
"streamType": "vod",
"streams": [
{
"anvato": {
"accessKey": "********************************",
"mcp": "ONEMCP1",
"token": "ey**********************************.ey**************************************************************************************************************************************************************",
"video": "10036277"
},
"type": "anvato"
}
],
"subtitles": [
{
"language": "nl",
"url": "https://dvt-subtitles.persgroep.be/153003_732e0e66-7ec0-46a0-bc2e-056642f3d514.vtt"
}
]
}
}
We need the video.streams[x].anvato.video
, video.streams[x].anvato.accessKey
and video.streams[x].anvato.token
to get the stream.
We have a video
, accessKey
and a token
. We can now get the stream details. First we need to construct a POST request we have to make.
- POST
https://tkx.apis.anvato.net/rest/v2/mcp/video/*video*
Example request
{
"ads": {
"freewheel": {
"custom": {
"ml_apple_advertising_id": "",
"ml_dmp_userid": "************************************",
"ml_gdprconsent": "functional|analytics|content_recommendation|targeted_advertising|social_media",
"ml_google_advertising_id": "************************************",
"ml_userid": "********************************"
},
"network_id": "385316",
"profile_id": "385316:medialaan_VTMGO_SSAI_android",
"server_url": "https://5e124.v.fwmrm.net/ad/g/1",
"site_section_id": "mdl_vtmgo_phone_android_default",
"video_asset_id": "a123b4c0-0846-4145-83b7-f26052d0984a_vod"
}
},
"api": {
"anvstk2": "ey**********************************.ey**************************************************************************************************************************************************************"
},
"content": {
"mcp_video_id": "10036277"
},
"sdkver": "5.0.39",
"user": {
"adobepass": {
"err_msg": "",
"maxrating": "",
"mvpd": "",
"resource": "",
"short_token": ""
},
"device": "android",
"device_id": "************************************"
},
"version": "3.0"
}
Example response
anvatoVideoJSONLoaded( {
"upload_id": "10036277",
"owner_id": "10000004",
"program_id": "10000001",
"video_type": "1",
"image_id": "0",
"src_image_url": "https:\/\/mcp-media-medialaan.storage.googleapis.com\/pvw\/54A\/632\/54A632D49DEB4EC3A9ABB304D66D4BD7_3.jpg",
"def_title": "Bo",
"def_description": "De vijftienjarige Deborah wil ontsnappen uit de verstikkende alledaagsheid van Linkeroever. Samen met de achttienjarige Jennifer duikt ze het Antwerpse nachtleven in. Wanneer ze ontdekt dat Jennifer als escorte werkt, is Deborah geprikkeld door wat voor haar een eenvoudige weg naar geld lijkt. Onder de naam Bo maakt ze kennis met een wereld waar ze duidelijk niet klaar voor is.",
"def_tags": "video",
"ts_added": "1561124113",
"ts_published": "1561124112",
"ts_expire": "1876484113",
"ts_end": "2192276112",
"ts_airdate": "1561124113",
"ts_updated": "1561132360",
"season": "2019",
"season_id": 0,
"episode_no": "0",
"episode_id": 0,
"state": "10",
"height": "1080",
"width": "1920",
"duration": "5364",
"bitrate_kbps": "7888",
"codec": "h264",
"container": "mp4",
"frame_rate": "25",
"profile": false,
"audio_codec": "aac",
"audio_sampling_rate": "44100",
"file_size": "5379022830",
"file_md5": "",
"audio_channels": "2",
"anchor_fields": "127",
"primary_category": "10000001",
"midroll_slots": "",
"user_file_name": "190621_10036277_7cf627ff_b837_47c8_98c4_ffdaf4e0adb1",
"source_type": "",
"video_source_id": "10000004",
"priority": "0",
"deleted": "0",
"approval_state": "1",
"approval_user_id": "0",
"done": true,
"error": false,
"nomedia": false,
"pvw_width": 800,
"pvw_height": 450,
"published_urls": [
{
"embed_url": "https:\/\/dcs-vod.apis.anvato.net\/vod\/yDv6O_gZ3To7_VRub**********************************************KFb9ahz_g\/manifest.mpd?anvtrid=********************************&anvauth=tb=0~te=**********~sgn=****************************************************************&t=1562404***",
"protocol": "https",
"format": "dash",
"format_name": "DASH Variant Stream",
"kbps": "Variant",
"abitrate": "Variant",
"akbps": "Variant",
"width": "Variant",
"height": "Variant",
"cdn_name": "Cloud",
"state": 10,
"license_url": "https:\/\/drm.apis.anvato.net\/cenc?eqp=uNfo9w*************************ROVjsIF5YNPoAjAkajg&anvauth=tb=0~te=1562415559~sgn=f2603a1*************************82d04&t=156240****"
}
],
"ad_slots": [
{
"slot_id": "0",
"ts": "4063",
"dur": "0",
"poskeys": "",
"negkeys": "",
"frames": "0"
},
{
"slot_id": "0",
"ts": "2897",
"dur": "0",
"poskeys": "",
"negkeys": "",
"frames": "4"
},
{
"slot_id": "0",
"ts": "1561",
"dur": "0",
"poskeys": "",
"negkeys": "",
"frames": "3"
}
],
"pvw_matrices": [
{
"url": "https:\/\/mcp-media-medialaan.storage.googleapis.com\/pvw\/54A\/632\/54A632D49DEB4EC3A9ABB304D66D4BD7_pvw-M0.jpg",
"pvw_type": "0"
}
/* ... */
],
"categories": [
"Entertainment"
],
"custom_metadata": [
{
"name": "broadcast-id",
"value": ""
},
{
"name": "Facebook link",
"value": ""
},
{
"name": "fw_caid",
"value": ""
},
{
"name": "Header Image",
"value": ""
},
{
"name": "Spotify link",
"value": ""
},
{
"name": "video-asset-id",
"value": "7cf627ff-b837-47c8-98c4-ffdaf4e0adb1"
},
{
"name": "video-context-id",
"value": "a123b4c0-0846-4145-83b7-f26052d0984a"
}
],
"previews": {
"url": "https:\/\/mcp-media-medialaan.storage.googleapis.com\/pvw\/54A\/632\/54A632D49DEB4EC3A9ABB304D66D4BD7_pvw",
"n": 400,
"twp": 128,
"thp": 72,
"tws": 64,
"ths": 36
},
"recommendations": [
{
"mcpid": "10036276",
"title": "A Family Reunion",
"image": "https:\/\/mcp-media-medialaan.storage.googleapis.com\/pvw\/969\/B10\/969B10317AA04DFCBA3638840789BBB9_5.jpg",
"description": "Vijftien jaar geleden ging de succesvolle band The Banners uit elkaar. Sindsdien hebben de twee broers en zus geen contact meer met elkaar en leiden ze elk hun leven. Desiree is een songwriter in Los Angeles, de oudste broer Mitchell is zanger en de jongste broer Danny is getrouwd en woont met zijn twee kinderen niet ver van hun geboortedorp Pinewood Grove. Het charmante dorpje heeft een moeilijke periode achter de rug en daarom wil moeder Banner de kinderen terug bij elkaar brengen, zodat ze als band kunnen optreden op het liefdadigheidsevenement van Pinewood Grove.",
"duration": "4879"
},
/* ... */
],
"thumbnail": "https:\/\/mcp-media-medialaan.storage.googleapis.com\/pvw\/54A\/632\/54A632D49DEB4EC3A9ABB304D66D4BD7_3_160x90.jpg",
"common": {
"rating": "1",
"language": "en",
"category_ids": "10000001",
"com_comments": "0",
"com_responses": "0",
"com_ratings": "0",
"com_embedding": "0",
"ads_adsense": "0",
"ads_invideo": "0",
"ads_instream": "0",
"ads_has_postroll": "0",
"ads_has_preroll": "0",
"crew": "",
"rules": "",
"cr_owner": "",
"cr_address": "",
"cr_phone": "",
"cr_email": "",
"cr_year": "0",
"def_rule_access": "0",
"subratings": ""
},
"captions": [
{
"language": "nl",
"format": "SCC",
"url": "https:\/\/b37bsurs4eaduwbfa7xqj4ar5d.gcdn.anvato.net\/captionupl\/0D9\/9BA\/0D99BA4134A84A788F5ED309C070920E.scc?Expires=1562408359&KeyName=mcpkey1&Signature=***************************"
},
{
"language": "nl",
"format": "SMPTE-TT",
"url": "https:\/\/dvt-subtitles.persgroep.be\/153003_732e0e66-7ec0-46a0-bc2e-056642f3d514_smpte-tt.xml"
}
],
"program_name": "Default Program",
"program_description": "This is the default program",
"timed_cues": [],
"mcp_id": "ONEMCP1",
"authorization": {
"mvpd": false,
"ppv": false,
"sub": false
},
"auth_period": [
{
"type": "free",
"start": "1561124112",
"end": "1876484113"
}
],
"adst_temp_ver": "2",
"atvm": "1",
"access_rules": "{}",
"generated": "at ********** in 0.00031208992004395 sec"
}
)
The reply is a JSON inside a anvatoVideoJSONLoaded()
. We need the published_urls[x].embed_url
and published_urls[x].license_url
.
The embed_url
will be the MPEG-DASH manifest, the license_url
is needed for widevine.