L’API de Peerbeta, pour vos scripts.
Créez un test fermé depuis votre chaîne de publication, suivez-le jour après jour et récupérez son rapport en JSON, en Markdown ou en PDF. Les mêmes actions passent par webhooks et par un serveur MCP.
Démarrer
Une clé appartient à une organisation. Son propriétaire ou un administrateur la crée dans Réglages › Clés API, avec les portées dont le script a besoin ; elle ne s’affiche qu’une fois. Passez-la dans l’en-tête Authorization de chaque requête.
curl https://api.peerbeta.com/api/v1/organization \
-H "Authorization: Bearer pbk_…"| Portée | Permet de |
|---|---|
workspaces.read | Lire l’organisation |
tests.read | Lire les tests et leur état |
tests.write | Créer des tests |
reports.read | Lire les rapports (JSON, Markdown, PDF) |
mcp.read | Serveur MCP : outils de lecture |
mcp.write | Serveur MCP : outils d’écriture |
Adresse de base : https://api.peerbeta.com/api/v1. L’organisation et le membre viennent toujours de la clé, jamais de la requête.
Limites
- 60 requêtes par minute et par clé. Chaque réponse porte X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset ; au-delà, la réponse est un 429 avec Retry-After.
- 50 tests créés par jour et par organisation.
- Un corps de requête de 64 Ko au plus.
- Chaque appel est inscrit au journal de la clé, visible dans Réglages › Clés API.
Créer un test
POST /tests crée l’app (ou reprend celle de l’organisation qui a ce paquet) et un test en brouillon. Avec funding, le test est payé tout de suite : une commande payée pas encore rattachée, la file crédits (vos crédits ou ceux de l’organisation), ou le cercle. Sans funding, le test attend son paiement sur le web.
{
"app": {
"name": "Carnet",
"packageName": "com.example.carnet",
"category": "productivity",
"countries": ["FR", "BE"],
"playConsoleAccountId": "<id from GET /organization>"
},
"features": [
{ "title": "Search", "instruction": "Search a note with two words." }
],
"optInUrl": "https://play.google.com/apps/testing/com.example.carnet",
"funding": { "type": "credits", "wallet": "organization" }
}Envoyez un en-tête Idempotency-Key (1 à 100 caractères) : la même clé avec le même corps rend le même test pendant 24 heures, sans en créer un second ; avec un autre corps, la réponse est un 409. La réponse est un 201 avec l’en-tête Location, ou un 200 quand la clé d’idempotence a déjà servi.
Les étapes de la Play Console (piste de test fermé, groupe des testeurs, pays, publication) restent confirmées par le développeur sur la page du test : son adresse est dans links.dashboard.
Suivre un test
GET /tests/{testId}/status dit où en est le test : le jour sur 14, les testeurs (installés, actifs aujourd’hui, à risque, partis), les retours J1, J7 et J14, et si le rapport est prêt. Plutôt que d’interroger l’API en boucle, abonnez-vous aux webhooks.
curl https://api.peerbeta.com/api/v1/tests/<testId>/status \
-H "Authorization: Bearer pbk_…"Récupérer le rapport
GET /tests/{testId}/report rend le rapport en JSON : chiffres, synthèse, recommandations, marque de l’organisation, et un lien PDF signé valable 15 minutes, sans clé. Avec format=markdown, la réponse est le Markdown du rapport.
Le rapport d’une organisation porte son nom et son logo, avec une mention discrète de Peerbeta. Peerbeta prépare la demande d’accès en production ; Google seul décide.
Webhooks
Ajoutez une destination HTTPS dans Réglages › Webhooks et choisissez ses événements. Chaque envoi est signé avec le secret de la destination. Une réponse hors 2xx est retentée jusqu’à 7 fois en tout (après 1 min, 5 min, 30 min, 2 h, 8 h puis 24 h) ; le journal des tentatives est dans les réglages.
| Événement | Envoyé quand |
|---|---|
test.started | Le jour 1 commence : assez de testeurs ont une installation vérifiée. |
test.paused | Le test est mis en pause (par vous, par Peerbeta, ou un lien cassé). |
tester.installed | Un testeur a installé l’app, vérifié par l’app testeur. |
checkpoint.completed | Un testeur a envoyé son retour J1, J7 ou J14. |
test.completed | Les 14 jours sont passés. |
report.ready | Le rapport est rédigé : JSON, Markdown et PDF. |
Le corps est une enveloppe JSON : id, type, created (millisecondes depuis 1970) et data, qui porte toujours le test. Les testeurs y sont des numéros, jamais un nom ni une adresse.
{
"id": "evt_3f9a2c7d1e0b4a5c6d7e8f90",
"type": "tester.installed",
"created": 1762700000000,
"data": {
"test": {
"id": "<testId>",
"status": "recruiting",
"offer": "pro",
"mode": "offer",
"app": { "id": "<appId>", "name": "Carnet", "packageName": "com.example.carnet" },
"day1At": null,
"endsAt": null,
"eligibleAt": null
},
"tester": { "number": 7, "installedAt": "2026-11-09T14:02:11.000Z", "proof": "open" },
"installs": 9
}
}Vérifiez la signature avant de lire le corps : un HMAC SHA-256 de « horodatage.corps » avec le secret, comparé à x-peerbeta-signature ; refusez un horodatage de plus de cinq minutes. x-peerbeta-event-id permet d’ignorer un envoi déjà traité.
import { createHmac, timingSafeEqual } from "node:crypto";
// body: the raw request body, as a string, before any JSON.parse.
export function isFromPeerbeta(body, headers, secret) {
const timestamp = headers["x-peerbeta-timestamp"];
const signature = headers["x-peerbeta-signature"] ?? "";
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const expected = "sha256=" + createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
return expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Serveur MCP
Un agent IA (Claude, Cursor…) fait les mêmes actions par le serveur MCP, en transport HTTP, avec une clé de l’organisation. Les outils de lecture demandent mcp.read ; create_test demande mcp.write et tests.write.
https://api.peerbeta.com/mcpOutils : list_workspaces, get_organization, list_tests, create_test, get_test_status, get_test_report.
Erreurs
Une erreur rend un JSON avec un code stable dans error, un message et, pour une validation, le champ en cause.
| HTTP | Code | Quand |
|---|---|---|
| 400 | VALIDATION | Un champ manque ou est mal formé ; `field` le nomme. |
| 401 | UNAUTHENTICATED | Aucune clé dans l’en-tête Authorization. |
| 401 | API_KEY_INVALID | Clé inconnue, expirée ou révoquée. |
| 403 | FORBIDDEN | La clé n’a pas la portée, ou l’API n’est pas ouverte. |
| 404 | NOT_FOUND | Ce test ou ce rapport n’est pas dans l’organisation de la clé. |
| 409 | CONFLICT | Un test est déjà en cours pour cette app, ou la clé d’idempotence a servi avec un autre corps. |
| 429 | RATE_LIMITED | Trop de requêtes ; `Retry-After` dit quand réessayer. |
Référence
| Requête | Portée | Rend |
|---|---|---|
GET /organization | workspaces.read | L’organisation de la clé : identité de facturation, comptes Play Console, crédits communs. |
GET /tests | tests.read | Les tests de l’organisation, du plus récent au plus ancien (`status`, `limit`, `cursor`). |
POST /tests | tests.write | Créer un test (et son app si le paquet est nouveau). |
GET /tests/{testId} | tests.read | Un test : mode, offre, configuration, dates, liens. |
GET /tests/{testId}/status | tests.read | L’état du test maintenant : jour, testeurs, retours, rapport. |
GET /tests/{testId}/report | reports.read | Le rapport en JSON (avec un lien PDF signé) ou en Markdown (`format=markdown`). |
GET /reports/{reportId}/pdf | aucune | Le PDF du rapport, par lien signé, sans clé. |
La spécification OpenAPI 3.1 complète, avec les schémas et les webhooks : https://api.peerbeta.com/api/v1/openapi.json.