Aucune page ne correspond. Essayez « écarts », « stratégie », « API » ou « abonnement ».
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.
Vous ne programmez pas ? Cette page n'est pas pour vous, et vous n'en avez pas besoin.
La même chose qu'ici, à plat et sans mise en page, pour la donner à un assistant ou la garder hors ligne. Le contenu est reconstruit à chaque téléchargement depuis le code de l'API : il ne peut pas décrire une version qui n'est plus en service.
Version 1.2.0 : les non partants sont identifiés. Chaque partant porte
le champ partant, chaque course distingue le peloton engagé du peloton réel,
et pour la journée en cours les forfaits sont relevés directement sur le flux PMU, seule
source qui les publie avant l'arrivée. Deux paramètres s'ajoutent,
non_partants et non_partants_direct. Aucun champ n'est retiré ni
renommé.
Jusqu'au 12/09/2026, /v1/courses/{date}/{rc} et
/v1/journees/{date}/partants servaient les non partants au même titre que les
autres chevaux, sans distinction. Un cheval déclaré forfait n'est pas absent de la
base : il y figure avec Rank = NP, et aucun des deux endpoints
ne filtrait cette valeur. Portée mesurée sur les 8 530 courses courues du
1er janvier au 8 juin 2026 : 3 768 lignes
portent ce code, réparties sur 2 741 courses, soit
32,1 % des courses de la période. La production en compte
davantage depuis ; la proportion, elle, ne dépend pas de la fenêtre.
Ce que porte désormais chaque partant :
| Champ | Ce que c'est |
|---|---|
partant | booléen. false : le cheval ne prend pas le départ. En CSV, 1 ou 0. |
statut_partant | partant ou non_partant, pour la lecture humaine. |
source_statut | sur un forfait seulement : provenance de l'information, base ou pmu_direct. |
Les autres codes de Rank ne désignent pas des forfaits.
D (disqualifié), A (arrêté), T (tombé),
R et N s'appliquent à des chevaux
qui ont pris le départ et qui restent donc des partants. NP est le seul code
qui signale un forfait. Un filtre écrit côté client sur Rank doit être
vérifié sur ce point.
Ce sont deux informations distinctes, et la réponse sert les deux. Le peloton engagé est celui sur lequel les conditions de course et les rapports de référence du matin ont été établis ; le peloton réel est celui qui prend le départ.
| Champ | Ce qu'il compte |
|---|---|
nombre_partants_initial | le peloton engagé, forfaits compris. |
nombre_non_partants | les forfaits connus à l'instant de l'appel. |
nombre_partants_reel | ceux qui prennent le départ. initial = réel + non partants, sans exception. |
numeros_non_partants | leurs numéros, triés. |
nombre_partants_declare | le chiffre annoncé par la source, tel quel. |
Pourquoi le chiffre annoncé est servi séparément. La colonne
nombre_partants de la source ne permet pas de déduire le peloton réel par
soustraction. Sur les 2 741 courses à forfait de la période mesurée, elle vaut
le peloton engagé 85,6 % du temps, le peloton réel
10,0 %, et ni l'un ni l'autre 4,4 %.
nombre_partants_initial et nombre_partants_reel sont donc
comptés ligne à ligne sur la même source, et le chiffre annoncé est servi tel quel sous
nombre_partants_declare. Quand les deux divergent,
note_partants_declare le signale. Sur les 8 530 courses mesurées,
l'écart concerne 398 courses, soit 4,7 %.
Le code NP entre en base par deux chemins : les forfaits déclarés tôt,
présents dès l'ingestion du programme, et les autres, qui n'arrivent qu'avec l'ingestion
de l'arrivée, donc après la course. Relevé du 08/06/2026 : 678 lignes, 674 encore
sans résultat, et 4 forfaits déjà enregistrés, pour une journée qui en comptera entre 26
et 52. Pendant la journée en cours, la base ne porte donc qu'une partie des forfaits, et
jamais ceux de dernière minute.
Le flux PMU les publie dès leur déclaration. /v1/courses/{date}/{rc}
l'interroge par défaut pour toute course du jour non encore
courue : un seul appel sortant, mutualisé par un cache de 45 secondes. Si le flux ne
répond pas, la réponse est servie avec le relevé de la base et
non_partants_direct passe à false. Le champ
note_non_partants indique dans chaque réponse la provenance du relevé.
Sur /v1/journees/{date}/partants, le relevé direct est en option
(non_partants_direct=1) : une journée compte jusqu'à une quarantaine de
courses, soit autant d'appels sortants pour une seule requête. Activé, il relève en
parallèle les courses non encore courues, dans la limite de 25 appels par requête. Sur une
journée passée le paramètre est sans effet : la base porte alors le relevé
définitif.
non_partants| Valeur | Ce que vous recevez |
|---|---|
non_partants=inclus | tous les chevaux, chacun marqué. C'est le défaut : le nombre de lignes rendues est inchangé. |
non_partants=exclus | seulement ceux qui prennent le départ. |
non_partants=seuls | seulement les forfaits, pour un rapprochement. |
Le filtre ne porte que sur la liste rendue : les 5 nombres ci-dessus et les numéros des forfaits sont identiques dans les 3 cas.
# la fiche d'une course du jour, forfaits de dernière minute compris curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/courses/2026-09-16/R1C1" # le même peloton, sans les forfaits curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/courses/2026-09-16/R1C1?non_partants=exclus" # la journée entière, avec le relevé PMU en direct curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/journees/2026-09-16/partants?non_partants_direct=1"
Compatibilité. Le mode par défaut rend les mêmes lignes qu'avant,
nombre_partants conserve sa valeur, et les champs ajoutés le sont en plus.
Aucun champ n'est retiré ni renommé. Une seule sortie change dans un fichier
existant : en CSV, un booléen faux s'écrit désormais 0 et non plus une
cellule vide, qui se confondait avec une donnée manquante.
Sur la journée, la liste courses donne les 5 nombres course par course, et
nb_courses_ecart_declare le nombre de courses présentant un écart avec le
chiffre annoncé.
champsJusqu'au 11/09/2026, 34 colonnes de la base ne sortaient sur aucun
endpoint, dont 16 figuraient au catalogue de
/v1/tops : le top 10 du jour par ELO_Proprio était
disponible, la lecture de ce même ELO sur le partant ne l'était pas. Ces colonnes sortent
désormais, l'ELO du propriétaire et celui de l'éleveur compris.
Les 16 indicateurs qui manquaient sur le partant :
| Champ | Ce que c'est |
|---|---|
ELO_Proprio | ELO du propriétaire, même échelle que les autres ELO |
ELO_Eleveur | ELO de l'éleveur |
Sigma_Horse | fiabilité de l'ELO cheval : plus c'est haut, plus l'historique est fourni |
IMDC | montée (+) ou descente (-) de catégorie, 5 points par échelon |
Synergie_JCh | synergie entre le driver et le cheval |
Turf_Points, TPch_90, Moy_TPch_90 | points turf du cheval, brut et moyenné |
Rang_J, TPJ_90 | classement et points turf du driver ou jockey |
distanceRecord_sec | record chronométrique ramené à la distance |
Taux_Incident | taux d'incident du cheval |
nombre_victoire, nombre_place | le palmarès en clair, pas seulement en taux |
Gains_Totaux, Gains_Course | gains de carrière, et gains moyens par course |
Et 15 de plus qui ne figuraient dans aucun catalogue mais qui servent
tous les jours : Courses_courues (l'effectif derrière les taux),
incident, Repos (jours depuis la dernière sortie),
Classe_Groupe, Moy_Alloc, ferrure_prec,
changement_ferrure, changement_driver,
Evo_Distance, Evo_Poids, et les points turf sur 365 jours.
champsDepuis le 12/09/2026, ces mêmes adresses acceptent aussi non_partants :
voyez le volet ci-dessus.
Il s'applique à /v1/courses/{date}/{rc},
/v1/journees/{date}/partants et
/v1/courses/{date}/{rc}/indicateurs.
| Valeur | Ce que vous recevez |
|---|---|
champs=base | exactement les colonnes d'avant le 11/09/2026, dans le même ordre. Pour un script déjà écrit qui veut un fichier identique à celui d'hier. |
champs=indicateurs | base plus le propriétaire, l'éleveur et les 16 indicateurs du tableau ci-dessus. |
champs=tout | tout ce que la base contient d'exploitable. C'est le défaut, vous n'avez rien à écrire. |
curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/courses/2026-09-10/R1C3" # le jeu d'hier, si votre script lit les colonnes par position curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/journees/2026-09-10/partants?champs=base&format=csv"
Vos scripts ne cassent pas. Les colonnes ajoutées le sont à la fin : en CSV, les positions 1 à 45 ne bougent pas, et aucune colonne n'a été retirée ni renommée. Les exports mensuels suivent la même liste et sont régénérés sur tout l'historique.
La vérification de chaque formule sur la base entière, préalable à leur documentation, a mis au jour deux erreurs dans le dictionnaire :
Taux_Victoire, Taux_Place et Taux_Incident sont
des fractions entre 0 et 1, pas des pourcentages :
0.21 se lit 21 %. /v1/schema les annonçait en
« pourcentage » depuis sa mise en service. Si vous affichiez la valeur telle
quelle, vous lisiez 0,21 % au lieu de 21 %.Taux_Place compte la victoire comme une place -
(nombre_victoire + nombre_place) / Courses_courues, vérifié sur
265 589 lignes sur 266 483 - alors que nombre_place ne compte
que les places hors victoire. Les deux ne se lisent donc pas de la même façon.Deux autres formules sont désormais vérifiées et publiées :
Taux_Victoire = nombre_victoire / Courses_courues et
Gains_Course = Gains_Totaux / Courses_courues, exactes l'une et l'autre sur
266 483 lignes sur 266 483.
Le dictionnaire complet est sur /v1/schema : il
décrit maintenant 80 champs au lieu de 49, chacun avec son type, son
unité, son taux de remplissage réel et le jeu champs qui le fait sortir.
Le tableau des partants affiche 5 probabilités par cheval, une par forme de pari. L'API n'en servait que 2. Les 3 autres existaient en base depuis l'origine sans avoir jamais été exposées.
| Champ | Bouton du tableau des partants | Ce qu'il vaut |
|---|---|---|
IA_Gagnant | IA GAGNANT | probabilité de finir 1er |
IA_Couple | IA COUPLE | nouveau : probabilité de finir dans les 2 premiers |
IA_Trio | IA TRIO | nouveau : probabilité de finir dans les 3 premiers |
IA_Multi | IA MULTI | nouveau : probabilité de finir dans les 4 premiers |
IA_Quinte | IA QUINTE | probabilité de finir dans les 5 premiers |
Elles sortent sur /v1/courses/{date}/{rc}, son /dossier,
/indicateurs, /journees/{date}/partants et /quinte.
Sur /indicateurs les noms sont en minuscules, comme le reste de cet endpoint :
ia_gagnant, ia_couple, ia_trio,
ia_multi, ia_quinte. Ce dernier y apparaît d'ailleurs pour la
première fois : l'endpoint le lisait depuis sa mise en service sans jamais le rendre.
Pour classer une journée entière sur l'une d'elles, /v1/tops les acceptait
déjà toutes les cinq :
curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/tops?indicateur=IA_Couple&n=20"
Le contrat ne bouge pas : ce sont des champs ajoutés, aucun retiré ni renommé. Vos scripts
existants continuent de fonctionner sans retouche. Les taux de remplissage de chaque champ
sont dans /v1/schema, à consulter avant de bâtir une
méthode dessus.
Jusqu'ici, on découvrait la limite en la dépassant : le 429 arrivait sans
prévenir. Désormais chaque réponse porte l'état du compteur, sans appel
supplémentaire et sans coût de quota, à deux endroits au choix.
Dans le corps, sous meta.quota :
{
"data": { ... },
"meta": {
"generated_at": "2026-09-16T01:23:26+02:00",
"quota": {
"limite_minute": 60, "restant_minute": 57, "reset_minute_s": 34,
"limite_jour": 10000, "restant_jour": 9873, "reset_jour_s": 51234
}
}
}
Et en en-têtes HTTP, pour ceux qui préfèrent ne pas lire le corps :
| En-tête | Ce qu'il vaut |
|---|---|
X-RateLimit-Limit | appels autorisés par minute (60) |
X-RateLimit-Remaining | appels restants sur la minute en cours, celui-ci déduit |
X-RateLimit-Reset | nombre de secondes avant remise à zéro de la minute. Ce n'est pas un horodatage |
X-RateLimit-Limit-Day | appels autorisés par jour (10 000) |
X-RateLimit-Remaining-Day | appels restants sur la journée |
X-RateLimit-Reset-Day | secondes avant minuit Europe/Paris |
curl -sSD - -o /dev/null \
-H "Authorization: Bearer VOTRE_CLE" "https://www.turf.bzh/api/v1/statut"
Trois points à connaître. Les mêmes valeurs partent sur un 429, où le bloc
quota rejoint retry_after et scope dans
error. Les en-têtes sont exposés par Access-Control-Expose-Headers,
donc lisibles depuis un fetch() de navigateur. Et si le compteur est
momentanément indisponible, il n'y a ni bloc ni en-tête : mieux vaut ne rien
afficher qu'afficher un chiffre faux, alors traitez l'absence plutôt que de supposer zéro.
Un assistant généraliste sait écrire et calculer, il ne sait rien des courses : il invente une adresse, confond un ordre et une probabilité, et cite un taux sans son échantillon. Le pack IA lui apprend le métier : les 36 adresses et leurs pièges mesurés, le dictionnaire des champs, la grammaire du turf, les mathématiques du pari mutuel, et ce que l'API permet vraiment en composant les appels.
Deux archives, une par outil : un CLAUDE.md avec 17 skills et 10 agents pour
Claude Code, un AGENTS.md au standard ouvert avec 17 méthodes et 11 prompts de
rôle pour Codex. Rien à installer au-delà de votre assistant : ni Python, ni
bash. Vous décompressez, vous ouvrez votre assistant dans le dossier, vous collez une phrase,
il fait le reste.
Compris dans la Licence API, sans supplément. Ce qu'il y a dedans et comment le prendre en main.
La demande revenait des intégrateurs : reconstituer une course complète imposait 7 appels de course plus un appel par partant, soit 21 pour un peloton de quatorze, et plus de mille pour une journée entière. C'est beaucoup de latence et beaucoup de code pour une seule course.
GET /v1/courses/{date}/{rc}/dossier assemble le tout côté serveur : fiche et
partants, indicateurs, classement, LigneBZH, écarts, carnet du Renifleur, ÉcurieBZH, cotes
en direct et mouvements le jour J, rapports définitifs sur une course passée, et l'historique
récent de chaque partant.
Il ne compte que pour un appel de quota, comme n'importe quelle autre adresse. Ni pondération, ni plafond dédié : les 60 requêtes par minute et les 10 000 par jour ne changent pas.
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/courses/2026-09-16/R1C4/dossier"
# plus leger : seulement ce dont vous avez besoin
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/courses/2026-09-16/R1C4/dossier?inclure=classement,cotes&historique=0"
Une section indisponible ne fait pas tomber le dossier : elle revient en
disponible: false avec sa raison, et le reste est servi.
Les paramètres et un exemple de réponse.
Deux demandes revenaient souvent : savoir à qui appartient un cheval et qui l'a élevé. Ces deux informations sont désormais servies partout où figurent les partants.
GET /v1/courses/{date}/{rc} et GET /v1/chevaux/{id}/historique
portent quatre champs de plus par partant : owner (le propriétaire),
breeder (l'éleveur ou naisseur), et leurs identifiants stables
idproprio et ideleveur - construits comme l'idjockey,
une empreinte du nom, à préférer au nom pour enchaîner les appels.
On peut aussi les chercher et lire leurs statistiques, exactement comme un jockey ou un entraîneur :
# trouver un proprietaire, puis ses stats
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/personnes?recherche=ecurie&type=proprietaire"
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/personnes/proprietaire/{idproprio}/stats?periode_jours=365"
type= accepte maintenant proprietaire et eleveur en
plus de jockey, driver et entraineur, sur la
recherche comme sur les stats (partants, chevaux distincts, victoires, placés, taux V/P,
ROI placé, répartition par discipline).
Une réserve, écrite plutôt que
tue. Ces colonnes sont remplies rétroactivement sur
l'historique : elles ne sont pas renseignées à 100 %. Un partant sans propriétaire connu
rend owner: null. Le taux de remplissage réel est publié, champ par champ, par
GET /v1/schema - vérifiez-le avant d'en tirer une conclusion.
GET /v1/programme ne servait que la journée du jour. Toute autre date
renvoyait une erreur 422 dont le message conseillait
/v1/courses/{date}/{rc}, qui sert une course, pas une
journée : personne ne pouvait deviner la bonne adresse. C'était notre premier motif
de support sur l'API.
https://www.turf.bzh/api/v1/programme?date=2026-08-20&api_key=VOTRE_CLE
La réponse porte les huit mêmes clés que pour le jour même, dans le
même ordre, y compris quand aucune course ne sort : un client qui lit déjà la réponse
du jour n'a rien à changer, il ajoute date=. format=csv
fonctionne aussi. /v1/quinte accepte le même paramètre.
Deux réserves, écrites plutôt que tues. filter=upcoming est refusé sur
une journée passée : le filtre du jour est posé sur le WHERE et fausserait le nombre
de partants. Et la couverture de /v1/quinte est celle du fichier de
référence Quinté+, pas celle de la base.
Pour tous les partants d'une journée, cote de départ comprise, en un
seul appel, /v1/journees/{date}/partants reste plus direct :
une requête par journée au lieu d'une par course.
Le champ link_url était construit avec le code de course
en minuscules. Or la page de course découpe son adresse avec une
expression sensible à la casse et retombe en silence sur R1C1 quand elle
échoue, pendant que la réécriture d'URL, elle, accepte les minuscules : aucune 404 ne
signalait quoi que ce soit. Mesure du 21/08 :
/pronostics-pmu-20082026-r5c3.html → R1C1 à Deauville (200, faux) /pronostics-pmu-20082026-R5C3.html → R5C3 à York (200, juste)
Tous les link_url sont désormais en majuscules et mènent à la course
annoncée. Si vous aviez contourné le problème en reconstruisant l'URL vous-même,
vous pouvez revenir au champ.
Une course était réputée terminée quand tous ses partants avaient un classement, et un
non partant comptait comme un classement manquant. Sur les
28 809 courses de la base, 9 716, soit 33,7 %,
ressortaient is_completed: false alors qu'elles étaient courues depuis
des mois. Le filtre completed, lui, appliquait déjà la bonne règle : le
champ et le filtre se contredisaient sur la même course.
La règle est désormais unique, partout : une course est terminée quand aucun partant n'attend son classement et qu'au moins un partant est classé. Vérifié sur les 28 809 courses, le champ et le filtre concordent, zéro désaccord.
Le jour même, rien ne change : la base ne reçoit les classements que le lendemain à 06h, donc les deux règles y répondaient déjà la même chose.
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"
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.
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.
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.
Le chemin le plus court, sans écrire une ligne de code : une adresse qui marche dans votre navigateur.
https://www.turf.bzh/api/v1/programme?api_key=tbz_live_VOTRE_CLE&format=csv à la fin de 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).
Deux conditions, vérifiées à chaque requête :
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.
Générez votre clé personnelle sur Ma clé 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.
curl -H "Authorization: Bearer tbz_live_VOTRE_CLE" \ "https://www.turf.bzh/api/v1/programme"
import requests, time
KEY = "tbz_live_VOTRE_CLE"
URL = "https://www.turf.bzh/api/v1/courses/2026-09-16/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
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-09-16/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]);
});
}
Six façons concrètes de vous servir de l'API. Cliquez sur une carte pour le tutoriel correspondant.
Le pack qui apprend l'API et le métier à Claude ou Codex. Compris dans la licence.
Un classeur qui va chercher le programme, les cotes et les value bets du jour.
Un message dès qu'une cote baisse fortement sur une course suivie.
Un bot Telegram (ou Discord ou Rakuten viber) qui répond à /course, /cotes, /tops...
Poser une question à l'agent depuis votre outil et récupérer sa réponse.
Automatiser sans coder avec un nœud HTTP et votre clé.
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.
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.
IMPORTDATA.
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
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.
let,
c'est cela qui s'est produit.
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-09-16/R1C4?api_key=VOTRE_CLE&format=csv # Les cotes de cette course, en direct https://www.turf.bzh/api/v1/courses/2026-09-16/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-09-16/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
C'est la question qui revient le plus souvent, alors autant l'écrire une fois pour toutes.
| Adresse | Dates acceptées |
|---|---|
/programme | Toute date présente en base, paramètre date=AAAA-MM-JJ ; sans lui, le jour même. Depuis le 21/08/2026 : cette adresse ne refuse plus les journées passées. Une date absente de la base renvoie 404 en donnant les bornes réelles. filter=upcoming n'a de sens que le jour même et renvoie 422 sur une journée passée. |
/quinte | Le jour même par défaut, paramètre date depuis le 21/08/2026. La couverture est celle du fichier de référence Quinté+, pas celle de la base : une journée qu'il ne couvre pas renvoie 404 et vous oriente vers /programme?date=. |
/courses/{date}/{rc} | Toute date présente en base, y compris des années en arrière. |
/cotes | Le jour même. Ce sont les cotes PMU en direct, elles n'existent pas pour une course passée. |
/cotes/historique | Toute journée depuis le 27/07/2026, date de début de la collecte. Avant, rien n'existe et rien ne peut être reconstruit. |
/tops | Le jour même par défaut, paramètre date optionnel sur les journées conservées. |
/value-bets | Idem : date optionnel, aujourd'hui par défaut. |
/journees/{date}/partants | Toute date présente en base. Les plages sont refusées en 422 : bouclez sur les dates, ou prenez les exports mensuels. |
/schema | Sans objet : le dictionnaire décrit la base, pas une journée. |
/chevaux/{id}/historique | Toute la carrière connue. Bornes debut et fin optionnelles ; sans elles, fenêtre glissante de 90 jours. |
/courses/{date}/{rc}/rapports | Toute 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. |
/rapports | Idem. date optionnel, aujourd'hui par défaut ; une date future renvoie 422, les rapports définitifs n'existant qu'après la course. |
/performances | Sans 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. |
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 :
"en-US" dans Table.TransformColumnTypes.Authorization: Bearer décrit
plus haut : la clé n'apparaît alors ni dans l'URL, ni dans les journaux
du serveur.
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 voulez | Par ou passer | Ce que ça coûte |
|---|---|---|
| Une course précise | /v1/courses/{date}/{rc} | 1 appel |
| Une journée entière | /v1/journees/{date}/partants | 1 appel, moins de 300 ms |
| Quelques semaines | Boucle sur /v1/journees/{date}/partants | 1 appel par journée, largement sous le plafond |
| Toute la base | /v1/exports | 19 téléchargements, une trentaine de Mo |
Ne parcourez pas la base course par course. Il y a 29 999 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.
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
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. Une quinzaine des 84 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.
/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.
/newbot :
il vous donne un jeton. Récupérez aussi votre identifiant de discussion (chat_id) en écrivant à @userinfobot./cotes/mouvements.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-09-16/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-09-16", "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.
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 courseIl 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 :
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.
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.
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.
| Endpoint | Ce que ça renvoie |
|---|---|
/v1/me | État de votre compte : licence, abonnement, clé, crédits IA, usage API du jour, limites. |
/v1/statut | Fraîcheur de la base (healthcheck pour vos scripts) : fresh / stale / empty, nombre de courses chargées. |
/v1/programme | Le programme d'une journée : courses, hippodromes, disciplines, heures, nombre de partants, drapeau Quinté+. Paramètre date : AAAA-MM-JJ, le jour même par défaut, toute journée présente en base depuis le 21/08/2026. Paramètre filter : all (défaut), upcoming, completed. is_completed vaut vrai quand aucun partant n'attend son classement et qu'au moins un partant est classé : un non partant ne fait plus passer une course courue pour « à venir ». Le champ et le filtre completed disent désormais la même chose, vérifié sur les 28 809 courses de la base, zéro désaccord. |
/v1/quinte | La course du Quinté+ d'une journée (source officielle turf.bzh) avec sa fiche complète et ses partants. Paramètre date : le jour même par défaut, ou toute journée couverte par le fichier de référence. Si le Quinté+ d'une journée passée n'y figure pas, la réponse le dit et renvoie vers /v1/programme?date=. |
/v1/resultats | Arrivé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, propriétaire (owner), éleveur (breeder), ferrure, musique, Cote BZH, ELO, Note IA, popularité... Fonctionne aussi sur tout l'historique (342 724 partants sur 29 999 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. Depuis le 11/09/2026, paramètre champs (base | indicateurs | tout, défaut tout) : tous les indicateurs de la base sortent, ELO propriétaire et éleveur compris. Depuis le 12/09/2026, chaque partant porte partant et la course sépare nombre_partants_initial de nombre_partants_reel ; pour une course du jour non encore courue, les forfaits de dernière minute sont relevés en direct sur le flux PMU. Paramètres non_partants (inclus | exclus | seuls) et non_partants_direct. |
/v1/courses/{date}/{rc}/dossier | Nouveau (06/09/2026), CSV et Excel depuis le 07/09. Toute la course en un seul appel. La fiche et ses partants, les indicateurs, le classement, LigneBZH, les écarts, le carnet du Renifleur, ÉcurieBZH, les cotes en direct et leurs mouvements le jour J, les rapports définitifs sur une course passée, et l'historique récent de chaque partant. Remplace 7 appels de course plus un appel par cheval : 21 appels pour un peloton de 14 deviennent un seul, et il ne compte que pour un appel de quota. Paramètres : inclure, historique, hippodrome. Comment s'en servir. |
/v1/journees/{date}/partants | Nouveau (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 29 999 appels, soit trois jours compte tenu du plafond quotidien ; par journée, 588 appels et une dizaine de minutes. Paramètres : discipline (plat, trot attelé, trot monté, obstacle, ou le code d'une lettre), hippodrome, format=csv, depuis le 11/09/2026 champs (base | indicateurs | tout, défaut tout), et depuis le 12/09/2026 non_partants (inclus | exclus | seuls) et non_partants_direct (relevé PMU en direct sur la journée en cours, inactif par défaut). Une liste courses donne le peloton engagé et le peloton réel course par course. 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/schema | Nouveau (03/08/2026). Le dictionnaire des champs servis : description, type, unité, jeu champs qui le fait sortir, et surtout taux de remplissage réel. Depuis le 11/09/2026 il décrit 80 champs au lieu de 49. Sur les 84 colonnes de la base, 38 sont renseignées à 100 % et une quinzaine le sont à moins de 65 % : Rapport_SG à 8,5 %, Cote_BZH à 50,3 %, et les colonnes propriétaire/éleveur (ajout rétroactif). Vérifiez-le avant de bâtir une hypothèse sur un champ. |
/v1/courses/{date}/{rc}/cotes | Cotes 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/mouvements | Les 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/historique | Exclusif. 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}/arrivee | L'arrivée d'une course en quasi temps réel le jour J (source PMU live), rapports inclus quand disponibles. |
/v1/courses/{date}/{rc}/rapports | Nouveau (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/rapports | Nouveau (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/performances | Nouveau (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}/indicateurs | Features calculées par partant : tendances ELO 30/90 j, forme récente, écarts, affinité distance/hippodrome, synergie jockey, signal IMDC, incidents... Depuis le 11/09/2026, les colonnes brutes du partant sortent aussi (ELO propriétaire et éleveur, Sigma, IMDC, palmarès complet) : paramètres champs et format=csv. |
/v1/courses/{date}/{rc}/analyse | Le 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}/renifleur | Exclusif. 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/courses/{date}/{rc}/ecuriebzh | Exclusif. ÉcurieBZH : les écuries engagées dans cette course qui sortent de leurs habitudes du jour (trajet inhabituel, plusieurs chevaux dans la même course, première venue sur un hippodrome...). Lecture descriptive du comportement des écuries, jamais un pronostic - le champ avertissement le rappelle. Ce que ça dit, ce que ça ne dit pas. |
/v1/ecuriebzh | Exclusif. Le panorama du jour : toutes les écuries hors habitude, toutes courses confondues. Paramètre ecurie pour n'en interroger qu'une (ex : ?ecurie=S. Roger). |
/v1/journees/{date}/ecuriebzh | Exclusif. Le même panorama pour une journée passée archivée : ce jour-là, ces écuries sortaient de leurs habitudes. 404 si la date n'est pas archivée. |
/v1/tops | Top 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-bets | Les 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}/historique | Les dernières courses du cheval (max 50 par appel, filtre discipline). |
/v1/chevaux/{id}/stats | Stats agrégées du cheval (periode_jours optionnel). |
/v1/chevaux/{id}/lectures | Les lectures expertes actives du jour pour ce cheval. |
/v1/chevaux/{id}/renifleur | Exclusif. 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 d'une personne du turf (moteur sémantique + repli) -> renvoie les ids. type : jockey, driver, entraineur, et depuis le 25/08/2026 proprietaire et eleveur. |
/v1/personnes/{type}/{id}/stats | Stats d'un jockey, entraîneur, propriétaire ou éleveur : periode_jours, discipline, hippodrome (jockey uniquement). Pour propriétaire et éleveur : partants, chevaux distincts, victoires, placés, taux V/P, ROI placé, répartition par discipline. type = jockey | entraineur | proprietaire | eleveur. |
/v1/methodes | La 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, /courses/{date}/{rc}/dossier (qui accepte aussi xlsx), /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.
Cette référence en un fichier : les adresses, leurs paramètres et les 80 champs du dictionnaire se téléchargent en Markdown, en JSON ou en PDF depuis le bloc Emporter cette documentation, en haut de page. C'est ce qu'il faut donner à un assistant plutôt que l'adresse de cette page.
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-09-16",
"code_course": "R1C4",
"source": "pmu",
"updated_at": "2026-09-16T01:23:26+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-09-16T01:23:26+02:00", "temps_reel": true }
}
L'API est granulaire par choix : une ressource, une adresse. C'est le bon découpage quand on veut une chose précise, et c'est le mauvais quand on veut tout. Reconstituer une course complète demandait 7 appels de course, plus un appel par partant pour son historique : 21 appels pour un peloton de quatorze, plus de mille pour une journée entière.
/dossier fait l'assemblage côté serveur. Il compte pour un appel, comme
n'importe quelle autre adresse : pas de pondération, pas de plafond à part.
# tout ce que l'on sait de la course, historique des partants compris
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/courses/2026-09-16/R1C4/dossier"
# juste ce dont vous avez besoin, et sans l'historique (reponse bien plus legere)
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/courses/2026-09-16/R1C4/dossier?inclure=classement,cotes&historique=0"
| Paramètre | Ce qu'il fait |
|---|---|
inclure | Les sections voulues, séparées par des virgules : indicateurs, classement, ligne_bzh, ecarts, renifleur, ecuriebzh, cotes, mouvements, arrivee, rapports, historique. Omis, le dossier sert ce qui a du sens pour la date : les cotes en direct pour aujourd'hui, les rapports définitifs pour une course passée. tout force les onze. |
historique | Nombre de dernières sorties par partant, de 0 à 10 (défaut 3). C'est la seule partie qui coûte une requête par cheval : historique=0 la supprime et allège nettement la réponse. |
hippodrome | Comme sur /v1/courses/{date}/{rc} : dix courses de l'historique partagent leur code RxCy avec une autre réunion le même jour. |
format | Nouveau (07/09/2026). csv ou xlsx. Omis, vous recevez le JSON complet. |
Un dossier est imbriqué, un tableur ne l'est pas. ?format=csv prend donc les partants comme
colonne vertébrale : une ligne par cheval, les champs de course répétés sur chaque ligne
(préfixés course_), et chaque section rabattue sur le cheval qu'elle décrit, préfixée par son nom
(classement_score, ecarts_ecart_victoire, historique_sorties…).
C'est la seule forme qu'un tableur sait filtrer, trier et croiser.
# une ligne par partant, pret pour Power Query
curl -H "Authorization: Bearer VOTRE_CLE" \
"https://www.turf.bzh/api/v1/courses/2026-09-16/R1C4/dossier?format=csv&historique=0"
# un classeur : un onglet par section, plus Partants et Course
curl -H "Authorization: Bearer VOTRE_CLE" -OJ \
"https://www.turf.bzh/api/v1/courses/2026-09-16/R1C4/dossier?format=xlsx"
xlsx rend un classeur avec un onglet Partants (la même mise à plat), un onglet
par section pour qui veut le détail, et un onglet Course qui rappelle ce que contient le fichier.
Ce que le tableur ne porte pas. L'historique détaillé de chaque cheval devient un simple
compteur historique_sorties : quatorze chevaux fois trois sorties, c'est une deuxième table,
pas des colonnes. Les valeurs imbriquées (comme top_features) sont jointes en texte lisible.
Le JSON reste la forme complète et fait foi ; ces deux sorties sont des commodités.
Aucune section ne peut faire tomber le dossier. Les autres adresses
traduisent une donnée manquante en erreur HTTP et s'arrêtent là. Ici, une section indisponible revient
en disponible: false avec sa raison, et tout le reste est servi. Seule la course
elle-même est bloquante : si elle n'existe pas, il n'y a pas de dossier.
meta.appels_economises ne compte que les sections qui ont réellement abouti : c'est un
décompte, pas un argument commercial.
{
"data": {
"course": { "date": "2026-09-16", "code_course": "R1C4", "hippodrome": "Vincennes",
"nombre_partants_initial": 14, "nombre_partants_reel": 13,
"nombre_non_partants": 1, "numeros_non_partants": [ 7 ],
"nombre_partants": 14, "partants": [ ... ] },
"sections": {
"classement": { "disponible": true, "donnees": { ... } },
"ligne_bzh": { "disponible": false, "raison": "daily_data_missing",
"message": "Pas de lecture LigneBZH pour cette journee." },
"historique": { "disponible": true, "sorties_par_cheval": 3,
"donnees": { "1347094367": { "disponible": true, "donnees": { ... } } } }
}
},
"meta": {
"sections_servies": [ "indicateurs", "classement", "..." ],
"sections_indisponibles": { "ligne_bzh": "daily_data_missing" },
"appels_economises": 21,
"cout_quota": 1,
"cache": "miss", "cache_ttl_s": 15
}
}
Cache. Une course passée ne bouge plus : son dossier est gardé six heures. Une course
du jour porte des cotes vivantes : quinze secondes seulement. meta.cache vous dit lequel
des deux cas vous avez touché. Pour une cote à la seconde près, /cotes reste l'adresse.
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.
| Champs | Sur quelle durée | Ce 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.
/cotes/historiqueLes 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épart | Un relevé toutes les |
|---|---|
| Plus de 2 h | 30 minutes |
| Entre 2 h et 30 min | 10 minutes |
| Moins de 30 min | 5 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-09-16", "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.
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.
| Champ | Ce qu'il vaut |
|---|---|
masse | en_ligne ou point_de_vente. |
pari | Le type sans la marque de masse : E_TRIO et TRIO donnent tous deux TRIO. Le filtre ?pari= accepte les deux écritures. |
libelle | Le 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_gagnants | Fractionnaire (Flexi, mises partielles). null quand le PMU ne le publie pas : ce n'est pas zéro, qui voudrait dire « personne ». |
paye | false 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é. |
meta.collecte de chaque réponse donne les bornes
exactes et le nombre de courses couvertes, pour que vous n'ayez pas à les deviner.
/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.
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
Trois défauts affectaient la restitution des arrivées. Ils ont été corrigés le 2 août 2026 et sont documentés ici parce qu'ils changent ce que reçoivent les scripts déjà écrits.
| Défaut | Porté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éponse | 555 courses absentes, soit 34,5 % |
| Les chevaux disqualifiés étaient triés avant le gagnant, avec un rang 0 | 776 arrivées polluées, soit 48,2 %, dont 94 qui ne contenaient aucun cheval classé |
| L'ordre d'arrivée était tronqué aux cinq premiers | une 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.
/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.
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.
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.
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.
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ée | Chevaux repérés | Tous les partants | Attendu par la cote |
|---|---|---|---|
| Dans les trois premiers | 35,9 % | 27,8 % | 34,5 % |
| Dans les quatre premiers | 46,7 % | 37,0 % | 44,7 % |
| Gagnant | 11,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.
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.
Chaque matin, ÉcurieBZH compare la journée de chaque écurie engagée au programme à ses habitudes des dix-huit derniers mois, et signale ce qui sort de l'ordinaire : un trajet bien plus long que sa zone habituelle (« trajet inhabituel », « plus loin que jamais »), trois chevaux dans la même course, une première venue sur un hippodrome, une monte confiée ou reprise, le retour d'un cheval, une première sortie chez un entraîneur. C'est la même donnée qui alimente le pictogramme et l'onglet ÉcurieBZH du tableau des partants.
curl -H "X-API-Key: VOTRE_CLE" \ "https://www.turf.bzh/api/v1/ecuriebzh" # panorama du jour curl -H "X-API-Key: VOTRE_CLE" \ "https://www.turf.bzh/api/v1/ecuriebzh?ecurie=S.%20Roger" # une écurie précise curl -H "X-API-Key: VOTRE_CLE" \ "https://www.turf.bzh/api/v1/courses/2026-08-19/R2C1/ecuriebzh" # les écuries d'une course curl -H "X-API-Key: VOTRE_CLE" \ "https://www.turf.bzh/api/v1/journees/2026-08-19/ecuriebzh" # une journée passée
Chaque écurie renvoyée porte ses deplacements du jour (hippodrome, heure) et,
pour chacun, ses faits : le libelle du fait, la phrase
rédigée qui l'explique, et le kilométrage (km, km_habituel). Une
journée passée est servie tant qu'elle est archivée ; une date non archivée répond 404. Le
champ archive vaut true sur une journée passée.
C'est une lecture descriptive du comportement des écuries, pas un pronostic.
Une écurie qui se déplace loin, qui aligne plusieurs chevaux ou qui découvre un hippodrome ne
gagne pas plus souvent pour autant. Le fait raconte quelque chose de la journée de l'écurie -
une contrainte, un choix, un déplacement rare - il ne dit rien de la probabilité qu'un de ses
chevaux gagne. Chaque réponse porte un champ avertissement qui le rappelle, de
sorte qu'un agent qui consomme l'API ne puisse pas le présenter comme une raison de jouer.
Donnée produite et financée par turf.bzh. Sa réutilisation publique suppose une attribution visible à turf.bzh. La republication en masse sans attribution n'est pas couverte par la Licence API : voir les conditions.
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.
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
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
$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"]);
}
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).
api_key par votre clé, ajustez date et rc.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...).
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.
/v1/chatPosez 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.reponse (texte), tools_utilises, credits.factures, credits.solde_restant, forfait_applique.Toute erreur renvoie un JSON uniforme :
{ "error": { "code": "subscription_required", "message": "...", "status": 403, "doc": "..." } }
| HTTP | code | Signification |
|---|---|---|
| 401 | missing_key / invalid_key | Clé absente, inconnue ou révoquée. Générez / régénérez sur Ma clé API. |
| 403 | api_locked | Licence API non débloquée sur ce compte. |
| 403 | subscription_required | Abonnement inactif. La licence reste acquise : réabonnez-vous et ça repart. |
| 402 | insufficient_credits | (/chat) Solde crédits IA insuffisant. Rechargez sur chatbzh-boutique.php. |
| 402 | cost_exceeds_max_credits | (/chat) L'estimation dépasse votre max_credits : augmentez-le ou simplifiez la question. Rien n'a été débité. |
| 404 | not_found | Route ou course inconnue (la réponse liste parfois les courses disponibles). |
| 405 | method_not_allowed | GET partout, sauf /v1/chat qui attend un POST. |
| 422 | invalid_params | Paramètre mal formé (date, code course, bornes). |
| 429 | rate_limited | Limite technique atteinte. Respectez l'en-tête Retry-After. La réponse porte aussi scope (minute, day ou ip) et le bloc quota, qui dit où vous en êtes sur les deux fenêtres. |
| 500 / 503 | internal_error / upstream_unavailable | Incident côté serveur ou source amont : réessayez un peu plus tard. |
429 + Retry-After au-delà.meta.quota (limite_minute, restant_minute, reset_minute_s, limite_jour, restant_jour, reset_jour_s) et les en-têtes X-RateLimit-*. Un programme qui boucle lit restant_minute et dort reset_minute_s secondes plutôt que d'attendre le 429. Le détail et un exemple.quota : ce n'est pas votre compteur. On le reconnaît à scope: "ip" sur un 429./v1, des champs peuvent être AJOUTÉS, jamais retirés ni renommés. Codez tolérant aux champs inconnus.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.
12/09/2026 Les non partants ne sont plus servis comme des partants.
Un cheval déclaré forfait figure dans notre base avec Rank = NP, et
/v1/courses/{date}/{rc} comme /v1/journees/{date}/partants ne le
distinguaient pas : 3 768 lignes et 2 741 courses sur les 8 530
mesurées de janvier à juin 2026. Chaque partant porte désormais partant,
statut_partant et, sur un forfait, source_statut. Chaque course
sépare nombre_partants_initial (le peloton engagé) de
nombre_partants_reel, avec nombre_non_partants et leurs numéros.
Ces deux nombres sont comptés ligne à ligne et non déduits de la colonne annoncée, servie
sous le nom nombre_partants_declare : sur les courses avec forfait, elle
vaut le peloton engagé 85,6 % du temps et le peloton réel 10,0 % seulement.
Pour une course du jour non encore courue, les forfaits de dernière minute sont relevés en
direct sur le flux PMU, seule source qui les connaisse avant l'arrivée. Nouveaux paramètres
non_partants et non_partants_direct. Rien n'est retiré ni
renommé.
09/09/2026 Les cinq probabilités IA, au lieu de deux.
IA_Couple, IA_Trio et IA_Multi rejoignent
IA_Gagnant et IA_Quinte sur /v1/courses/{date}/{rc},
/dossier, /indicateurs, /journees/{date}/partants et
/quinte. Elles existaient en base et s'affichaient dans le tableau des partants
(boutons IA COUPLE, IA TRIO, IA MULTI), mais l'API n'en exposait aucune.
ia_quinte apparaît par ailleurs
dans /indicateurs, qui la lisait depuis sa mise en service sans jamais la rendre.
Rien n'est retiré ni renommé.
V1 16/09/2026 - Lancement : clé API self-service, licence à vie au tarif de lancement de 19,90 €, endpoints me, statut, programme, quinte, resultats, courses/{date}/{rc} (+ /cotes, /cotes/mouvements, /arrivee).
V2-V4 16/09/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 16/09/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.
08/09/2026 Vos appels restants, dans chaque réponse.
Toute réponse 200 porte un bloc meta.quota et six en-têtes
X-RateLimit-* : limite et restant sur la minute, limite et restant sur la journée,
et les secondes avant chaque remise à zéro. Les mêmes valeurs partent sur un 429,
où quota rejoint retry_after et scope dans
error. Les en-têtes sont exposés par Access-Control-Expose-Headers,
donc lisibles par un fetch() de navigateur. Quand le compteur est indisponible,
rien n'est émis plutôt qu'un chiffre faux. Aucun coût de quota supplémentaire, aucun champ
retiré : le contrat v1 tient. Livré en même temps, le pack IA qui
forme Claude et Codex au turf, compris dans la Licence API.
06/09/2026 Le dossier complet d'une course, en un appel.
/v1/courses/{date}/{rc}/dossier assemble côté serveur ce qu'il fallait aller chercher en
sept appels de course plus un par partant. Paramètres inclure, historique et
hippodrome. Il compte pour un appel de quota, sans pondération ni plafond
dédié, et les limites existantes (60/minute, 10 000/jour) sont inchangées. Aucune adresse existante
n'est modifiée : le contrat v1 tient, des champs sont ajoutés, aucun n'est retiré ni renommé.
25/08/2026 Le propriétaire et l'éleveur entrent dans l'API.
Chaque partant porte désormais owner (propriétaire), breeder (éleveur) et leurs
identifiants stables idproprio / ideleveur, sur /v1/courses/{date}/{rc}
et /v1/chevaux/{id}/historique. La recherche /v1/personnes?type= et les stats
/v1/personnes/{type}/{id}/stats acceptent deux types de plus, proprietaire et
eleveur, avec les mêmes mesures qu'un jockey ou un entraîneur (partants, chevaux distincts,
victoires, placés, taux V/P, ROI placé, répartition par discipline). Ces colonnes sont remplies
rétroactivement sur l'historique : elles ne sont pas renseignées à 100 %, et /v1/schema en
publie le taux réel. Le contrat v1 ne change pas : des champs sont ajoutés, aucun n'est retiré ni renommé.
22/08/2026 ÉcurieBZH ouvre à l'API : ce que fait chaque écurie aujourd'hui.
Trois adresses nouvelles - /v1/ecuriebzh (le panorama du jour, paramètre ecurie pour
n'en interroger qu'une), /v1/courses/{date}/{rc}/ecuriebzh (les écuries engagées dans une course)
et /v1/journees/{date}/ecuriebzh (une journée passée archivée). La même donnée que l'onglet
ÉcurieBZH du tableau des partants : lecture descriptive du comportement des écuries, jamais un pronostic
(champ avertissement). 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.
21/08/2026 Le programme s'ouvre aux journées passées.
/v1/programme et /v1/quinte ne servaient que le jour même : toute autre date
renvoyait une erreur 422 dont le message conseillait /v1/courses/{date}/{rc}, qui sert
une course. Celui qui demandait le programme d'hier ne trouvait donc pas la bonne adresse et
concluait à une panne. C'est devenu notre premier motif de support sur l'API.
Les deux adresses acceptent désormais date=AAAA-MM-JJ. /programme rend les
huit mêmes clés que pour le jour même, dans le même ordre, y compris quand aucune course
ne sort : un client qui lit déjà la réponse du jour n'a rien à changer. Deux réserves, écrites plutôt que
tues : filter=upcoming est refusé sur une journée passée, parce que le filtre du jour est posé
sur le WHERE et fausserait le nombre de partants (mesure du 1er août : 17 au lieu de 28 sur
Goodwood R6C4) ; et la couverture de /quinte est celle du fichier de référence Quinté+, pas
celle de la base. Pour reconstituer une journée entière avec tous les partants et leur cote de départ,
/v1/journees/{date}/partants reste plus direct, en un seul appel.
21/08/2026 is_completed disait « à venir » d'une course sur trois, courue depuis des mois.
Une course était réputée terminée quand tous ses partants avaient un classement, et un non partant
comptait comme un classement manquant. Mesure sur les 28 809 courses de la base : 9 716, soit 33,7 %,
ressortaient is_completed: false alors qu'elles étaient courues. Le filtre completed,
lui, appliquait déjà la bonne règle : le champ et le filtre se contredisaient sur la même course. La règle est
harmonisée partout, y compris pour /v1/programme au jour même et pour l'agent : une course est
terminée quand aucun partant n'attend son classement et qu'au moins un partant est classé. Le jour même,
le changement est neutre : la base ne reçoit les classements que le lendemain à 06h, donc les deux
règles y répondaient déjà la même chose. Corrigé au même endroit : une course entièrement dépouillée mais
comptant un non partant était signalée « en attente d'ingestion ».
21/08/2026 Les liens de course rendus par l'API pointaient sur la mauvaise course.
Le champ link_url de /v1/programme était construit en minuscules. Or la page de
course découpe son adresse avec une expression sensible à la casse et retombe en silence sur R1C1
quand elle échoue, sans jamais renvoyer 404. Mesure du 21/08 :
/pronostics-pmu-20082026-r5c3.html répondait 200 en affichant R1C1 à Deauville, au lieu de
R5C3 à York. Tous les link_url sont désormais en majuscules et mènent à la bonne course.
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 16/09/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).
API réservée aux abonnés turf.bzh titulaires de la Licence API. Données fournies "en l'état", sans garantie d'exactitude ni de disponibilité. turf.bzh ne saurait être tenu responsable des décisions de jeu prises sur la base de ces données.