Aller au contenu

API turf.bzh - documentation

Accès programmatique aux données hippiques turf.bzh : programme, partants exacts, cotes PMU en direct, arrivées, Quinté+. Format JSON, lecture seule. Que vous vouliez remplir un tableur, recevoir une alerte ou brancher un bot, commencez par les Premiers pas ci-dessous : une URL qui marche dans votre navigateur suffit.

05/08/2026 Ce qui vient d'arriver

Les rapports PMU s'ouvrent, et avec eux la mesure de ce que nos sélections ont couvert. Trois adresses nouvelles, et trois de plus qui rendent un tableau. Dépliez ce qui vous concerne ; le reste de la page n'a pas bougé.

Les rapports définitifs d'une course, les deux masses PMU

GET /v1/courses/{date}/{rc}/rapports rend les rapports définitifs collectés d'une course. À ne pas confondre avec /arrivee, qui interroge le PMU en direct et ne sert donc que la journée en cours, sur une seule masse : ici, 306 088 rapports sur 9 038 courses depuis le 1er janvier, point de vente et en ligne.

Le PMU sert deux masses pour le même pari, et elles ne paient pas pareil. Le 20 juillet sur la R1C5, le couplé placé 15-7 valait 17,80 € au point de vente et 30,10 € en ligne, soit 69 % de plus. La masse est donc un champ nommé, en_ligne ou point_de_vente, jamais un préfixe à deviner.

Chaque ligne porte deux montants, et pour la même raison. Le PMU publie rapport_pour_1_euro. Ce qu'un ticket encaisse vaut ce nombre multiplié par la mise de base du pari, qui n'est pas la même partout : 1 € au simple gagnant en ligne, 2 € au point de vente, 1,50 € au quarté, 3 € au Multi et au 2 sur 4. Nous servons donc aussi rapport_pour_la_mise_de_base : personne n'a à refaire la multiplication, ni à se tromper dessus.

Paramètres : masse, pari, payants, format=csv. Ce que chaque champ signifie.

curl -H "Authorization: Bearer VOTRE_CLE" \
  "https://www.turf.bzh/api/v1/courses/2026-07-20/R1C5/rapports?masse=en_ligne"
Une journée entière de rapports en un seul appel

GET /v1/rapports?date= sert les mêmes lignes sur toute une journée, soit environ 340 rapports sur 40 courses. Mêmes champs, mêmes filtres, plus code_course pour se restreindre à une course.

Le meta porte nb_courses, paris_presents et surtout collecte, qui donne les bornes exactes de ce qui existe : vous n'avez pas à deviner jusqu'où remonte l'historique.

Le taux de couverture mesuré de nos sélections

GET /v1/performances?fenetre=30j sert une mesure et une seule : la part des courses où la combinaison gagnante d'un pari était entièrement contenue dans nos N premiers chevaux. Elle est calculée sur toutes les courses de la période où le PMU a publié cette formule, jamais sur une sélection des meilleures, et le dénominateur voyage avec le taux pour que vous puissiez le recompter.

Chaque ligne porte tickets, le nombre de combinaisons à jouer, et engage_eur, leur coût. Un taux sans son prix ne veut rien dire : couvrir un trio avec six chevaux, ce sont vingt combinaisons, et ça n'a pas le même sens que de le couvrir avec quatre.

Fenêtres : 7j, 14j, 30j, 90j, annee. Le palmarès des plus gros rapports couverts n'est joint qu'avec palmares=1 : la mesure pèse moins que la vitrine, et c'est la mesure qu'on sert par défaut.

Ce n'est ni un rendement ni une promesse. Ces nombres disent ce qu'une combinaison a payé, pas ce qu'un joueur a gagné : personne n'a joué toutes les courses. Le champ meta.avertissement le porte dans chaque réponse.

Douze adresses rendent un tableau, contre neuf auparavant

Les trois nouvelles acceptent format=csv : /v1/courses/{date}/{rc}/rapports, /v1/rapports et /v1/performances. Les douze adresses sont écrites en entier, prêtes à copier, dans la section dédiée.

La spécification openapi.json déclare le paramètre format sur les trois, ce qui vaut pour Postman et pour tout client généré depuis la spec.

Les changements plus anciens sont dans le journal des modifications. Le contrat de l'API v1 ne change pas : des champs sont ajoutés, jamais retirés ni renommés. Vos scripts existants continuent de fonctionner sans retouche.

Premiers pas en 5 minutes

Le chemin le plus court, sans écrire une ligne de code : une adresse qui marche dans votre navigateur.

  1. Débloquez la Licence API (19,90 € une fois) sur api-licence.php, avec un abonnement turf.bzh actif.
  2. Générez votre clé sur Mon accès API (elle ne s'affiche qu'une fois, copiez-la).
  3. Collez cette adresse dans la barre de votre navigateur, en remplaçant la clé, et validez :
    https://www.turf.bzh/api/v1/programme?api_key=tbz_live_VOTRE_CLE
    Vous voyez le programme du jour en JSON. Si ça s'affiche, vous êtes prêt.
  4. Pour un tableau propre qui s'ouvre dans Excel, ajoutez &format=csv à la fin de l'adresse.
Prendre un modèle prêt à l'emploi
Pratique, mais à connaître : la clé dans l'adresse (?api_key=) convient à un test rapide ou à un fichier gardé sur votre ordinateur. Ne la mettez jamais dans une page web publique ni un lien partagé : dans ce cas, préférez l'en-tête Authorization côté serveur (voir Authentification).

Accès

Deux conditions, vérifiées à chaque requête :

  • La Licence API (paiement unique, à vie) : api-licence.php
  • Un abonnement turf.bzh actif. En cas de résiliation, l'API est suspendue ; elle se réactive seule au réabonnement, sans re-payer.

URL de base : https://www.turf.bzh/api/v1/ - HTTPS uniquement. Les réponses ne sont jamais mises en cache (Cache-Control: no-store) : ce que vous recevez est frais.

Une clé, deux usages. La même clé couvre les endpoints DONNÉES (programme, partants, cotes en direct, arrivées, base historique - illimités) et les endpoints CHATBZH (question à l'agent, méthodes), qui consomment vos crédits IA comme sur le site.

Authentification

Générez votre clé personnelle sur Mon accès API (affichée une seule fois). Envoyez-la à chaque requête, de préférence en en-tête :

Authorization: Bearer tbz_live_VOTRE_CLE

Alternatives tolérées : l'en-tête X-Api-Key: tbz_live_..., ou le paramètre ?api_key=tbz_live_... pour les outils qui ne savent pas poser d'en-tête (Google Sheets, tableurs, liens de test). Attention avec le paramètre d'URL : la clé peut traîner dans des historiques. En cas de doute, régénérez-la (l'ancienne meurt immédiatement).

La clé est personnelle : pas de partage, pas d'usage mutualisé. Chaque clé est auditée.

Quickstart

curl - le programme du jour

curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \
  "https://www.turf.bzh/api/v1/programme"

Python - cotes live d'une course, toutes les 60 secondes

import requests, time

KEY = "tbz_live_VOTRE_CLE"
URL = "https://www.turf.bzh/api/v1/courses/2026-08-08/R1C4/cotes"

while True:
    r = requests.get(URL, headers={"Authorization": f"Bearer {KEY}"}, timeout=15)
    j = r.json()
    if "error" in j:
        print("Erreur:", j["error"]["message"])
        break
    for p in j["data"]["partants"]:
        print(p["num"], p["cheval"], p["cote"])
    time.sleep(60)  # restez au-dessus de 30 s : les cotes sont cachées côté serveur

Google Sheets - partants dans une feuille

Le plus simple, sans en-tête à gérer : la fonction IMPORTDATA avec la clé en paramètre et &format=csv. Mettez votre clé dans une cellule (ici A1) :

=IMPORTDATA("https://www.turf.bzh/api/v1/programme?api_key=" & A1 & "&format=csv")

Pour poser un vrai en-tête Authorization (plus propre), passez par Apps Script (Extensions > Apps Script) :

function partants() {
  var url = "https://www.turf.bzh/api/v1/courses/2026-08-08/R1C4";
  var res = UrlFetchApp.fetch(url, {
    headers: { Authorization: "Bearer tbz_live_VOTRE_CLE" }
  });
  var data = JSON.parse(res.getContentText()).data;
  var sheet = SpreadsheetApp.getActiveSheet();
  sheet.clear();
  sheet.appendRow(["Num", "Cheval", "Driver", "Cote BZH", "Note IA"]);
  data.partants.forEach(function (p) {
    sheet.appendRow([p.num, p.name, p.jockey_driver, p.Cote_BZH, p.Note_IA]);
  });
}

Galerie de cas d'usage

Tutoriel - Excel qui se remplit tout seul

L'astuce : la même clé accepte ?api_key= dans l'URL et ?format=csv pour renvoyer un tableau propre. Excel n'a alors aucun en-tête à gérer, il suffit de lui donner une adresse.

Le plus simple : le pack prêt à l'emploi

Téléchargez le Pack Excel turf.bzh : programme et Quinté, cotes d'une course, value bets, plus un suivi de bankroll. Les trois requêtes sont déjà dans le classeur : vous collez votre clé dans la cellule jaune de l'onglet Réglages, vous cliquez sur Actualiser tout, et les trois onglets se remplissent. Vous n'avez aucun code à écrire ni à copier.

Vous avez téléchargé le pack avant le 30/07/2026 ? Cette version-là ne contenait pas les requêtes, il fallait les créer soi-même. Retéléchargez le classeur, puis recopiez votre clé, votre date et votre course dans l'onglet Réglages. Rien d'autre à refaire.

Le faire vous-même (Power Query)

Vérifiez d'abord que votre Excel sait le faire. Power Query est intégré d'origine à partir d'Excel 2016 sur Windows (onglet Données > Récupérer et transformer). Sur Excel 2010 et 2013, c'était un complément à installer séparément, et Microsoft ne le met plus à jour ; pour Excel 2010 il réclamait la version Professionnel Plus (avec Software Assurance). Il n'a jamais existé pour Excel 2007. Côté Mac, Excel 2016 et Excel 2019 pour Mac n'ont pas Power Query du tout : il faut Excel pour Microsoft 365 pour Mac. Si vous n'avez rien de tout cela, ce n'est pas bloquant : le CSV s'ouvre directement dans n'importe quel tableur, et Google Sheets fait la même chose avec IMPORTDATA.
  1. Ruban Données > Obtenir des données > À partir d'autres sources > Requête vide.
  2. Ouvrez Affichage > Éditeur avancé et collez ce code (adaptez l'endpoint) :
    let
        Cle = "tbz_live_VOTRE_CLE",
        Source = Csv.Document(
            Web.Contents("https://www.turf.bzh",
                [ RelativePath = "api/v1/programme",
                  Query = [ api_key = Cle, #"format" = "csv" ] ]),
            [Delimiter=";", Encoding=65001, QuoteStyle=QuoteStyle.Csv]),
        Promu = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
    in
        Promu
  3. Fermer et charger. À la première fois, Excel demande le niveau de confidentialité de la source : choisissez Anonyme.
  4. Ensuite, Données > Actualiser tout met le tableau à jour quand vous voulez.
Le piège classique de Power Query (erreur "source de données dynamique") vient d'une URL construite en un seul morceau. On l'évite en gardant une adresse de base fixe (https://www.turf.bzh) et en passant le reste via RelativePath et Query, comme ci-dessus. Le pack Excel applique déjà cette recette. Rappel : votre clé est en clair dans le classeur, gardez-le sur votre ordinateur.
Copiez le code depuis cette page, pas depuis une cellule Excel. Quand on copie une cellule qui contient des retours à la ligne, Excel entoure tout le bloc de guillemets : Power Query lit alors le code comme un simple texte et vous renvoie une seule cellule au lieu du tableau. Si vous obtenez une ligne unique commençant par let, c'est cela qui s'est produit.

Les douze adresses qui rendent un tableau

Douze adresses acceptent ?format=csv. Elles renvoient un fichier CSV en UTF-8 avec le point-virgule comme séparateur, donc directement lisible par Excel, LibreOffice, Google Sheets, ou n'importe quel outil capable d'aller chercher une adresse web. Aucune n'exige Power Query : elles marchent avec un Excel ancien, un script, ou simplement en enregistrant le fichier depuis votre navigateur.

Les voici en entier, prêtes à copier. Remplacez VOTRE_CLE par la vôtre, et adaptez la date et le code course :

# Le programme du jour
https://www.turf.bzh/api/v1/programme?api_key=VOTRE_CLE&format=csv

# Les partants d'une course, avec tous les indicateurs
https://www.turf.bzh/api/v1/courses/2026-08-08/R1C4?api_key=VOTRE_CLE&format=csv

# Les cotes de cette course, en direct
https://www.turf.bzh/api/v1/courses/2026-08-08/R1C4/cotes?api_key=VOTRE_CLE&format=csv

# Toute la série des cotes de cette course, du matin au départ
https://www.turf.bzh/api/v1/courses/2026-08-08/R1C4/cotes/historique?api_key=VOTRE_CLE&format=csv

# Le classement du jour pour un indicateur
https://www.turf.bzh/api/v1/tops?indicateur=ELO_Cheval&n=10&api_key=VOTRE_CLE&format=csv

# Les value bets du jour
https://www.turf.bzh/api/v1/value-bets?n=10&api_key=VOTRE_CLE&format=csv

# Tous les partants d'une journee entiere, cote de depart comprise
https://www.turf.bzh/api/v1/journees/2026-08-01/partants?api_key=VOTRE_CLE&format=csv

# Le dictionnaire des champs, avec leur taux de remplissage reel
https://www.turf.bzh/api/v1/schema?api_key=VOTRE_CLE&format=csv

# Les dernieres courses d'un cheval (l'idcheval vient de /v1/chevaux?recherche=)
https://www.turf.bzh/api/v1/chevaux/VOTRE_IDCHEVAL/historique?api_key=VOTRE_CLE&format=csv

# Les rapports definitifs d'une course, les DEUX masses PMU
https://www.turf.bzh/api/v1/courses/2026-07-20/R1C5/rapports?api_key=VOTRE_CLE&format=csv

# Tous les rapports d'une journee entiere
https://www.turf.bzh/api/v1/rapports?date=2026-07-20&api_key=VOTRE_CLE&format=csv

# Le taux de couverture de nos selections, sur trente jours
https://www.turf.bzh/api/v1/performances?fenetre=30j&api_key=VOTRE_CLE&format=csv

Quelles dates chacune accepte

C'est la question qui revient le plus souvent, alors autant l'écrire une fois pour toutes.

AdresseDates acceptées
/programmeLe jour même uniquement. Une autre date renvoie une erreur 422 qui vous le dit.
/courses/{date}/{rc}Toute date présente en base, y compris des années en arrière.
/cotesLe jour même. Ce sont les cotes PMU en direct, elles n'existent pas pour une course passée.
/cotes/historiqueToute journée depuis le 27/07/2026, date de début de la collecte. Avant, rien n'existe et rien ne peut être reconstruit.
/topsLe jour même par défaut, paramètre date optionnel sur les journées conservées.
/value-betsIdem : date optionnel, aujourd'hui par défaut.
/journees/{date}/partantsToute date présente en base. Les plages sont refusées en 422 : bouclez sur les dates, ou prenez les exports mensuels.
/schemaSans objet : le dictionnaire décrit la base, pas une journée.
/chevaux/{id}/historiqueToute la carrière connue. Bornes debut et fin optionnelles ; sans elles, fenêtre glissante de 90 jours.
/courses/{date}/{rc}/rapportsToute journée depuis le 01/01/2026, début de la collecte des rapports. Avant, rien n'a été collecté et rien ne peut être reconstruit. Le meta.collecte donne les bornes exactes.
/rapportsIdem. date optionnel, aujourd'hui par défaut ; une date future renvoie 422, les rapports définitifs n'existant qu'après la course.
/performancesSans objet : cinq fenêtres glissantes (7j, 14j, 30j, 90j, annee), arrêtées à la dernière journée dépouillée. Le meta.arrete_au la donne.

Le piège du séparateur décimal

Les nombres sortent avec un point décimal : 4.8 et non 4,8. Un Excel configuré en français les prendra pour du texte, et vos calculs ne suivront pas. Deux façons de s'en sortir, selon la méthode d'import :
  • Import manuel : ruban Données > Convertir, et à la dernière étape, bouton Avancé, indiquez le point comme séparateur décimal.
  • Power Query : au moment de typer les colonnes, précisez la culture "en-US" dans Table.TransformColumnTypes.
Votre clé voyage dans l'adresse. Une de ces URL complète vaut donc mot de passe : ne la collez pas dans un message public, un forum ou une capture d'écran. Si elle vous échappe, régénérez la clé depuis Mon accès API, l'ancienne est révoquée aussitôt. Pour un script, préférez l'en-tête Authorization: Bearer décrit plus haut : la clé n'apparaît alors ni dans l'URL, ni dans les journaux du serveur.

Étudier 18 mois d'archives

C'est la demande qui revient le plus chez les utilisateurs avancés : tester une hypothèse sur l'ensemble de la base plutôt que sur la journée du jour. Trois chemins existent, et le bon dépend uniquement du volume que vous visez. Prendre le mauvais coûte des heures.

Ce que vous voulezPar ou passerCe que ça coûte
Une course précise/v1/courses/{date}/{rc}1 appel
Une journée entière/v1/journees/{date}/partants1 appel, moins de 300 ms
Quelques semainesBoucle sur /v1/journees/{date}/partants1 appel par journée, largement sous le plafond
Toute la base/v1/exports19 téléchargements, une trentaine de Mo

Ne parcourez pas la base course par course. Il y a 28 310 courses : au plafond de 10 000 appels par jour, cela représente trois jours calendaires, pour un résultat que les exports donnent en quelques minutes et sans solliciter la base.

La recette, en quatre commandes

K="Authorization: Bearer VOTRE_CLE"
B=https://www.turf.bzh/api/v1

# 1. La liste des mois, avec taille, nombre de lignes et empreinte
curl -s -H "$K" "$B/exports" | jq '.data.fichiers[] | {nom, lignes, partiel, scelle}'

# 2. Tout télécharger, sauf le mois en cours
curl -s -H "$K" "$B/exports" \
  | jq -r '.data.fichiers[] | select(.partiel == false) | .nom' \
  | while read f; do curl -s -H "$K" "$B/exports/$f" -o "$f"; done

# 3. Vérifier que rien n'est arrivé tronqué
for f in partants-*.csv.gz; do gzip -t "$f" || echo "CORROMPU : $f"; done

# 4. Empiler, en ne gardant l'en-tête qu'une fois
zcat partants-2025-*.csv.gz partants-2026-*.csv.gz \
  | awk 'NR==1 || !/^date;/' > base.csv

Trois pièges, mesurés et pas supposés

La dernière journée n'a pas encore sa cote de départ. Elle arrive au rafraîchissement du lendemain matin. Si vous mesurez un rendement en incluant cette journée, vous comptez des lignes sans prix. /v1/statut donne la date exacte où s'arrêter, dans cotes.derniere_journee_avec_cote_de_depart, et l'index des exports la porte aussi.

Un mois clos n'est pas forcément figé. Quand un mois se termine, sa dernière journée entre en base sans ses cotes : le mois est clos mais son contenu bouge encore une nuit. Ces mois-là portent scelle: false et seront régénérés. Ne bâtissez pas une mesure dessus sans revenir la chercher.

Toutes les colonnes ne sont pas remplies. Quatorze des 80 colonnes sont sous 65 %, et Rapport_SG n'est présent que sur 8,5 % des lignes. Interrogez /v1/schema avant de choisir vos variables : c'est la différence entre un échantillon de 320 000 lignes et un échantillon de 27 000.

La cote servie dans les archives est la cote de départ, celle qui a payé. Ce n'est pas la cote du matin ni une moyenne. Pour la trajectoire d'une cote à l'intérieur d'une journée, il faut /v1/courses/{date}/{rc}/cotes/historique, et cette collecte ne remonte pas avant le 27/07/2026 : le flux PMU ne publie que l'instantané, une courbe passée ne se reconstruit pas après coup.

Tutoriel - alerte "baisse de cote" sur Telegram en 3 étapes

  1. Créez un bot Telegram. Dans Telegram, écrivez à @BotFather, envoyez /newbot : il vous donne un jeton. Récupérez aussi votre identifiant de discussion (chat_id) en écrivant à @userinfobot.
  2. Lisez les mouvements de cote d'une course avec l'endpoint /cotes/mouvements.
  3. Envoyez un message quand une cote baisse au-delà de votre seuil, via l'API Telegram.

Repérez d'abord les champs exacts de la réponse (le nom des colonnes peut évoluer) :

curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \
  "https://www.turf.bzh/api/v1/courses/2026-08-08/R1C4/cotes/mouvements?fenetre=5&limit=8"

Puis, un script à lancer à intervalle régulier (adaptez les noms de champs à ce que vous voyez) :

import requests

CLE   = "tbz_live_VOTRE_CLE"
BOT   = "JETON_BOTFATHER"      # de @BotFather
CHAT  = "VOTRE_CHAT_ID"        # de @userinfobot
DATE, COURSE = "2026-08-08", "R1C4"
SEUIL = 0.8                    # alerter si une cote perd au moins 0,8 point

api = requests.Session()
api.headers["Authorization"] = f"Bearer {CLE}"
url = f"https://www.turf.bzh/api/v1/courses/{DATE}/{COURSE}/cotes/mouvements"
data = api.get(url, params={"fenetre": 5, "limit": 8}, timeout=15).json().get("data", {})

for m in data.get("mouvements", []):
    evo = m.get("evolution_5min", m.get("evolution", 0)) or 0
    if evo <= -SEUIL:
        txt = f"Baisse {COURSE} : {m.get('cheval')} -> {m.get('cote')} ({evo})"
        requests.get(f"https://api.telegram.org/bot{BOT}/sendMessage",
                     params={"chat_id": CHAT, "text": txt}, timeout=15)

Pour lancer ce script automatiquement, utilisez le Planificateur de tâches (Windows) ou une tâche cron (Mac/Linux), ou passez par n8n/Make (voir plus bas). Pour un bot qui répond à des commandes, voir le tutoriel suivant.

Tutoriel - votre bot turf privé

Nous fournissons un bot Telegram prêt à héberger, en Python, qui tourne sur votre ordinateur et répond à vos commandes :

  • /course R1C4 - la fiche et les partants d'une course
  • /cotes R1C4 - les cotes PMU en direct
  • /tops ELO_Cheval - le top du jour d'un indicateur
  • /value - les value bets du jour
  • /analyse R1C4 - le classement multi-features d'une course

Il n'a besoin d'aucun serveur ni adresse publique (il utilise le "long polling" Telegram). Téléchargez-le, il est livré avec un mode d'emploi :

Télécharger le bot Telegram

En résumé : créez le bot avec @BotFather, mettez votre jeton et votre clé API dans config.py, lancez python bot.py. La même logique se transpose à Discord (bibliothèque discord.py) : une commande, un appel à l'API, une réponse.

Tutoriel - brancher ChatBZH à votre outil

Une question, une réponse complète en un seul appel. L'agent choisit ses outils (programme, cotes, indicateurs, vos méthodes), analyse et renvoie un texte. Cet endpoint consomme vos crédits IA (comme sur le site).

import requests

r = requests.post("https://www.turf.bzh/api/v1/chat",
    headers={"Authorization": "Bearer tbz_live_VOTRE_CLE"},
    json={"question": "Quels sont les 3 meilleurs ELO du Quinté du jour ?",
          "mode": "standard", "max_credits": 100},
    timeout=240)
j = r.json()
if "error" in j:
    print("Refus:", j["error"]["message"])   # ex : plafond de crédits dépassé, avant tout débit
else:
    d = j["data"]
    print(d["reponse"])                       # le texte de l'agent, à afficher dans votre outil
    print("Outils:", d.get("tools_utilises"))
    print("Crédits facturés:", d["credits"]["factures"], "- reste:", d["credits"]["solde_restant"])

Le JSON de réponse contient : reponse (le texte), tools_utilises (les outils appelés), credits.factures et credits.solde_restant, et forfait_applique (60 cr pour appliquer une méthode, 100 cr pour un backtest). Prévoyez un délai : un tour peut durer 30 à 180 secondes. Chaque appel est autonome (pas de mémoire de conversation), mettez tout le contexte utile dans la question.

Endpoints V1

Tout est en GET (sauf /chat). Les dates sont au format YYYY-MM-DD, les courses au format turfiste R1C4. Réponse : {"data": ..., "meta": {...}} en succès, {"error": {...}} sinon.

EndpointCe que ça renvoie
/v1/meÉtat de votre compte : licence, abonnement, clé, crédits IA, usage API du jour, limites.
/v1/statutFraîcheur de la base (healthcheck pour vos scripts) : fresh / stale / empty, nombre de courses chargées.
/v1/programmeLe programme du jour : courses, hippodromes, disciplines, heures, nombre de partants, drapeau Quinté+. Paramètre filter : all (défaut), upcoming, completed.
/v1/quinteLa course du Quinté+ du jour (source officielle turf.bzh) avec sa fiche complète et ses partants.
/v1/resultatsArrivées et rapports (SG/SP) des courses terminées d'une journée, aujourd'hui ou n'importe quelle date passée présente en base. L'ordre d'arrivée est complet par défaut, pas tronqué. Paramètres optionnels : date (YYYY-MM-DD, défaut aujourd'hui), hippodrome (recherche partielle, insensible à la casse, ex. enghien), code_course, arrivee (complete par défaut, top5 pour les cinq premiers). Chaque course porte aussi non_partants et non_classes. Ce qui a changé le 02/08/2026.
/v1/courses/{date}/{rc}Fiche d'une course + partants EXACTS : numéro, cheval, driver/jockey, entraîneur, ferrure, musique, Cote BZH, ELO, Note IA, popularité... Fonctionne aussi sur tout l'historique (324 162 partants sur 28 310 courses, depuis le 01/02/2025). Paramètre hippodrome : sur dix journées de l'historique, deux réunions différentes portent le même code RxCy ; sans ce paramètre la réponse mélange les deux pelotons et porte alors ambigu: true. Le jour J, la colonne Cote est volontairement absente : utilisez /cotes (temps réel) ou Cote_BZH.
/v1/journees/{date}/partantsNouveau (03/08/2026). Tous les partants d'une journée en un seul appel, avec la cote de départ. C'est l'endpoint à utiliser pour une étude sur plusieurs jours : reconstituer la base course par course demande 28 310 appels, soit trois jours compte tenu du plafond quotidien ; par journée, 554 appels et une dizaine de minutes. Paramètres : discipline (plat, trot attelé, trot monté, obstacle, ou le code d'une lettre), hippodrome, format=csv. Les plages de dates sont refusées : bouclez sur les dates, ou prenez les exports mensuels.
/v1/exports
/v1/exports/{fichier}
Nouveau (03/08/2026). La base entière, en CSV compressé, un fichier par mois, avec taille et empreinte SHA-256. Le mois en cours est marqué partiel. À préférer à toute extraction massive : c'est plus rapide pour vous et cela ne sollicite pas la base.
/v1/schemaNouveau (03/08/2026). Le dictionnaire des champs servis : description, type, unité, et surtout taux de remplissage réel. Sur les 80 colonnes de la base, 38 sont renseignées à 100 % et 14 le sont à moins de 65 % : Rapport_SG à 8,5 %, Cote_BZH à 50,3 %. Vérifiez-le avant de bâtir une hypothèse sur un champ.
/v1/courses/{date}/{rc}/cotesCotes PMU en direct : cote actuelle, cote de référence du matin, écart depuis le matin, écart sur les dernières minutes, fraîcheur. Cache serveur adaptatif (rafraîchi plus vite à l'approche du départ). Les trois reculs n'ont pas la même durée.
/v1/courses/{date}/{rc}/cotes/mouvementsLes plus gros mouvements de cote sur une fenêtre glissante, écart depuis le matin inclus. Paramètres : fenetre (2-60 min, défaut 5), limit (1-8, défaut 5). Le champ fenetre_reelle_sec donne la durée réellement couverte.
/v1/courses/{date}/{rc}/cotes/historiqueExclusif. Toute la série des cotes de la course, du matin jusqu'au départ : chaque relevé porte son horodatage, la cote du moment et la cote de référence du matin. Le flux PMU ne publie que l'instantané, une courbe ne se reconstruit donc pas après coup : nous l'enregistrons nous-mêmes depuis le 27/07/2026. Ce que couvre la collecte.
/v1/courses/{date}/{rc}/arriveeL'arrivée d'une course en quasi temps réel le jour J (source PMU live), rapports inclus quand disponibles.
/v1/courses/{date}/{rc}/rapportsNouveau (05/08/2026). Exclusif. Les rapports définitifs collectés d'une course, les deux masses PMU. À ne pas confondre avec /arrivee, qui interroge le PMU en direct et ne sert donc que la journée en cours : ici, tout l'historique collecté depuis le 1er janvier. Chaque ligne porte deux montants : rapport_pour_1_euro, ce que le PMU publie, et rapport_pour_la_mise_de_base, ce qu'un ticket encaisse réellement. Paramètres : masse (en_ligne | point_de_vente), pari, payants, format=csv. Ce que sont les deux masses.
/v1/rapportsNouveau (05/08/2026). Les mêmes rapports sur une journée entière, environ 340 lignes sur 40 courses. Paramètres : date (défaut aujourd'hui), code_course, masse, pari, payants, format=csv.
/v1/performancesNouveau (05/08/2026). Exclusif. Le taux de couverture de nos sélections, mesuré sur ces mêmes rapports : la part des courses où la combinaison gagnante était entièrement contenue dans nos N premiers chevaux. Mesuré sur toutes les courses de la période, jamais sur une sélection des meilleures, et chaque ligne porte son nombre de combinaisons et son coût. Paramètres : fenetre (7j | 14j | 30j | 90j | annee), famille, palmares=1, n, format=csv. Ce que ce taux est, et ce qu'il n'est pas.
/v1/courses/{date}/{rc}/indicateursFeatures calculées par partant : tendances ELO 30/90 j, forme récente, écarts, affinité distance/hippodrome, synergie jockey, signal IMDC, incidents...
/v1/courses/{date}/{rc}/analyseLe classement multi-features de la course (avec explication des rangs) + la LigneBZH du jour quand elle est disponible.
/v1/courses/{date}/{rc}/ecartsÉcarts de victoires/places : ceux du CHEVAL (vrai signal de forme) et ceux du numéro (folklore, présenté comme tel).
/v1/courses/{date}/{rc}/renifleurExclusif. Le carnet de notes du Renifleur : le résumé rédigé après l'arrivée de cette course, le cheval retenu pour la prochaine fois, et surtout les partants qui avaient déjà été repérés à leur dernière sortie. Contenu éditorial, lisez ce que ça vaut.
/v1/topsTop N du jour par indicateur (34 indicateurs : ELO_Cheval, Cote_BZH, Note_IA, IMDC, Popularite...). Paramètres : indicateur (requis), n (1-50), discipline, date.
/v1/value-betsLes value bets du jour (composite maison Cote_BZH_fiable). Paramètre n (1-30).
/v1/chevaux?recherche=Recherche d'un cheval par nom -> renvoie les idcheval à utiliser ensuite.
/v1/chevaux/{id}/historiqueLes dernières courses du cheval (max 50 par appel, filtre discipline).
/v1/chevaux/{id}/statsStats agrégées du cheval (periode_jours optionnel).
/v1/chevaux/{id}/lecturesLes lectures expertes actives du jour pour ce cheval.
/v1/chevaux/{id}/renifleurExclusif. Toutes les notes que Le Renifleur a écrites sur ce cheval, de la plus récente à la plus ancienne. Paramètre n_last (1-40, défaut 10).
/v1/personnes?recherche=&type=Recherche jockey/driver/entraîneur (moteur sémantique + repli) -> renvoie les ids.
/v1/personnes/{type}/{id}/statsStats d'un jockey ou entraîneur : periode_jours, discipline, hippodrome (jockey uniquement).
/v1/methodesLa liste de vos méthodes BZH enregistrées (créées dans ChatBZH). Pour les appliquer/backtester par API : voir /v1/chat ci-dessous.

Sortie tableur : ajoutez ?format=csv sur /programme, /courses/{date}/{rc}, /cotes, /cotes/historique, /tops, /value-bets, /journees/{date}/partants, /schema, /chevaux/{id}/historique, /courses/{date}/{rc}/rapports, /rapports et /performances pour recevoir un CSV UTF-8 (Excel FR, séparateur point-virgule). Les douze adresses en entier, prêtes à copier, avec les dates que chacune accepte.

Spec machine : openapi.json (OpenAPI 3, importable dans Postman, Swagger, ou un générateur de client).

Exemple de réponse (extrait de /v1/courses/{date}/{rc}/cotes) :

{
  "data": {
    "date": "2026-08-08",
    "code_course": "R1C4",
    "source": "pmu",
    "updated_at": "2026-08-08T18:01:53+02:00",
    "partants": [
      { "num": 1, "cheval": "EXEMPLE DU BOIS", "cote": 4.8,
        "cote_ref": 7.5, "evolution_ouverture": -2.7,
        "evolution_ouverture_pct": -36.0, "sens_ouverture": "baisse",
        "cote_5min_ago": 5.2, "evolution_5min": -0.4,
        "comparaison_age_sec": 312,
        "tendance": "-", "tendance_pct": 4.1,
        "favori": false, "fraicheur_label": "< 2 min", "np": false }
    ]
  },
  "meta": { "generated_at": "2026-08-08T18:01:53+02:00", "temps_reel": true }
}

Trois reculs, trois durées différentes

C'est le point sur lequel on se trompe le plus facilement, alors autant l'écrire noir sur blanc. La réponse contient trois indicateurs de mouvement. Ils ne mesurent pas la même chose et ils ne portent pas sur la même durée.

ChampsSur quelle duréeCe qu'on peut en dire
tendance, tendance_pct Depuis le dernier rapport PMU. Le PMU ne publie pas quand ce rapport a été pris. « La cote est en baisse de 4 % sur le dernier rapport. » N'annoncez aucune durée chiffrée.
cote_5min_ago, evolution_5min, comparaison_age_sec La durée exacte est dans comparaison_age_sec. Le nom du champ dit 5 minutes, la réalité est « au moins 5 minutes » : sur une course peu consultée l'écart peut porter sur bien plus. Lisez comparaison_age_sec avant d'écrire une durée. Les deux champs valent null tant qu'aucune mesure assez ancienne n'existe pour cette course, c'est normal et ce n'est pas une erreur.
cote_ref, evolution_ouverture, evolution_ouverture_pct, sens_ouverture Depuis la cote de référence de la matinée, donc la journée entière. Le recul le plus large et le plus parlant : « passé de 7,5 ce matin à 4,8 maintenant, soit 36 % de moins ».

Comment le lire. Une cote qui baisse veut dire que l'argent rentre sur le cheval, une cote qui monte qu'il est délaissé. C'est une photographie de ce que le public a joué, pas une prévision de l'arrivée. Un cheval massivement joué perd souvent, un cheval délaissé gagne parfois : le mouvement de marché est un élément de contexte parmi d'autres, jamais un signal à suivre seul.

La série complète : /cotes/historique

Les trois reculs ci-dessus sont des écarts entre deux points. Si vous voulez la courbe, c'est /v1/courses/{date}/{rc}/cotes/historique. Elle existe parce que nous l'enregistrons : le flux PMU ne publie que le rapport du moment et celui du matin, jamais la série. Une journée non collectée est perdue définitivement, et c'est pour cela que la collecte tourne depuis le 27/07/2026 alors qu'aucun écran ne l'exploitait encore.

Temps restant avant le départUn relevé toutes les
Plus de 2 h30 minutes
Entre 2 h et 30 min10 minutes
Moins de 30 min5 minutes

Ce que la collecte ne couvre pas, dit franchement. La finesse ne peut jamais dépasser la fréquence à laquelle notre collecteur est appelé, et elle est aujourd'hui de 5 minutes. Les toutes dernières minutes avant le départ sont donc couvertes par un ou deux relevés, pas davantage, même si le tableau annonce un palier plus serré. Aucune course antérieure au 27/07/2026 n'a de série, et un jour sans relevé renvoie un 404 : c'est une absence de donnée, pas une panne.

Un relevé ressemble à ceci. Le champ cotes donne la cote du moment par numéro, cotes_reference celle du matin. Un numéro absent d'un relevé n'avait pas de cote à cet instant : soit c'est un non-partant, soit le marché n'était pas encore ouvert. Le PMU renvoie 0 dans ce second cas ; nous ne le publions pas comme une cote, parce qu'un 0 fausse toute moyenne et fait exploser tout calcul qui divise par la cote. En CSV, la cellule est simplement vide. Un relevé où aucun numéro n'avait de cote n'apparaît pas du tout.

{
  "data": {
    "date": "2026-08-08", "code_course": "R1C4",
    "hippodrome": "Vichy", "heure_depart": "13:47", "nb_mesures": 24,
    "premiere_mesure": "07:12:03", "derniere_mesure": "13:46:10",
    "mesures": [
      { "t": 1785138324, "heure": "12:25:24",
        "cotes":           { "1": 4.8, "2": 12.3, "3": 3.1 },
        "cotes_reference": { "1": 7.5, "2": 11.0, "3": 3.4 } }
    ]
  }
}

En ?format=csv, la même série arrive en forme longue : une ligne par relevé et par partant, sept colonnes qui ne changent jamais (date;code_course;t;heure;num;cote;cote_ref). C'est volontaire. Une colonne par numéro se lirait mieux à l'oeil, mais le nombre de colonnes changerait d'une course à l'autre et votre requête Power Query casserait au premier changement de partants. Là, la même requête marche sur toutes les courses, et vous pouvez empiler plusieurs courses dans un seul tableau puisque chaque ligne porte sa date et son code course.

Les rapports PMU : deux masses, deux montants

Le PMU ne publie pas un rapport par pari, il en publie deux. Le flux nu rend la masse point de vente, celle des bureaux et des cafés. Le même flux appelé avec ?specialisation=INTERNET rend la masse en ligne, dont les types de pari portent le préfixe E_ et les libellés la minuscule (« e-Trio »). Elles ne paient pas la même somme, et l'écart n'est pas un détail : le 20 juillet 2026 sur la R1C5, le couplé placé 15-7 valait 17,80 € au point de vente et 30,10 € en ligne, soit 69 % de plus. Un script qui ignore la distinction compare des nombres qui ne se comparent pas. C'est pourquoi masse est un champ nommé et jamais un préfixe à deviner.

Chaque ligne porte de même deux montants, et pour la même raison. Le PMU publie rapport_pour_1_euro. Ce qu'un parieur encaisse pour un ticket vaut ce nombre multiplié par la mise de base du pari, qui n'est pas la même partout : 1 € au simple gagnant en ligne, 2 € au point de vente, 1,50 € au quarté, 3 € au Multi et au 2 sur 4. Nous servons donc aussi rapport_pour_la_mise_de_base et mise_de_base : personne n'a à refaire la multiplication, ni à se tromper dessus.

ChampCe qu'il vaut
masseen_ligne ou point_de_vente.
pariLe type sans la marque de masse : E_TRIO et TRIO donnent tous deux TRIO. Le filtre ?pari= accepte les deux écritures.
libelleLe libellé du PMU, et il compte : au Multi, « Multi en 4 » et « Multi en 7 » sont deux rapports différents sur la même combinaison.
nb_gagnantsFractionnaire (Flexi, mises partielles). null quand le PMU ne le publie pas : ce n'est pas zéro, qui voudrait dire « personne ».
payefalse quand le rapport vaut zéro. Ces lignes sont servies par défaut : elles prouvent que la formule était proposée et que personne ne l'a trouvée, ce qui n'est pas une absence. ?payants=1 ne garde que celles qui ont payé.
Ce que la collecte ne couvre pas, dit franchement. Elle commence au 1er janvier 2026 : aucune course antérieure n'a de rapport ici. Sur les journées collectées, environ 2 500 courses n'ont aucun rapport définitif publié par le PMU, essentiellement des courses internationales et du PMH : le flux y répond 204, et c'est une réponse, pas une panne. Le meta.collecte de chaque réponse donne les bornes exactes et le nombre de courses couvertes, pour que vous n'ayez pas à les deviner.

Le taux de couverture : ce qu'il est, ce qu'il n'est pas

/v1/performances sert une mesure et une seule : la part des courses où la combinaison gagnante d'un pari était entièrement contenue dans nos N premiers chevaux. Elle est calculée sur toutes les courses de la période où le PMU a publié cette formule, jamais sur une sélection des meilleures, et le dénominateur voyage avec le taux (courses, couvert) pour que vous puissiez le recompter.

Chaque ligne porte tickets, le nombre de combinaisons à jouer, et engage_eur, leur coût. Un taux sans son prix ne veut rien dire : couvrir un trio avec six chevaux, ce sont vingt combinaisons, et ça n'a pas le même sens que de le couvrir avec quatre.

Ce n'est ni un rendement ni une promesse. Ces nombres disent ce qu'une combinaison a payé, pas ce qu'un joueur a gagné : personne n'a joué toutes les courses. Le champ meta.avertissement le porte dans chaque réponse, et il n'est pas décoratif. Turf.bzh ne prend aucun pari et ne promet aucun gain.
GET /api/v1/performances?fenetre=30j&famille=DEUX_SUR_QUATRE&api_key=VOTRE_CLE
GET /api/v1/courses/2026-07-20/R1C5/rapports?masse=en_ligne&payants=1
GET /api/v1/rapports?date=2026-07-20&pari=TRIO&format=csv

Les arrivées : ce qui a changé le 2 août 2026

Un client nous a signalé qu'il n'obtenait pas toutes les arrivées d'une réunion d'Enghien. Il avait raison, et le problème dépassait largement Enghien. Trois défauts ont été corrigés le même jour. Nous les documentons parce qu'ils changent ce que vos scripts reçoivent.

DéfautPortée mesurée, sur 30 jours et 1 609 courses terminées
Une course dont un seul partant était déclaré non-partant disparaissait entièrement de la réponse555 courses absentes, soit 34,5 %
Les chevaux disqualifiés étaient triés avant le gagnant, avec un rang 0776 arrivées polluées, soit 48,2 %, dont 94 qui ne contenaient aucun cheval classé
L'ordre d'arrivée était tronqué aux cinq premiersune course compte 9,2 chevaux classés en moyenne

Depuis le correctif, 97,6 % des courses terminées sont servies contre 65,5 % auparavant. Les 2,4 % restantes sont des courses dont la source n'a classé qu'une partie du champ : nous préférons ne rien publier plutôt qu'une arrivée à moitié vraie.

Ce que cela change pour vous. L'ordre d'arrivée est désormais complet par défaut. Si votre script s'appuyait sur exactement cinq lignes, ajoutez ?arrivee=top5 et rien ne bouge. Les rangs sont tous chiffrés : les disqualifiés et les arrêtés ne figurent plus dans arrivee, ils sont comptés dans le champ non_classes, à côté de non_partants.

Une réunion passée en un seul appel

/v1/resultats ne servait que la journée en cours. Reconstituer une réunion passée obligeait à appeler /v1/courses/{date}/{rc}/arrivee course par course. Ce n'est plus le cas : l'endpoint lit maintenant l'historique de la base.

GET /api/v1/resultats?date=2026-08-01&hippodrome=enghien

{
  "data": {
    "date": "2026-08-01", "est_aujourdhui": false,
    "hippodrome": "enghien", "nb_courses": 8,
    "results": [
      { "code_course": "R3C3", "hippodrome": "Enghien",
        "discipline": "Trot attelé", "heure": "15h47",
        "nombre_partants": 10, "non_partants": 1, "non_classes": 1,
        "arrivee_complete": true, "rapport_sg": 10.7,
        "arrivee": [
          { "rank": 1, "numero": 4,  "cheval": "MISTHOS CHRISTAL", "rapport_sp": 2.0 },
          { "rank": 2, "numero": 3,  "cheval": "MONTJOIE",         "rapport_sp": 1.4 }
        ] }
    ]
  },
  "meta": { "date": "2026-08-01", "arrivee": "complete", "hippodrome": "enghien" }
}

Le filtre hippodrome est une recherche partielle insensible à la casse : enghien, Enghien et ENGH donnent le même résultat. Une date future renvoie une 422 : cet endpoint sert les arrivées, pas le programme. Une journée absente de la base renvoie nb_courses: 0 et une note qui le dit, jamais une erreur.

Sur le jour en cours, la base n'est alimentée que le lendemain matin pour certaines réunions. Le champ pending_db_update liste alors les courses déjà courues dont l'arrivée n'est pas encore ingérée. Pour une arrivée immédiate le jour J, /v1/courses/{date}/{rc}/arrivee reste la bonne porte : il interroge le flux PMU en direct. Sur une date passée, pending_db_update est toujours vide : ce qui manque manquera.

Le carnet de notes du Renifleur

Après chaque réunion, Le Renifleur relit les commentaires officiels de fin de course, raconte l'épreuve en deux ou trois phrases et retient un cheval qui mérite un coup d'oeil la prochaine fois. C'est du contenu écrit, produit chaque jour, que vous ne trouverez nulle part ailleurs. Deux endpoints l'exposent.

Sur une course

curl -H "X-API-Key: VOTRE_CLE" \
  "https://www.turf.bzh/api/v1/courses/2026-07-25/R1C4/renifleur"
{
  "data": {
    "date": "2026-07-25",
    "code_course": "R1C4",
    "carnet_de_course": {
      "resume": "Comme une pâtissière qui sort son gâteau du four une seconde trop tard, ...",
      "cheval_retenu": { "numero": 8, "nom": "LORIGRE",
                         "raison": "A pointé en retard à l'usine, mais a fait les heures sup'." },
      "url": "/pronostics-pmu-25072026-R1C4.html"
    },
    "deja_reperes": [
      { "num": 3, "cheval": "PASSE COMPOSE", "repere_le": "2026-07-20", "repere_a": "Vichy",
        "portait_le_numero": 9, "jours_depuis": 6, "course_origine": "R2C1",
        "note": "Bloquée aux six cents, elle n'a jamais pu sortir." }
    ],
    "nb_deja_reperes": 1,
    "avertissement": "Note de course, pas un pronostic. ..."
  },
  "meta": { "date": "2026-07-25", "code_course": "R1C4" }
}

carnet_de_course vaut null tant que la course n'a pas été courue et débriefée. deja_reperes est le champ le plus intéressant : ce sont les partants du jour qui étaient passés sous le nez du Renifleur à leur sortie précédente. La note n'apparaît que sur leur course de rentrée, et pendant 60 jours au maximum.

Sur un cheval

curl -H "X-API-Key: VOTRE_CLE" \
  "https://www.turf.bzh/api/v1/chevaux/1347094367/renifleur?n_last=5"

Renvoie nb_notes et la liste des notes. nb_notes: 0 signifie que ce cheval n'a jamais été repéré sur la période conservée, soit 400 jours. Environ un cheval sur seize parmi les partants du jour porte une note active.

Ce que ça vaut, honnêtement

Nous mesurons ce carnet et nous publions le résultat, parce qu'un chiffre non vérifié ne vaut rien. Sur 2 411 rentrées analysées :

Arrivée à la rentréeChevaux repérésTous les partantsAttendu par la cote
Dans les trois premiers35,9 %27,8 %34,5 %
Dans les quatre premiers46,7 %37,0 %44,7 %
Gagnant11,9 %9,3 %12,0 %

Lecture : Le Renifleur repère des chevaux nettement meilleurs que la moyenne du peloton. Mais la troisième colonne dit l'essentiel : à cote égale, l'écart n'est pas significatif. Ces chevaux partent déjà à une cote qui tient compte de leur valeur. Un euro joué au gagnant sur chacun d'eux en rendrait 0,72.

Ne construisez pas un modèle de pari sur ce champ. Utilisez-le pour ce qu'il est : du contenu à afficher, un fil narratif pour vos utilisateurs, une mémoire de course. Chaque réponse porte un champ avertissement qui reprend ces chiffres, de sorte qu'un agent qui consomme l'API ne puisse pas les ignorer.

Droits d'usage

Ces textes sont produits et financés par turf.bzh. Leur réutilisation publique suppose une attribution visible à turf.bzh et un lien vers la course d'origine, fourni dans le champ url. La republication en masse sans attribution n'est pas couverte par la Licence API : voir les conditions.

Exemples copier-coller (Python, Node.js, PHP)

Le même appel dans trois langages : on lit le programme du jour, on affiche le contenu. Explorez la structure renvoyée, puis ciblez ce qui vous intéresse.

Python (requests)

import requests

r = requests.get("https://www.turf.bzh/api/v1/programme",
                 headers={"Authorization": "Bearer tbz_live_VOTRE_CLE"},
                 timeout=15)
j = r.json()
if "error" in j:
    print("Erreur:", j["error"]["message"])
else:
    print(j["data"])   # explorez, puis ciblez j["data"][...] selon vos besoins

Node.js (18+ : fetch intégré)

const KEY = "tbz_live_VOTRE_CLE";

const res = await fetch("https://www.turf.bzh/api/v1/programme", {
  headers: { Authorization: `Bearer ${KEY}` }
});
const j = await res.json();
if (j.error) {
  console.error("Erreur:", j.error.message);
} else {
  console.log(j.data);
}

PHP (cURL)

<?php
$ch = curl_init("https://www.turf.bzh/api/v1/programme");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer tbz_live_VOTRE_CLE"],
    CURLOPT_TIMEOUT        => 15,
]);
$j = json_decode(curl_exec($ch), true);
curl_close($ch);
if (isset($j["error"])) {
    echo "Erreur: " . $j["error"]["message"];
} else {
    print_r($j["data"]);
}

Postman, n8n et Make

Postman

Deux façons de démarrer : importez notre collection Postman prête (tous les endpoints rangés par thème), ou importez directement la spec openapi.json (Postman > Import > Link ou File).

  1. Importez la collection dans Postman.
  2. Ouvrez l'onglet Variables de la collection, remplacez api_key par votre clé, ajustez date et rc.
  3. L'authentification Bearer est déjà posée au niveau de la collection : lancez n'importe quelle requête.

n8n

Ajoutez un nœud HTTP Request : méthode GET, URL https://www.turf.bzh/api/v1/programme. Dans Authentication, choisissez Generic Credential Type > Header Auth, avec le nom Authorization et la valeur Bearer tbz_live_VOTRE_CLE. Enchaînez ensuite un nœud de traitement ou une notification (Telegram, e-mail...).

Make (ex-Integromat)

Module HTTP > Make a request : URL de l'endpoint, méthode GET, et un en-tête Authorization = Bearer tbz_live_VOTRE_CLE. Activez Parse response pour manipuler le JSON directement.

Depuis un navigateur (fetch cross-origin), l'API accepte les requêtes : elle renvoie les en-têtes CORS nécessaires et répond aux pré-vérifications. Mais ne mettez jamais votre clé en dur dans une page web publique (elle serait visible dans le code) : pour un outil public, faites transiter l'appel par votre propre serveur (relais), qui seul détient la clé.

POSTChatBZH par API - /v1/chat

Posez une question à ChatBZH comme sur le site : l'agent choisit ses outils (programme, cotes live, indicateurs, vos méthodes...), analyse, et renvoie une réponse complète en un seul JSON. Cet endpoint consomme les crédits IA de votre compte (mêmes règles que le site : plafond 300 cr par question, forfait 60 cr pour appliquer une méthode, 100 cr pour un backtest).

curl -X POST "https://www.turf.bzh/api/v1/chat" \
  -H "Authorization: Bearer tbz_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"question": "Applique ma méthode Borda V2 sur R1C4", "mode": "standard", "max_credits": 100}'
  • question (requis) : votre question, comme dans le chat.
  • mode : standard (défaut) ou expert (analyse approfondie, coûte ~3x plus).
  • max_credits (5-300, défaut 300) : votre plafond d'acceptation. Si l'estimation dépasse, refus 402 cost_exceeds_max_credits AVANT tout débit - l'équivalent API de la modale de confirmation du site.
  • Réponse : reponse (texte), tools_utilises, credits.factures, credits.solde_restant, forfait_applique.
  • Patience : un tour peut durer 30 à 180 secondes (l'agent enchaîne plusieurs outils). Prévoyez un timeout client de 240 s. Limite : 10 questions/minute, 1 question à la fois par compte.
  • Chaque appel est autonome (pas de mémoire de conversation) : mettez tout le contexte dans la question.

Erreurs

Toute erreur renvoie un JSON uniforme :

{ "error": { "code": "subscription_required", "message": "...", "status": 403, "doc": "..." } }
HTTPcodeSignification
401missing_key / invalid_keyClé absente, inconnue ou révoquée. Générez / régénérez sur Mon accès API.
403api_lockedLicence API non débloquée sur ce compte.
403subscription_requiredAbonnement inactif. La licence reste acquise : réabonnez-vous et ça repart.
402insufficient_credits(/chat) Solde crédits IA insuffisant. Rechargez sur chatbzh-boutique.php.
402cost_exceeds_max_credits(/chat) L'estimation dépasse votre max_credits : augmentez-le ou simplifiez la question. Rien n'a été débité.
404not_foundRoute ou course inconnue (la réponse liste parfois les courses disponibles).
405method_not_allowedGET partout, sauf /v1/chat qui attend un POST.
422invalid_paramsParamètre mal formé (date, code course, bornes).
429rate_limitedLimite technique atteinte. Respectez l'en-tête Retry-After.
500 / 503internal_error / upstream_unavailableIncident côté serveur ou source amont : réessayez un peu plus tard.

Limites et bonnes pratiques

  • 60 requêtes/minute et 10 000/jour par clé (limite technique anti-abus, pas un compteur commercial). Réponse 429 + Retry-After au-delà.
  • /v1/chat : 10 questions/minute et une seule question à la fois par compte (le solde de crédits IA borne naturellement le reste).
  • Les cotes live sont rafraîchies côté serveur avec un cache adaptatif : interroger plus d'une fois toutes les 30 s n'apporte rien.
  • Ne mettez JAMAIS votre clé dans du code visible (page web publique, dépôt GitHub public, capture d'écran). En cas de fuite : régénérez.
  • Usage personnel uniquement : la revente, la redistribution des données ou le partage de clé sont interdits (CGV turf.bzh) et détectés par l'audit.
  • Contrat de version : sur /v1, des champs peuvent être AJOUTÉS, jamais retirés ni renommés. Codez tolérant aux champs inconnus.

Jeu responsable

Les données et indicateurs turf.bzh sont des outils d'aide à la décision pour un loisir. Aucune API, aucun indicateur, aucun modèle ne garantit un gain : les courses comportent une part d'incertitude irréductible. Automatiser ses analyses ne doit jamais devenir automatiser ses mises. Fixez-vous des limites de budget et de temps, et ne jouez que ce que vous pouvez vous permettre de perdre.

Changelog

V1 08/08/2026 - Lancement : clé API self-service, licence à vie 19,90 €, endpoints me, statut, programme, quinte, resultats, courses/{date}/{rc} (+ /cotes, /cotes/mouvements, /arrivee).

V2-V4 08/08/2026 - Données premium (indicateurs, analyse + LigneBZH, écarts, tops, value-bets, lectures, chevaux, personnes), format=csv, POST /v1/chat (crédits IA), /v1/methodes, spec openapi.json.

V5 08/08/2026 - Carnet de notes du Renifleur : /v1/courses/{date}/{rc}/renifleur et /v1/chevaux/{id}/renifleur. Contenu éditorial exclusif, avec la mesure de ce qu'il vaut publiée dans la section dédiée.

30/07/2026 Pack Excel refait, et un correctif sur /value-bets. Le classeur contient maintenant les trois requêtes Power Query prêtes à l'emploi : coller la clé et cliquer sur Actualiser tout suffit. Séparément, /v1/value-bets?format=csv renvoyait du JSON au lieu du CSV, ce qui rendait la troisième requête inopérante même correctement saisie ; c'est corrigé. Retéléchargez le pack si vous l'aviez pris avant cette date.

05/08/2026 Les rapports PMU s'ouvrent, les deux masses, et la mesure de ce qu'ils ont couvert. Trois adresses nouvelles. /v1/courses/{date}/{rc}/rapports et /v1/rapports servent les rapports definitifs COLLECTES, 306 088 sur 9 038 courses depuis le 1er janvier, point de vente ET en ligne : /arrivee interrogeait le PMU en direct et ne servait donc que la journee en cours, sur une seule masse. Chaque ligne porte les DEUX montants, rapport_pour_1_euro et rapport_pour_la_mise_de_base, parce que la mise de base va de 1 a 3 EUR selon le pari et que servir un seul des deux obligeait chacun a refaire la multiplication. /v1/performances publie le taux de couverture mesure de nos selections, avec son denominateur et le cout de chaque formule. Les trois acceptent format=csv, ce qui porte a douze le nombre d'adresses qui rendent un tableau. Ce que sont les deux masses, et ce que le taux n'est pas.

03/08/2026 L'historique profond s'ouvre : une journee entiere, un dictionnaire, et la base en telechargement. Trois adresses nouvelles. /v1/journees/{date}/partants rend tous les partants d'une journee en un seul appel, cote de depart comprise : reconstituer la base demandait un appel par course, elle en demande un par journee. /v1/schema publie le taux de remplissage reel de chaque champ, qui n'etait ecrit nulle part. /v1/exports sert la base entiere en CSV compresse, un fichier par mois, avec son empreinte SHA-256. Les deux premieres acceptent format=csv, et /v1/chevaux/{id}/historique l'acceptait deja sans que ce soit ecrit : cela porte a neuf le nombre d'adresses qui rendent un tableau. Les bornes debut et fin arrivent aussi sur l'historique d'un cheval et sur les statistiques d'un jockey ou d'un entraineur.

31/07/2026 L'historique des cotes s'ouvre : /v1/courses/{date}/{rc}/cotes/historique. Toute la série d'une course, du matin au départ, là où /cotes ne donnait que l'instantané et /cotes/mouvements qu'une fenêtre de deux heures. La donnée était collectée depuis le 27/07/2026 sans que personne puisse la lire ; c'est réparé. Sortie format=csv en forme longue, ce qui porte à six le nombre d'adresses qui rendent un tableau. Ce que la collecte couvre, et ce qu'elle ne couvre pas.

31/07/2026 Une page pour les six adresses en CSV. Elles étaient annoncées en une phrase, sans jamais être écrites en entier. Elles le sont désormais, prêtes à copier, avec les dates que chacune accepte et le piège du séparateur décimal sous Excel français. Aller à la section.

Docs 08/08/2026 - Documentation orientée cas d'usage : premiers pas en 5 min, galerie, tutoriels (Excel/Power Query, alerte Telegram, bot, ChatBZH), exemples Python/Node/PHP, outils n8n/Make, et modèles téléchargeables (pack Excel, collection Postman, bot).