API de lecture
Récupérez vos statistiques par programme : tableaux de bord internes, rapports, exports. Réservée aux plans Pro et Agence, à la bêta et à l'essai en cours.
Authentification
Créez un jeton dans Espace membre → Jetons API, puis passez-le en Bearer :
curl -H "Authorization: Bearer VOTRE_JETON" \ "https://quietmetrics.dev/api/v1/stats/qm_pub_XXXXXXXXXXXXXXXXX/summary?period=30d"
Toutes les routes sont préfixées par /api/v1/stats/{clé_publique_du_site}. Un jeton ne donne accès qu'aux sites de votre compte ; un site qui ne vous appartient pas répond 404 (aucune fuite d'existence).
Un jeton couvre soit tous vos sites, actuels et futurs, soit les seuls sites choisis à sa création : un site de votre compte qu'il ne couvre pas répond 403. Il cesse de fonctionner à la date d'expiration affichée dans la liste des jetons.
Endpoints
| Endpoint | Contenu |
|---|---|
GET /summary |
Indicateurs de la période, et ceux de la période précédente de même durée |
GET /timeseries |
Série temporelle : un point par jour, ou par heure quand la période tient sur un jour |
GET /breakdown |
Classement des valeurs d'une dimension ({label, value}) |
GET /realtime |
Visiteurs actifs sur les 5 dernières minutes ({"visitors": N}) |
Paramètres communs
period
Un préréglage du dashboard : today, yesterday, 7d, 30d (défaut), month (mois en cours), last_month, 12m. Les périodes glissantes se terminent aujourd'hui, jour inclus.
Ou une plage explicite, par exemple ?period=custom:2026-07-01..2026-07-31 :
custom:AAAA-MM-JJ..AAAA-MM-JJ: du premier au second jour, bornes incluses, sur 760 jours au plus. Des bornes inversées sont remises dans l'ordre. La plage peut finir aujourd'hui, pas après.
Les dates sont celles du fuseau du site.
Une période invalide ne produit pas d'erreur : elle est remplacée par 30d. C'est le cas d'un préréglage inconnu comme d'une plage mal formée (format autre que AAAA-MM-JJ, date qui n'existe pas), trop longue ou qui finit dans le futur. Vérifiez donc la période servie : chaque réponse, sauf realtime, porte period.from et period.to, et summary y ajoute period.preset, qui vaut custom pour une plage acceptée et 30d après un remplacement.
La période précédente (previous dans summary) compte le même nombre de jours et se termine la veille du premier jour de la période. Pour month, ce n'est donc pas le mois précédent entier, mais autant de jours que le mois en cours en a déjà.
filters
filters reprend les filtres des clics du dashboard, au format dimension:valeur, séparés par ; :
?filters=country:FR;device:mobile ?filters=page:/tarifs ?filters=channel:search;browser:Firefox
- Les dimensions utilisables sont indiquées dans la table ci-dessous. Toute autre dimension,
entryetexitcomprises, répond422en la nommant : vous recevez une erreur plutôt que des chiffres non filtrés. - Une seule valeur par dimension : quand une dimension apparaît deux fois, la dernière l'emporte (
?filters=device:mobile;device:desktopne garde quedesktop). Des dimensions différentes se cumulent : seuls comptent les envois qui les satisfont toutes. filtersn'accepte que des valeurs de 500 caractères au plus : au-delà, la valeur est écartée sans erreur, et elle n'apparaît alors pas dans lefiltersde la réponse.- Le
filtersdesummaryne porte que les filtres RÉELLEMENT appliqués, et non la requête reçue : une dimension qui y figure a bien restreint les chiffres qui l'accompagnent. - Vocabulaire fermé :
channelvautdirect,search,social,referral,emailoucampaign;devicevautdesktop,mobile,tabletouother. - Dès qu'un filtre s'applique, les indicateurs sont recalculés depuis les événements bruts, et
bounce_ratecommeavg_durationvalentnull. - Dans
breakdown, un filtre sur la dimension classée est ignoré pour ce classement :?dimension=country&filters=country:FRrend tous les pays, pour que le filtre ne réduise pas le classement à sa propre valeur.
dimension (breakdown)
dimension |
Ce que compte value |
Utilisable dans filters |
|---|---|---|
page |
Pages vues | oui |
entry |
Entrées : sessions commencées sur cette page | non |
exit |
Sorties : sessions terminées sur cette page, calculées la nuit suivante (la journée en cours n'en compte aucune, sauf sous filtre) | non |
referrer |
Sessions, par domaine référent | oui |
channel |
Sessions, par canal d'acquisition | oui |
source |
Sessions, par valeur de utm_source |
oui |
medium |
Sessions, par valeur de utm_medium |
oui |
campaign |
Sessions, par valeur de utm_campaign |
oui |
country |
Pages vues, par pays (code ISO à deux lettres) | oui |
region |
Pages vues, par région | oui |
browser |
Pages vues, par navigateur | oui |
os |
Pages vues, par système d'exploitation | oui |
device |
Pages vues, par type d'appareil | oui |
lang |
Pages vues, par langue | oui |
event |
Déclenchements, par nom d'événement | oui |
Une dimension absente de cette table répond 422. label vaut null pour les visites sans valeur, par exemple sans domaine référent.
limit (breakdown) : nombre de lignes, 10 par défaut, borné entre 1 et 1 000.
Pas de la série temporelle
GET /timeseries rend un point par jour en général, et un point par heure dès que la période tient sur une seule journée locale (today, yesterday, ou une plage custom: dont les deux bornes sont égales). Le champ date prend alors la forme AAAA-MM-JJ HH:MM, à l'heure locale du site, au lieu de AAAA-MM-JJ.
Une journée locale ne fait pas toujours 24 heures. Aux deux changements d'heure de l'année, la série porte donc 23, 24 ou 25 points : ne comptez pas sur un nombre fixe.
- Nuit où l'heure recule : 25 points. L'heure locale rejouée (02:00 à Paris) donne deux points consécutifs portant le même
date, chacun avec ses propres mesures. Ce format ne porte pas de décalage horaire : repérez ces points par leur rang dans la liste, jamais par leur libellé. - Nuit où l'heure avance : 23 points, l'heure locale sautée (02:00 à Paris) est absente. Aucun point ne déborde sur le lendemain.
Les visiteurs d'un point horaire sont ceux qui étaient actifs pendant cette heure : une personne présente sur deux heures apparaît dans les deux, et les points horaires ne s'additionnent donc pas en un total de journée.
Exemples de réponse
summary
GET /summary?period=30d :
{ "site": "monsite.fr", "period": { "preset": "30d", "from": "2026-06-17", "to": "2026-07-16" }, "filters": {}, "kpis": { "pageviews": 9873, "visitors": 4210, "sessions": 4630, "events": 312, "bounce_rate": 47.2, "avg_duration": 74 }, "previous": { "pageviews": 9145, "visitors": 3877, "sessions": 4288, "events": 290, "bounce_rate": 49.1, "avg_duration": 71 } }
kpis |
Contenu |
|---|---|
pageviews |
Pages vues |
visitors |
Visiteurs uniques de chaque journée, additionnés sur la période : une personne revenue le lendemain est comptée deux fois |
sessions |
Visites |
events |
Événements mesurés autres que les pages vues |
bounce_rate |
Taux de rebond en pourcentage, de 0 à 100, arrondi au dixième : part des sessions avec un seul envoi mesuré. null sans aucune session, ou dès qu'un filtre s'applique |
avg_duration |
Durée moyenne d'une session, en secondes entières. null sans aucune session, ou dès qu'un filtre s'applique |
previous porte les mêmes clés pour la période précédente. L'API ne calcule aucune variation : c'est à vous de la déduire des deux valeurs, en traitant à part une valeur précédente nulle ou null.
GET /summary?period=7d&filters=device:mobile :
{ "site": "monsite.fr", "period": { "preset": "7d", "from": "2026-07-10", "to": "2026-07-16" }, "filters": { "device": "mobile" }, "kpis": { "pageviews": 1204, "visitors": 611, "sessions": 655, "events": 38, "bounce_rate": null, "avg_duration": null }, "previous": { "pageviews": 1139, "visitors": 590, "sessions": 622, "events": 41, "bounce_rate": null, "avg_duration": null } }
timeseries
Un point par jour, jours sans trafic compris (ils valent zéro).
GET /timeseries?period=custom:2026-07-14..2026-07-16 :
{ "period": { "from": "2026-07-14", "to": "2026-07-16" }, "data": [ { "date": "2026-07-14", "pageviews": 341, "visitors": 142, "sessions": 158, "events": 9 }, { "date": "2026-07-15", "pageviews": 298, "visitors": 131, "sessions": 144, "events": 12 }, { "date": "2026-07-16", "pageviews": 0, "visitors": 0, "sessions": 0, "events": 0 } ] }
Quand la période tient sur un jour (today, yesterday, ou une plage dont les deux bornes sont égales), la série passe à l'heure : un point par heure de la journée locale, de 00:00 à 23:00 à l'heure locale du site (23 ou 25 points les jours de changement d'heure, voir « Pas de la série temporelle »), et date prend la forme AAAA-MM-JJ HH:MM. Les visiteurs d'un point horaire sont ceux qui étaient actifs pendant cette heure : une personne présente sur deux heures apparaît dans les deux, et les points ne s'additionnent donc pas en un total de journée.
GET /timeseries?period=custom:2026-07-16..2026-07-16, les trois premiers des 24 points :
{ "period": { "from": "2026-07-16", "to": "2026-07-16" }, "data": [ { "date": "2026-07-16 00:00", "pageviews": 4, "visitors": 3, "sessions": 3, "events": 0 }, { "date": "2026-07-16 01:00", "pageviews": 1, "visitors": 1, "sessions": 1, "events": 0 }, { "date": "2026-07-16 02:00", "pageviews": 0, "visitors": 0, "sessions": 0, "events": 0 } ] }
breakdown
GET /breakdown?dimension=page&limit=3 :
{ "dimension": "page", "period": { "from": "2026-06-17", "to": "2026-07-16" }, "data": [ { "label": "/", "value": 3120 }, { "label": "/tarifs", "value": 1284 }, { "label": "/docs", "value": 977 } ], "capped": { "any": true, "dimensions": [ { "dimension": "page", "days": 2, "days_repaired": 1, "folded_values": 1406, "cap": 2000 } ] } }
Sans plafonnement sur la période, capped vaut {"any": false, "dimensions": []}.
realtime
GET /realtime :
{ "visitors": 3 }
Plafond de cardinalité (capped)
Chaque site mesure un nombre borné de valeurs distinctes par jour et par dimension. Au-delà, les valeurs suivantes sont comptées ensemble sous un libellé unique, ~autres : le détail est regroupé, aucune visite n'est perdue et les totaux restent exacts. breakdown annonce cet état dans la clé capped :
anyest toujours présent etdimensionstoujours une liste : le schéma ne varie pas selon l'état du site, aucun branchement n'est nécessaire pour le lire.dimensionest la dimension plafonnée, pas celle que vous avez demandée.entryetexitsont regroupés par le plafondpage;sourceetmediumsont regroupés par le plafondcampaign.dayscompte les journées locales plafonnées sur la période ;capest le seuil le plus haut réellement appliqué (il peut varier si un relèvement est posé en cours de période).folded_values: nullne signifie PAS zéro. Le nombre de valeurs regroupées n'est connu qu'après la reconstruction de nuit, qui ne traite une journée qu'à J+2.nullveut dire « plafond atteint, nombre encore inconnu » ;0veut dire « journée reconstruite, aucune valeur regroupée ». Afficher0à la place denullannoncerait à vos utilisateurs qu'ils ne perdent aucun détail, ce qui est faux.days_repaireddit sur combien desdaysjournées le compte est connu. Sidays_repaired < days,folded_valuesest une somme partielle, comme dans l'exemple ci-dessus.
capped ne décrit que la dimension demandée. Il vaut aussi {"any": false, "dimensions": []} dès qu'un filtre s'applique au classement : celui-ci est alors lu sur les événements bruts, où rien n'est regroupé.
summary et timeseries ne portent pas cette clé : elles ne lisent aucune dimension et sont insensibles au plafonnement.
L'export CSV du dashboard porte le même indicateur, mais dans l'en-tête HTTP X-Cardinality-Capped plutôt que dans le corps du fichier : {"capped":false}, ou {"capped":true,"dimension":"page","days":1,"days_repaired":1,"folded_values":2812,"cap":2000} cadré sur la seule dimension exportée. Le corps du CSV reste une donnée machine pure, sans ligne de métadonnée : aucune ligne synthétique ne peut ainsi fausser un total ni faire échouer un analyseur à nombre de colonnes fixe.
Ce plafond peut être relevé pour votre site : écrivez au support.
Exemple PHP
$response = json_decode(file_get_contents( 'https://quietmetrics.dev/api/v1/stats/qm_pub_XXXX/summary?period=7d', false, stream_context_create(['http' => ['header' => "Authorization: Bearer VOTRE_JETON\r\nAccept: application/json"]]) ), true); echo $response['kpis']['visitors'];
Erreurs
Le corps d'une erreur est un objet JSON dont la clé message décrit la cause.
| Code | Signification |
|---|---|
401 |
Jeton absent, invalide, révoqué ou expiré |
403 |
Le site est à vous, mais le jeton ne le couvre pas, ou votre plan n'ouvre pas l'API (essai expiré compris) |
403 |
Votre compte est suspendu ou en attente d'approbation : l'API reste fermée, quel que soit le plan |
404 |
Site inconnu, ou appartenant à un autre compte |
422 |
Dimension de breakdown inconnue, ou dimension de filters sur laquelle on ne peut pas filtrer |
429 |
Trop de requêtes : au plus 120 par minute par jeton, et 300 par minute pour l'ensemble du compte, tous jetons confondus. L'en-tête Retry-After donne le délai à attendre, en secondes |
Bonnes pratiques
- Mettez en cache côté client : interroger toutes les heures suffit largement. Attention toutefois, une journée close n'est pas définitivement figée : la reconstruction de nuit repasse sur les journées plafonnées et peut en recalculer les agrégats jusqu'à deux jours après. Prévoyez donc, sur les deux derniers jours, un cache court ou une réinterrogation ; au-delà, les valeurs ne bougent plus.
- Lisez
perioddans chaque réponse avant d'afficher un chiffre : une période invalide ne produit pas d'erreur, elle est remplacée par30d. - Un jeton par usage (un pour le dashboard interne, un pour le rapport mensuel) : la révocation reste chirurgicale, et deux usages ne se disputent pas la limite du même jeton.
- Pour un export ponctuel, chaque vue du dashboard s'exporte aussi en CSV depuis l'espace membre : pas besoin d'écrire du code.
Vous ne trouvez pas votre réponse ?
Le support répond en français, les jours ouvrés.