API reference

The OpenAPI contract, Swagger UI, and the analytics, events, experiment and purchase routes with the permission each one requires.

The contract

The API is defined with OpenAPI and the contract is generated from the code, so it cannot describe routes that do not exist. Two URLs, both public:

The typed TypeScript SDK validates every response against the same @broctic/core schemas the routes are declared with, which is what keeps client and server from drifting apart.

Authentication

Organization API keys

Server-to-server calls use an organization API key, prefix brk_, created under Settings → API keys. Send it as x-api-key, or as a Bearer token through the SDK. It is scoped to one organization and carries that key's permissions.

curl
curl https://api.broctic.com/v1/analytics?days=30 \
  -H "x-api-key: brk_…"

Publishable keys

The routes a shipped app calls are the exception: event ingest, the onboarding funnel it should show, and its purchases. They take a per-app publishable key, prefix bpk_, in the x-broctic-key header, and can do nothing except those things for that one app. The key is meant to ship inside an app binary. See events.

Sessions

The console authenticates with a session cookie rather than an API key. Browser code should never hold a brk_ key.

App Store analytics routes

RouteWhat it doesRequires
GET /v1/apple/connectionThe App Store Connect connection and its statusanalytics: read
POST /v1/apple/connectionConnect an App Store Connect keyintegration: manage
DELETE /v1/apple/connectionDisconnect and delete the keyintegration: manage
GET /v1/apple/appsApps the key can see, and what they are linked toanalytics: read
POST /v1/apple/apps/refreshRe-ask Apple which apps existintegration: manage
POST /v1/apple/apps/linkLink an Apple app to a Broctic appintegration: manage
GET /v1/analyticsThe read model: totals, series, breakdowns, freshnessanalytics: read
POST /v1/analytics/syncPull the days that need attention nowanalytics: sync

sync sits with read rather than with integration: manage: it exposes nothing a reader cannot already see, and gating it behind admin left members watching figures they could not refresh.

Event, funnel and insight routes

RouteWhat it doesRequires
POST /v1/eventsIngest a batch of up to 100 product eventspublishable key (bpk_)
GET /v1/apps/{id}/keysThe app's publishable keys (prefixes only)app: read
POST /v1/apps/{id}/keysMint a publishable key — the only time it is shownapp: update
DELETE /v1/apps/{id}/keys/{keyId}Revoke a publishable keyapp: update
GET /v1/apps/{id}/events/summaryWhat has arrived: names, people, platforms, versionsanalytics: read
GET /v1/apps/{id}/funnelsThe app's funnelsanalytics: read
POST /v1/apps/{id}/funnelsCreate a funnelapp: update
PUT /v1/apps/{id}/funnels/{funnelId}Edit a funnel's stepsapp: update
DELETE /v1/apps/{id}/funnels/{funnelId}Delete a funnelapp: update
GET /v1/apps/{id}/funnels/{funnelId}/reportStep counts, rates, median times, verdictsanalytics: read
GET /v1/apps/{id}/insightsStore-side conversions plus the default funnel, with recommendationsanalytics: read

Only the first row is called by a shipped app, and it is the only one that takes a publishable key. Everything else is a console route: a signed-in member of the organization that owns the app. An app id belonging to another organization returns 404, not 403 — whether it exists is not something to confirm.

Routes a shipped app calls

RouteWhat it doesRequires
GET /v1/experimentsThe onboarding funnel this person should see, and the arm it came frompublishable key (bpk_)
GET /v1/purchases/entitlementWhether this person may use what they paid forpublishable key (bpk_)
POST /v1/purchases/verifyHand over a StoreKit 2 transaction the moment it completespublishable key (bpk_)
POST /v1/purchases/notificationsApp Store Server Notifications V2, from AppleApple's signature

Each of these fails well: an app that cannot reach them keeps the funnel it shipped with and the entitlement the device already knows. The last row is called by Apple, not the app, and is authenticated by Apple's signature checked against a pinned root. See purchases.

Purchase key routes

RouteWhat it doesRequires
GET /v1/purchases/connectionThe organization's In-App Purchase key and its status — never the key itselfintegration: manage
PUT /v1/purchases/connectionConnect an In-App Purchase keyintegration: manage
DELETE /v1/purchases/connectionDisconnect and delete the keyintegration: manage

Limits and conventions

  • Rate limits are per identity: 240 requests a minute on /v1/*, 60 a minute on log ingest, and 600 batches a minute per publishable key on event ingest — the last one is keyed by the key rather than the IP, because a fleet of phones behind one carrier NAT is a single address. The purchase routes a device calls allow 1,200 a minute per app, and Apple's notifications 600 a minute per app.
  • Request bodies are capped at 64KB.
  • Money is in minor units — an integer, times 100 — because floats drift and money must not.
  • Dates in App Store data are Pacific dates. Timestamps elsewhere are ISO 8601 with an offset.
  • Cross-organization access returns 404 rather than 403: the existence of another organization's app is not something to confirm.