OKAST · EPG DOCTOR API

es-epgdoctor Partner API · v1.9.60

MCP & REST API authentication. Every request needs Authorization: Bearer <api_key>, a partner API key created from the admin’s Utilisateurs tab. Each key carries its own named scopes — none are implicit — and its own access modes (api today, mcp reserved for a future MCP layer built on this same key system; a key without the right mode for a channel can never authenticate on it, regardless of its scopes). Base URL: https://es-epgdoctor.cedric-monnier.workers.dev/v1/api. Full machine-readable spec: /v1/api/openapi.json.

GET /channels

Liste des chaînes mémorisées

Required scope: channels:read

Renvoie chaque chaîne mémorisée disposant d’un channel_id réel (une source jamais vérifiée n’en a pas encore). Ne renvoie jamais l’URL du flux XML source. channel_id (valeur brute du provider) et channel_uuid (identifiant canonique de l’app, persistance garantie même si le provider renomme un jour la chaîne) sont tous deux exposés — chaque route channels/* accepte l’un ou l’autre.

Aucun paramètre de requête.

Exemple de réponse

{
  "data": {
    "channels": [
      {
        "channel_id": "raw_provider_id_123",
        "channel_uuid": "3f1a2b4c-5d6e-7f89-a0b1-c2d3e4f56789",
        "name": "Example Channel",
        "provider": "OKAST",
        "logo_url": "https://…",
        "format": "xmltv",
        "last_checked_at": "2026-08-24T10:00:00.000Z",
        "has_errors": false
      }
    ]
  },
  "meta": {
    "count": 1
  }
}

GET /channels/programs

Grille EPG d’une chaîne sur une plage donnée

Required scope: channels:read

Programmes connus pour une chaîne entre since et until (ISO 8601). Plage maximale : 31 jours. Un scope content:read supplémentaire (jamais requis, jamais un gate sur l’accès aux programmes eux-mêmes) enrichit chaque programme d’un champ iab_tags — les tags IAB Content Taxonomy 2.2 associés au TITRE de ce programme (pas à cette diffusion précise), destinés à enrichir le contexte passé au décision process de l’Ad Server pour le ciblage contextuel. Absent du tout sans ce scope ; un tableau vide (pas absent) si le scope est présent mais que le titre n’a encore aucun tag connu.

ParamètreTypeRequisNote
channel_idstringnonchannel_id ou channel_uuid requis (l’un des deux)
channel_uuidstringnonidentifiant canonique de la chaîne, persistance garantie contrairement à channel_id — préférable pour un stockage long terme côté partenaire
sincestringoui
untilstringoui

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "programs": [
      {
        "title": "Example Show",
        "subtitle": null,
        "description": null,
        "start": "2026-08-24T10:00:00.000Z",
        "end": "2026-08-24T10:30:00.000Z",
        "category": [
          "Documentaire"
        ],
        "image_url": "https://example.test/programme.jpg",
        "iab_tags": [
          {
            "taxonomy_version": "3.1",
            "code": "640",
            "label": "Television"
          }
        ]
      }
    ]
  },
  "meta": {
    "count": 1,
    "since": "2026-08-24T00:00:00.000Z",
    "until": "2026-08-25T00:00:00.000Z"
  }
}

POST /channels/refresh

Relance un contrôle sur une chaîne déjà mémorisée

Required scope: channels:write

Refait un fetch + parse réel de la source associée à ce channel_id et persiste le résultat — une vraie action avec effet de bord, pas une lecture passive. Nécessite le scope channels:write, jamais accordé par channels:read seul. channel_id ou channel_uuid requis dans le corps (l’un des deux).

Aucun paramètre de requête.

Corps de la requête

{
  "channel_id": "raw_provider_id_123"
}

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "checked_at": "2026-08-24T10:00:00.000Z",
    "program_count": 42,
    "issues_count": 0,
    "valid": true,
    "latest_epg_date": "2026-09-06T18:00:00.000Z"
  }
}

GET /channels/quality

Analyse ADN d’une chaîne (structure de grille, diversité de titres, catégories)

Required scope: quality:read

range=week nécessite week_start (une date ISO, alignée automatiquement sur le lundi de cette semaine-là) ; range=90d couvre toujours la fenêtre de rétention complète, aucun paramètre supplémentaire.

ParamètreTypeRequisNote
channel_idstringnonchannel_id ou channel_uuid requis (l’un des deux)
channel_uuidstringnonidentifiant canonique de la chaîne, persistance garantie contrairement à channel_id — préférable pour un stockage long terme côté partenaire
rangestring (week | 90d)oui
week_startstringnonrequis si range=week

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "dna": {
      "catalogue_size": 128,
      "top_category": {
        "name": "Documentaire",
        "pct": 62
      }
    },
    "range": {
      "since": "2026-08-24T00:00:00.000Z",
      "until": "2026-08-31T00:00:00.000Z"
    }
  },
  "meta": {
    "truncated": false
  }
}

GET /channels/heatmap

Grille hebdomadaire densité/fraîcheur/qualité, jour par jour et heure par heure

Required scope: quality:read

Même moteur que la «Vue hebdomadaire» de l’onglet Analyse.

ParamètreTypeRequisNote
channel_idstringnonchannel_id ou channel_uuid requis (l’un des deux)
channel_uuidstringnonidentifiant canonique de la chaîne, persistance garantie contrairement à channel_id — préférable pour un stockage long terme côté partenaire
week_startstringoui

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "heatmap": {
      "days": [
        "Lun 24 août",
        "…"
      ],
      "cells": []
    },
    "range": {
      "since": "2026-08-24T00:00:00.000Z",
      "until": "2026-08-31T00:00:00.000Z"
    }
  },
  "meta": {
    "truncated": false
  }
}

GET /channels/ad-load

Statistiques de charge publicitaire (quotidien/horaire)

Required scope: ad-load:read

Historique complet mergé (import quotidien et/ou coupures détaillées), pas une plage bornée — mêmes données que la carte «Ad Load Profile».

ParamètreTypeRequisNote
channel_idstringnonchannel_id ou channel_uuid requis (l’un des deux)
channel_uuidstringnonidentifiant canonique de la chaîne, persistance garantie contrairement à channel_id — préférable pour un stockage long terme côté partenaire

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "stats": {
      "period": {
        "start": "2026-08-01",
        "end": "2026-08-24",
        "days": 24
      },
      "avails_per_day_avg": 96
    },
    "hourly_trend": {
      "series": [],
      "avg_avails_per_hour": 4
    }
  }
}

GET /channels/ad-fill-rate

Taux de remplissage Ad Server (coupures programmées vs décisions réelles)

Required scope: ad-load:read

Best-effort : si l’Ad Server est injoignable, une heatmap réelle revient quand même (tout en NO_REQUEST), avec ad_serving_warning expliquant pourquoi plutôt qu’une erreur.

ParamètreTypeRequisNote
channel_idstringnonchannel_id ou channel_uuid requis (l’un des deux)
channel_uuidstringnonidentifiant canonique de la chaîne, persistance garantie contrairement à channel_id — préférable pour un stockage long terme côté partenaire
week_startstringoui

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "heatmap": {
      "days": [],
      "cells": []
    },
    "range": {
      "since": "2026-08-24T00:00:00.000Z",
      "until": "2026-08-31T00:00:00.000Z"
    }
  },
  "meta": {
    "ad_serving_warning": null
  }
}

GET /channels/context

Snapshot complet programmes + coupures + tags IAB, pour préchargement par un décision process externe

Required scope: channels:read

Conçu pour le préchargement — jamais appelé pendant une requête VAST en direct. Retourne un instantané COMPLET, non paginé et autoritatif de [since, until[ : tout programme ou coupure connu qui chevauche la fenêtre est inclus (un programme commencé avant since mais qui se termine après since est inclus). Plage maximale : 7 jours. Une correction ou dépublication de grille est observable dès le prochain appel — un programme qui n\u2019a pas été reconfirmé par la vérification la plus récente de la chaîne n\u2019est plus jamais retourné, quelle que soit la fenêtre demandée. program_id et break_id sont des identifiants publics stables (voir program_id : assignation interne UUID + correspondance titre/horaire tant qu\u2019aucun fournisseur EPG n\u2019expose son propre identifiant persistant ; break_id : identifiant interne stable, ou l\u2019identifiant persistant OKAST lui-même dès qu\u2019il sera disponible dans les exports réels). Les tags IAB (taxonomie IAB Content 3.1) sont une classification publicitaire structurée, distincte des catégories éditoriales. keywords (Ad Server, 2026-09-08) : termes éditoriaux extraits du titre/sous-titre/description de CE programme — jamais mappés vers IAB ici, ce mapping reste entièrement du côté Ad Server, à partir de son propre dictionnaire keyword → IAB Content 3.1 ; les tags IAB natifs (champ iab), quand ils existent, restent prioritaires. series_id (optionnel) : identifiant stable de série/émission récurrente quand la source en fournit un ; null pour la grande majorité des flux aujourd\u2019hui — jamais dérivé du titre. Supporte ETag/If-None-Match (304 si la fenêtre n\u2019a pas changé) — keywords et series_id sont inclus dans ce calcul, un changement de l\u2019un ou l\u2019autre invalide le cache.

ParamètreTypeRequisNote
channel_idstringnonchannel_id ou channel_uuid requis (l’un des deux)
channel_uuidstringnonidentifiant canonique de la chaîne, persistance garantie contrairement à channel_id — préférable pour un stockage long terme côté partenaire
sincestringoui
untilstringoui

Exemple de réponse

{
  "data": {
    "channel_id": "raw_provider_id_123",
    "programs": [
      {
        "program_id": "3f1a2b4c-5d6e-7f89-a0b1-c2d3e4f56789",
        "series_id": null,
        "start": "2026-08-25T18:00:00.000Z",
        "end": "2026-08-25T19:00:00.000Z",
        "title": "Example Show",
        "subtitle": null,
        "description": "An optional editorial synopsis.",
        "categories": [
          "Documentaire"
        ],
        "image_url": "https://example.test/programme.jpg",
        "iab": {
          "taxonomy": "IAB_CONTENT",
          "version": "3.1",
          "cattax": 9,
          "tags": [
            {
              "term_id": "653",
              "label": "Travel",
              "source": "epg_doctor_manual",
              "confidence": null
            }
          ]
        },
        "keywords": [
          "synopsis",
          "editorial",
          "documentaire"
        ],
        "updated_at": "2026-08-25T10:12:30.000Z"
      }
    ],
    "ad_breaks": [
      {
        "break_id": "7c8d9e0f-1a2b-3c4d-5e6f-7a8b9c0d1e2f",
        "program_id": "3f1a2b4c-5d6e-7f89-a0b1-c2d3e4f56789",
        "start": "2026-08-25T18:22:00.000Z",
        "end": "2026-08-25T18:24:00.000Z",
        "duration_seconds": 120,
        "position": "unknown",
        "updated_at": "2026-08-25T10:12:30.000Z"
      }
    ]
  },
  "meta": {
    "since": "2026-08-25T00:00:00.000Z",
    "until": "2026-09-01T00:00:00.000Z",
    "generated_at": "2026-08-25T10:15:00.000Z",
    "source_updated_at": "2026-08-25T10:12:30.000Z",
    "coverage_until": "2026-09-06T18:00:00.000Z",
    "program_count": 1,
    "ad_break_count": 1
  }
}