# HTTP API The hosted public API origin is `https://api.appbase.so`, also supplied by App setup. Mobile clients receive a public write-only collection key; dashboard account tokens and provider secrets stay on trusted servers. ## Collection - `POST /v1/events/batch`: `x-api-key` collection key; JSON `{ "events": [...] }`, 1–100 events. Event fields and typed helpers are documented in the [SDK](sdk.md). The API validates, scopes, deduplicates and commits before returning accepted/rejected event IDs. Retry pending IDs with unchanged identity/timestamp. - `POST /v1/feedback`: same collection key; explicit message/optional reply email. Separate quotas, storage and deduplication. See [feedback](feedback.md). - `GET /health`: service/database health. Invalid keys return 401; paused collection 403; invalid requests 400; budgets 429 with Retry-After. Network failures/5xx are retryable. A collection key never grants report access. ## Dashboard and app setup All `/v1/admin/*` calls require `Authorization: Bearer ACCOUNT_ACCESS_TOKEN` in hosted InsForge mode, from a verified dashboard session. The API resolves ownership independently for every app or connection ID. A foreign object returns 404. Never extract cookies or put account tokens into client SDK code. Dashboard server actions perform these calls automatically. Legacy REPORTING_TOKEN applies only to the separate local backend. - `GET /v1/admin/apps`: account-owned apps. - `POST /v1/admin/apps`: `{name,slug}` creates a development/production pair. The returned collection keys are shown once; only hashes are stored. - `GET /v1/admin/apps/:id/setup`: app/environment, sibling environments, event count, latest receipt, ingestion usage. - `POST /v1/admin/apps/:id/collection-key`: `{replace:true}` replaces the key, immediately invalidating the old one. - `POST /v1/admin/apps/:id/collection`: `{enabled:boolean}` controls ingestion without deleting history. - `GET /v1/admin/apps/:id/report?from=YYYY-MM-DD&to=YYYY-MM-DD`: scoped report, inclusive/exclusive UTC dates. No 90-day limit. Alternatively use `?range=all` to resolve the selected app/environment’s earliest stored SDK or RevenueCat purchase/event through today UTC. `granularity` accepts `daily`, `weekly`, `monthly`, or `yearly`; monthly/yearly use calendar boundaries. More than 800 chart points automatically selects a coarser interval, returned in `dashboard.granularity`, without shortening the date range or totals. - `GET /v1/admin/apps/:id/journey`: scoped recent events/user journey. Use dashboard-generated query parameters. - Feedback reads/status mutations are described in the feedback guide. ## Agent setup access The customer clicks **Copy prompt** in the empty dashboard or **Create app** dialog. This avoids exposing dashboard sessions to the coding agent. Read the complete [agent workflow](https://appbase.so/docs/agent-setup.md). With `Authorization: Bearer apb_setup_…`: - `POST /v1/agent/apps`: strict `{name,slug}`; creates one owned development/production pair (201). Repeating the same slug with the same active token returns the same credentials (200). Different slug, an existing app or a subsequently rotated key returns 409. Creation and credential recovery are atomic, including simultaneous retries. - `GET /v1/agent/setup`: creation/delivery state and per-environment event, first-open and foreground-open counts. No event payloads, identities, secrets or analytics reports. - `GET /v1/agent/revenuecat`: this app's connection state (`authorization_required`, `selection_required`, `connected`, `reconnect_required`), login-preserving owner `connect_url`, provider/app/environment mappings and aggregate mapped receipt counts. `delivery_verified` requires an actual mapped webhook, not configuration alone. Provider credentials and customer records are never returned. - `POST /v1/agent/revenuecat/retry` with `{}`: finish an interrupted webhook setup only for a provider selection already saved by this app's owner. No new selection, provider key submission, manual-connection takeover or other configuration is permitted. Maximum three attempts/minute/IP. - `POST /v1/agent/revenuecat/sync` with `{}`: bounded read-only provider import for this app's automatically connected projects. Returns `{completed:true,history_coverage:"partial"}` only; no customer records. Maximum three attempts/minute/IP. Uses existing sync limits and connection locks, and never invents complete history. Setup access stays valid until revoked and is limited to one app; a prompt copied from an existing app recovers that app’s saved keys only. It is not valid on collection or `/v1/admin/*` routes. A valid collection key is still required for event delivery. No API for silent replacement or claiming an existing app is exposed to setup agents. These account-only routes back the dashboard (setup tokens cannot call them): - `POST /v1/admin/agent-setups`: `{}`; issues a token once. Maximum five active prompts per account and sixty copy/issue requests per hour per IP. - `GET /v1/admin/agent-setups`: newest 20 own access records, metadata only. - `GET /v1/admin/agent-setups/:id/status`: own setup progress, including after expiry/revocation. - `POST /v1/admin/agent-setups/:id/revoke`: `{}`; immediately rejects further token use and erases its encrypted token and key recovery copies. Existing app collection keys remain valid. Token hashes and encrypted recovery credentials are private in `agent_setups`, inaccessible through the generated Data API. The direct `/v1/admin/apps` legacy API stores only collection-key hashes. New dashboard manual creation and agent setup retain an AES-256-GCM recovery envelope to handle lost responses and returning to setup. The authenticated owner can read saved SDK configuration at `GET /v1/admin/apps/:id/setup-credentials`; keys must still match the app’s current hashes. This never issues a token or rotates keys. New tokens have `expires_at: null` (no expiration). Recovery envelopes remain available until revocation, which clears them immediately. Legacy expired keys stay invalid and their envelopes are lazily cleared on the next owner issuance. No scheduled deletion job runs. ## RevenueCat webhook inbox ### Automatic connection API Account-only routes under `/v1/admin/apps/:id/revenuecat-connect` use the app's development/production pair and enforce verified ownership: - `GET` returns safe status; `GET /projects` and `GET /apps?project_id=…` return available selection metadata. - `POST /key` accepts strict `{project_id,api_key}`; validates provider read access and stores encrypted credentials. Webhook-write permission is checked when creating the webhook. - `POST /oauth/start` accepts `{browser_binding}` and returns an authorization URL/state; `POST /oauth/finish` accepts `{state,code,browser_binding}`. State expires after 10 minutes, is consumed once, and binds account/app/browser with S256 PKCE. This short OAuth handshake lifetime does not expire persistent Appbase setup access. - `POST /complete` accepts `{project_id,provider_app_id}`. Verifies the selected provider app/store, persists the receiver/mappings, then creates or recovers its own webhook. Existing manual/paused connections and other app mappings are never taken over. - `POST /disconnect` clears the app's stored provider grant. It does not delete webhook delivery or analytics history. OAuth/key grants are never returned by the API. Agents use the handoff/status endpoints above. They do not copy account-session cookies or submit provider secrets. OAuth must first be registered by the service operator; the API-key flow remains available while registration is pending. Connection status includes `history`, one entry per mapped connection: `state` (`not_started`, `running`, `completed`, `failed`, `interrupted`), `completed_at`, app-scoped stored `events`, safe `error_code` and `coverage:"partial"`. Completing automatic setup attempts an initial bounded import after the webhook is live. Import failure does not roll back a connected webhook; use the sync endpoint to retry. OAuth insufficient permissions return HTTP 403 with `code:"revenuecat_permissions"`. Read & Write and sufficient Custom scopes work; Read only cannot provision a webhook. The simplest setup is **App setup → Connect RevenueCat**. [Full guide](revenuecat-setup.md). - `POST /v1/admin/revenuecat/connections`: `{name,project_id}`. Returns connection, webhook_path and a one-time authorization value. A project is unique within its owner account. - `GET /v1/admin/revenuecat/connections`: owned connections; presence flags never reveal stored credentials. - `GET /v1/admin/revenuecat/connections/:id`: mappings, receipt summaries and recent delivery metadata. - `POST .../:id/mappings`: `{provider_app_id,store,environment,app_id}`. Exact store/app mapping; SANDBOX maps to development, PRODUCTION to production. TEST_STORE is sandbox-only. An existing mapping cannot silently move to another analytics app. - `POST .../:id/credentials`: `{api_key?:string|null,signing_secret?:string|null}`. API key must be a secret v2 key. The API checks access to the project's products before encrypted persistence; sync tests remaining permissions. Omitted fields stay unchanged; null removes the credential and its legacy fallback. Never logs or returns submitted secrets. - `POST .../:id/authorization`: `{replace:true}` returns a replacement webhook authorization and revokes the old value immediately. - `POST .../:id/settings`: `{enabled:boolean}` pauses/resumes webhook reception. Provider retries may follow a pause. - `POST .../:id/replay`: `{event_ids:[...]}` explicitly retries routing 1–100 stored receipts, for example after adding a missing mapping. Attributed receipts cannot move between apps. - `POST /v1/admin/apps/:id/revenuecat-sync`: performs bounded read-only provider history/catalog/subscription sync using stored credentials. Previous snapshots survive failure. - `POST /v1/webhooks/revenuecat/:connectionId`: provider endpoint; requires the generated authorization and a valid HMAC signature if configured. Persists the original payload before acknowledgement. TEST receipts check delivery only. All credentials and billing history remain behind the account-authorized Fastify boundary; InsForge's generated Data API cannot read these tables. ### Copyable setup prompts Account-only `POST /v1/admin/agent-setups` accepts an empty object to issue new access, `{ "reuse": true }` to copy existing unbound access (or issue it once), `{ "app_id": "UUID" }` to copy access already scoped to an owned app, or `{ "app": { "name": "My app", "slug": "my-app" } }` to create a manual app and its recoverable setup prompt atomically. `app` and `app_id` are mutually exclusive. New access returns 201; reused access returns 200. Both return `{setup,token}`; manual creation additionally returns `pair`. Copying existing-app access never rotates keys. Missing saved keys or changed hashes return 409; foreign app IDs return 404. No page-view GET mints credentials. Tokens are hashed for authentication and additionally encrypted for owner-only recopy; lists/status omit all secret fields. Revocation erases both recovery ciphertexts.