{"openapi":"3.1.0","info":{"title":"Peerbeta API","version":"1.0.0","description":"Create closed tests for Android apps, follow them day by day and fetch their report. Keys belong to an organization (Settings → API keys). Each key may make 60 requests a minute and create 50 tests a day per organization. Every answer carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. Peerbeta prepares the request for production access; Google alone decides.","contact":{"url":"https://peerbeta.com/docs/api"}},"servers":[{"url":"https://api.peerbeta.com/api/v1"}],"security":[{"bearer":[]}],"tags":[{"name":"Organization"},{"name":"Tests"},{"name":"Reports"}],"paths":{"/organization":{"get":{"tags":["Organization"],"summary":"The key's organization","description":"Scope `workspaces.read`. Its name, billing identity, Play Console accounts and shared credit wallet.","operationId":"getOrganization","responses":{"200":{"description":"The organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Organization"}}}},"401":{"description":"Missing, unknown, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope, or the module is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tests":{"get":{"tags":["Tests"],"summary":"List the organization's tests","description":"Scope `tests.read`. Newest first.","operationId":"listTests","parameters":[{"name":"status","in":"query","schema":{"type":"string"},"description":"Only tests in this status."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"`nextCursor` of the previous page."}],"responses":{"200":{"description":"A page of tests.","content":{"application/json":{"schema":{"type":"object","properties":{"tests":{"type":"array","items":{"$ref":"#/components/schemas/Test"}},"nextCursor":{"type":["string","null"]}}}}}},"401":{"description":"Missing, unknown, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope, or the module is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tests"],"summary":"Create a test","description":"Scope `tests.write`. Creates the app (or reuses the organization's app with this package) and a draft test. With `funding`, attaches a paid order or holds the credits queue's credits. With `optInUrl`, the link is checked at once. The Play Console steps are confirmed by the developer on the test's page (`links.dashboard`). Send an `Idempotency-Key` header to retry safely: the same key returns the same test for 24 hours.","operationId":"createTest","parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":100}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTest"}}}},"responses":{"200":{"description":"Already created with this `Idempotency-Key`.","content":{"application/json":{"schema":{"type":"object","properties":{"test":{"$ref":"#/components/schemas/Test"}}}}}},"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"test":{"$ref":"#/components/schemas/Test"}}}}}},"400":{"description":"Invalid field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, unknown, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope, or the module is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The app already has a test under way, or the order or credits are not available.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests. `Retry-After` says when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tests/{testId}":{"get":{"tags":["Tests"],"summary":"One test","description":"Scope `tests.read`.","operationId":"getTest","parameters":[{"name":"testId","in":"path","required":true,"schema":{"type":"string"},"description":"The test's id."}],"responses":{"200":{"description":"The test.","content":{"application/json":{"schema":{"type":"object","properties":{"test":{"$ref":"#/components/schemas/Test"}}}}}},"401":{"description":"Missing, unknown, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope, or the module is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not a test of this organization."},"429":{"description":"Too many requests. `Retry-After` says when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tests/{testId}/status":{"get":{"tags":["Tests"],"summary":"The test's state, now","description":"Scope `tests.read`. Testers with a verified install, active today, the day of the 14, the eligibility date.","operationId":"getTestStatus","parameters":[{"name":"testId","in":"path","required":true,"schema":{"type":"string"},"description":"The test's id."}],"responses":{"200":{"description":"The state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestStatus"}}}},"401":{"description":"Missing, unknown, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope, or the module is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not a test of this organization."},"429":{"description":"Too many requests. `Retry-After` says when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tests/{testId}/report":{"get":{"tags":["Reports"],"summary":"The test's report","description":"Scope `reports.read`. The latest report in JSON (its numbers, its synthesis, a signed PDF link valid 15 minutes), or in Markdown with `format=markdown`. The report carries the organization's name and logo (white label).","operationId":"getTestReport","parameters":[{"name":"testId","in":"path","required":true,"schema":{"type":"string"},"description":"The test's id."},{"name":"format","in":"query","schema":{"type":"string","enum":["json","markdown"],"default":"json"}}],"responses":{"200":{"description":"The report.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Report"}},"text/markdown":{"schema":{"type":"string"}}}},"401":{"description":"Missing, unknown, expired or revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the scope, or the module is off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No report yet, or not a test of this organization."},"429":{"description":"Too many requests. `Retry-After` says when to try again.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reports/{reportId}/pdf":{"get":{"tags":["Reports"],"summary":"The report's PDF, by signed link","description":"No key: the link of `pdf.url` carries its own signature and expiry. Ask the report again for a new link.","operationId":"getReportPdf","security":[],"parameters":[{"name":"reportId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"integer"}},{"name":"signature","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The PDF.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Wrong signature, or the link expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such report."}}}}},"webhooks":{"test.started":{"post":{"summary":"test.started","description":"Day 1 starts: enough testers have a verified install. Signed: `x-peerbeta-signature: sha256=<hex>` is the HMAC SHA-256 of `<x-peerbeta-timestamp>.<body>` with the destination's secret.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges. Anything else is tried again: after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h."}}}},"test.paused":{"post":{"summary":"test.paused","description":"The test is paused (by you, by Peerbeta, or a broken link). Signed: `x-peerbeta-signature: sha256=<hex>` is the HMAC SHA-256 of `<x-peerbeta-timestamp>.<body>` with the destination's secret.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges. Anything else is tried again: after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h."}}}},"tester.installed":{"post":{"summary":"tester.installed","description":"A tester installed the app, verified by the tester app. Signed: `x-peerbeta-signature: sha256=<hex>` is the HMAC SHA-256 of `<x-peerbeta-timestamp>.<body>` with the destination's secret.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges. Anything else is tried again: after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h."}}}},"checkpoint.completed":{"post":{"summary":"checkpoint.completed","description":"A tester sent their day 1, day 7 or day 14 feedback. Signed: `x-peerbeta-signature: sha256=<hex>` is the HMAC SHA-256 of `<x-peerbeta-timestamp>.<body>` with the destination's secret.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges. Anything else is tried again: after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h."}}}},"test.completed":{"post":{"summary":"test.completed","description":"The 14 days are over. Signed: `x-peerbeta-signature: sha256=<hex>` is the HMAC SHA-256 of `<x-peerbeta-timestamp>.<body>` with the destination's secret.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges. Anything else is tried again: after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h."}}}},"report.ready":{"post":{"summary":"report.ready","description":"The report is written: JSON, Markdown and PDF. Signed: `x-peerbeta-signature: sha256=<hex>` is the HMAC SHA-256 of `<x-peerbeta-timestamp>.<body>` with the destination's secret.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Any 2xx acknowledges. Anything else is tried again: after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h."}}}}},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"An API key of the organization: `Authorization: Bearer pbk_…`."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"A stable code: API_KEY_INVALID, FORBIDDEN, NOT_FOUND, VALIDATION, CONFLICT, RATE_LIMITED, LINK_EXPIRED…"},"message":{"type":"string"},"field":{"type":"string","description":"The field at fault, for VALIDATION."}}},"Organization":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"billing":{"type":["object","null"],"properties":{"legalName":{"type":"string"},"address":{"type":["string","null"]},"country":{"type":["string","null"]},"vatNumber":{"type":["string","null"]}}},"playConsoleAccounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string","enum":["personal_new","personal_old","organization"]},"developerId":{"type":["string","null"]},"ruleApplies":{"type":"boolean"}}}},"wallet":{"type":"object","properties":{"balance":{"type":"integer","description":"Credits members gave to the organization, not used yet."}}}}},"CreateTest":{"type":"object","required":["app"],"properties":{"app":{"type":"object","required":["name","packageName","category"],"properties":{"name":{"type":"string","maxLength":80},"packageName":{"type":"string","examples":["com.example.notes"]},"category":{"type":"string","enum":["art_design","auto_vehicles","beauty","books_reference","business","comics","communication","dating","education","entertainment","events","finance","food_drink","game","health_fitness","house_home","libraries_demo","lifestyle","maps_navigation","medical","music_audio","news_magazines","parenting","personalization","photography","productivity","shopping","social","sports","tools","travel_local","video_players","weather"]},"pricing":{"type":"string","enum":["free","paid"],"default":"free"},"activities":{"type":"array","items":{"type":"string","enum":["finance","health","vpn","government"]}},"launchActivity":{"type":"string","examples":["com.example.notes.MainActivity"]},"description":{"type":"string","maxLength":400},"countries":{"type":"array","items":{"type":"string","pattern":"^[A-Z]{2}$"},"description":"Left out: all countries."},"playConsoleAccountId":{"type":"string"}}},"features":{"type":"array","maxItems":8,"items":{"type":"object","required":["title","instruction"],"properties":{"title":{"type":"string","maxLength":80},"instruction":{"type":"string","maxLength":300}}}},"optInUrl":{"type":"string","examples":["https://play.google.com/apps/testing/com.example.notes"]},"instructions":{"type":"string","maxLength":1000},"funding":{"oneOf":[{"type":"object","required":["type","orderId"],"properties":{"type":{"const":"order"},"orderId":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"const":"credits"},"wallet":{"type":"string","enum":["organization","personal"],"default":"organization"}}},{"type":"object","required":["type"],"properties":{"type":{"const":"circle"}}}]}}},"Test":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","description":"draft, setup, ready, recruiting, pretest, running, paused, finished, reported, applied, accepted, refused, refunded, cancelled, closed."},"mode":{"type":["string","null"],"enum":["offer","circle","credits_queue",null]},"offer":{"type":["string","null"],"enum":["express","pro","launch",null]},"app":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"packageName":{"type":"string"},"playConsoleAccount":{"type":["object","null"]}}},"setup":{"type":"object","properties":{"done":{"type":"integer"},"total":{"type":"integer"},"complete":{"type":"boolean"},"linkStatus":{"type":"string"},"optInUrl":{"type":["string","null"]},"issues":{"type":"array","items":{"type":"string"}}}},"dates":{"type":"object","properties":{"createdAt":{"type":["string","null"],"format":"date-time"},"readyAt":{"type":["string","null"],"format":"date-time"},"recruitingAt":{"type":["string","null"],"format":"date-time"},"day1At":{"type":["string","null"],"format":"date-time"},"endsAt":{"type":["string","null"],"format":"date-time"},"eligibleAt":{"type":["string","null"],"format":"date-time"}}},"links":{"type":"object","properties":{"dashboard":{"type":"string"},"checkout":{"type":["string","null"]},"report":{"type":"string"}}}}},"TestStatus":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"day":{"type":"object","properties":{"current":{"type":["integer","null"]},"of":{"type":"integer"}}},"testers":{"type":"object","properties":{"target":{"type":"integer"},"startThreshold":{"type":"integer"},"required":{"type":"integer","description":"Google's rule: 12."},"accepted":{"type":"integer"},"installed":{"type":"integer","description":"Verified by the tester app."},"activeToday":{"type":"integer"},"atRisk":{"type":"integer"},"lost":{"type":"integer"},"completed":{"type":"integer"}}},"feedback":{"type":"object","properties":{"day1":{"type":"integer"},"day7":{"type":"integer"},"day14":{"type":"integer"}}},"dates":{"type":"object","properties":{"day1At":{"type":["string","null"],"format":"date-time"},"endsAt":{"type":["string","null"],"format":"date-time"},"eligibleAt":{"type":["string","null"],"format":"date-time"}}},"report":{"type":"object","properties":{"ready":{"type":"boolean"},"generatedAt":{"type":["string","null"],"format":"date-time"}}},"updatedAt":{"type":"string","format":"date-time"}}},"Report":{"type":"object","properties":{"id":{"type":"string"},"testId":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"writtenBy":{"type":"string","enum":["ai","rules"]},"locale":{"type":"string"},"branding":{"type":["object","null"],"properties":{"name":{"type":"string"},"logoUrl":{"type":["string","null"]}}},"data":{"type":"object","description":"The report's numbers: participation, retention, devices, countries, features, bugs, quotes, updates."},"synthesis":{"type":"object","properties":{"summary":{"type":"string"},"features":{"type":"array"},"recommendations":{"type":"array","items":{"type":"string"}}}},"markdown":{"type":"string"},"pdf":{"type":["object","null"],"properties":{"url":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","examples":["evt_…"]},"type":{"type":"string","enum":["test.started","test.paused","tester.installed","checkpoint.completed","test.completed","report.ready"]},"created":{"type":"integer","description":"Milliseconds since 1970."},"data":{"type":"object","properties":{"test":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"app":{"type":"object"},"day1At":{"type":["string","null"],"format":"date-time"},"endsAt":{"type":["string","null"],"format":"date-time"},"eligibleAt":{"type":["string","null"],"format":"date-time"}}},"tester":{"type":"object","properties":{"number":{"type":"integer"}},"description":"tester.installed, checkpoint.completed."},"checkpoint":{"type":"string","enum":["day1","day7","day14"],"description":"checkpoint.completed."},"report":{"type":"object","description":"report.ready: its id and API path."}}}}}}}}