SDK reference
The wire-level API behind the optional SDKs. Two write endpoints, no API key: devices prove which app they are with App Attest.
What the SDK API is
RevenueHog needs no SDK: revenue, the live feed, metrics and alerts all work server-side from your App Store Connect key. The SDK API adds exactly one capability: user-level attribution, linking purchases to your own user IDs so anonymous transaction-derived customers upgrade to real profiles. Two endpoints cover it: identify (who the user is) and attribute (which transaction belongs to them). The SDKs page shows the Swift / React Native / Kotlin call sites.
Authentication
There is no API key. On iOS the SDK enrolls each install with App Attest: Apple signs a statement that the caller is your genuine app on a real device, RevenueHog verifies it against the App Store Connect data it already has, and mints an opaque device token (dt_…) the SDK sends as Authorization: Bearer dt_… from then on. Nothing to copy from a dashboard, nothing secret in your binary. Enrolled devices are listed (and revocable) under Settings → SDK devices.
POST /api/sdk/v1/attest/challenge
→ 200 { "challenge": "<base64url, single-use, 5 min TTL>" }
POST /api/sdk/v1/attest
Content-Type: application/json
{
"keyId": "<App Attest key id, base64>",
"attestation": "<attestation object, base64>",
"challenge": "<echoed back>",
"bundleId": "com.example.app"
}
→ 200 { "deviceToken": "dt_…" }
→ 401 attestation or challenge failed
→ 404 bundle id not connected to any RevenueHog orgCalls without a device token (the simulator, app extensions, React Native and Android today, raw HTTP integrations) are still accepted when the bundleId resolves to exactly one connected app, but the data is stored unverified and labeled that way in the dashboard. A verified jws on attribute upgrades the transaction link even without a token (see below).
POST /api/sdk/v1/identify
Upserts an app user. Repeat calls merge: attributes shallow-merge into what's stored, device fields update, and the user's last-seen timestamp bumps. Anonymous pre-login IDs (the SDKs send $anon_<uuid>) are ordinary user IDs here.
POST /api/sdk/v1/identify
Authorization: Bearer dt_… // omit when unattested (see Auth)
Content-Type: application/json
{
"appUserId": "user_42", // required: your user id
"bundleId": "com.example.app", // required
"platform": "ios", // optional: "ios" | "android"
"osVersion": "26.2", // optional
"deviceModel": "iPhone17,2", // optional
"locale": "en_US", // optional
"attributes": { "plan": "pro" } // optional, flat JSON ≤ 8 KB
}
→ 200 { "ok": true, "id": "…" }attributes must be a flat JSON object of at most 8 KB. platform accepts exactly ios or android; anything else is ignored rather than rejected.
POST /api/sdk/v1/attribute
Links a store transaction to an app user. One transaction belongs to one user per organization, and re-POSTing the same originalTransactionId with a different appUserId moves it. That upsert is the whole aliasing mechanism: after login, the SDKs re-send earlier anonymous transactions under the real user ID and history re-points automatically.
POST /api/sdk/v1/attribute
Authorization: Bearer dt_… // omit when unattested (see Auth)
Content-Type: application/json
{
"appUserId": "user_42", // required
"bundleId": "com.example.app", // required
"originalTransactionId": "2000000123456789", // required
"productId": "com.example.app.pro.monthly", // optional
"jws": "<StoreKit 2 signed transaction>" // optional, recommended on iOS
}
→ 200 { "ok": true }On iOS, send the StoreKit 2 signed transaction as jws. RevenueHog verifies Apple's signature chain and, when it checks out, trusts the verified fields over the raw ones and marks the link verified, even from an unattested caller. An unverifiable JWS (Xcode StoreKit testing signs locally) is simply ignored. Android clients send the Google Play purchase token as originalTransactionId; it's stored as-is (RevenueHog ingests Apple revenue today; the mapping is kept for future Play support).
Rate limits, CORS & errors
Attested writes are limited to 240 requests per minute per organization; unattested writes to 30 per minute per device. Over a limit, responses are 429 with a retry-after header (seconds). Other errors: 400 for malformed JSON or missing required fields, 401 for an invalid device token (the SDKs re-enroll once, then fall back to unattested). Both endpoints are idempotent in practice: the official SDKs retry with exponential backoff and queue offline, and re-sends merge rather than duplicate.
CORS is deliberately permissive (* origin, POST + OPTIONS): there is no secret to protect, and unresolvable writes are dropped, not stored. Responses are never cached.
Versioning
/v1/). Fields may be added over time; existing fields won't change meaning within v1.