Aller au contenu

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.

Première requête
curl https://api.peerbeta.com/api/v1/organization \
  -H "Authorization: Bearer pbk_…"
PortéePermet de
workspaces.readLire l’organisation
tests.readLire les tests et leur état
tests.writeCréer des tests
reports.readLire les rapports (JSON, Markdown, PDF)
mcp.readServeur MCP : outils de lecture
mcp.writeServeur 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.

Corps de la requête
{
  "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.

État d’un test
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énementEnvoyé quand
test.startedLe jour 1 commence : assez de testeurs ont une installation vérifiée.
test.pausedLe test est mis en pause (par vous, par Peerbeta, ou un lien cassé).
tester.installedUn testeur a installé l’app, vérifié par l’app testeur.
checkpoint.completedUn testeur a envoyé son retour J1, J7 ou J14.
test.completedLes 14 jours sont passés.
report.readyLe 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.

Exemple : tester.installed
{
  "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é.

Vérifier la signature (Node.js)
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.

Adresse du serveur
https://api.peerbeta.com/mcp

Outils : 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.

HTTPCodeQuand
400VALIDATIONUn champ manque ou est mal formé ; `field` le nomme.
401UNAUTHENTICATEDAucune clé dans l’en-tête Authorization.
401API_KEY_INVALIDClé inconnue, expirée ou révoquée.
403FORBIDDENLa clé n’a pas la portée, ou l’API n’est pas ouverte.
404NOT_FOUNDCe test ou ce rapport n’est pas dans l’organisation de la clé.
409CONFLICTUn test est déjà en cours pour cette app, ou la clé d’idempotence a servi avec un autre corps.
429RATE_LIMITEDTrop de requêtes ; `Retry-After` dit quand réessayer.

Référence

RequêtePortéeRend
GET /organizationworkspaces.readL’organisation de la clé : identité de facturation, comptes Play Console, crédits communs.
GET /teststests.readLes tests de l’organisation, du plus récent au plus ancien (`status`, `limit`, `cursor`).
POST /teststests.writeCréer un test (et son app si le paquet est nouveau).
GET /tests/{testId}tests.readUn test : mode, offre, configuration, dates, liens.
GET /tests/{testId}/statustests.readL’état du test maintenant : jour, testeurs, retours, rapport.
GET /tests/{testId}/reportreports.readLe rapport en JSON (avec un lien PDF signé) ou en Markdown (`format=markdown`).
GET /reports/{reportId}/pdfaucuneLe 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.