Reference
HTTP API reference
The /api/sdk endpoints behind every InfluTo SDK, the RevenueCat and Google Play webhooks InfluTo receives, and where the agent REST API is documented.
The SDKs are thin clients over a small HTTP API. Use this page when your stack has no SDK, or to see what an SDK sends while you debug. If an SDK exists for your platform, use it — it also stores the attribution, sets RevenueCat attributes and de-duplicates for you. The full wire contract, including the rules every SDK follows, is at docs.influ.to/contract.md (wire version 1.6.0).
Basics
- Base URL:
https://influ.to/api - Auth:
Authorization: Bearer <SDK key>on every request. The key starts withit_and is on Setup & SDK; one key per app covers sandbox and production. It only works on the/api/sdk/*endpoints of its own app. It ships inside your app, so treat it as public. - Format: JSON bodies with
Content-Type: application/json. Field names are snake_case./sdk/eventand/sdk/purchasealso accept camelCase, which is what the SDKs send there. - Errors:
{"detail": "…"}with the status code. A missing or mistyped field is a422with the field list indetail. A request over the rate limit gets a plain429page. All codes: Limits and errors. - The server reads the client IP and User-Agent from the request itself; don't put them in the body.
curl https://influ.to/api/sdk/validate-code \
-H "Authorization: Bearer $INFLUTO_SDK_KEY" \
-H "Content-Type: application/json" \
-d '{"code": "MAYA"}'Endpoints
| Method | Path | Purpose | SDK method |
|---|---|---|---|
POST |
/sdk/init |
Start-up check; says whether the platform is store-direct | initialize |
POST |
/sdk/track-install |
Decide whether this install was referred | checkAttribution |
POST |
/sdk/identify |
Attach your user ID to the install | identifyUser |
POST |
/sdk/event |
Record a custom event | trackEvent |
POST |
/sdk/validate-code |
Check a typed code | validateCode |
POST |
/sdk/set-referral-code |
Apply a code; redeem a free-access code | setReferralCode |
POST |
/sdk/purchase |
Report a store purchase (store-direct) | reportPurchase |
GET |
/sdk/access |
Whether a user has free access | checkAccess |
GET |
/sdk/campaigns |
Active public campaigns | getActiveCampaigns |
GET |
/sdk/attribution |
A paying user's attribution | — |
GET |
/sdk/recent-conversions |
A user's recent purchase outcomes, for testing | — |
POST /sdk/init
Call once per app start. It marks the SDK as installed on the Setup page and tells the client whether this platform uses store-direct validation.
| Field | Required | Notes |
|---|---|---|
app_version |
Yes | Your app's version ("unknown" if you don't have it) |
sdk_version |
Yes | Your client's version |
platform |
Yes | ios, android or web |
sdk_name, runtime |
No | Sent by the web SDK (web; capacitor, cordova, twa, pwa or browser) |
{
"app_id": "3f6c…",
"app_name": "Your App",
"attribution_window_hours": 24,
"campaigns": [{ "id": "a1b2…", "name": "Launch", "description": null }],
"store_direct": false,
"initialized": true
}campaigns lists every active campaign of the app. store_direct is true when this platform validates purchases directly with Apple or Google; the SDKs start automatic purchase capture only then.
POST /sdk/track-install
Decides once per install whether a creator referred it — by the code a link carried into the app, or by link-click matching within 72 hours. Call it on first launch and store the answer; the SDKs never call it again after they have one.
| Field | Required | Notes |
|---|---|---|
platform |
Yes | ios, android or web |
device_id |
Recommended | A random UUID you generate once per install and persist. Installs are counted once per device_id. |
device_brand, device_model, os_version, screen_resolution, timezone, language |
No | Device details the SDKs send for matching. camelCase also accepted. |
referral_code, click_id |
No | From a link that carried the code into the app (influto_code / influto_click on a web app's URL, or the Play Install Referrer). The install is then attributed exactly (link_param). |
{
"attributed": true,
"referral_code": "MAYA",
"attribution_method": "ip_device_match",
"clicked_at": "2026-10-09T08:12:44.120000",
"click_id": "9d2e…",
"install_recorded": true,
"message": "Install attributed successfully"
}- Organic:
{"attributed": false, "install_recorded": true, "message": "No matching referral click found (organic install)"}. install_recordedisfalsewhen thisdevice_idwas seen before.- A device that already claimed a click within the window gets the same answer again, with
"re_claimed": true— so a lost response can be fetched again without taking someone else's click. attribution_method:link_paramorip_device_match(link-click matching). See attribution.
POST /sdk/identify
Attaches your user ID — usually the RevenueCat app user ID — to the install. When a RevenueCat event later arrives without the influto_code attribute, InfluTo looks for an attributed click with that user ID.
| Field | Required | Notes |
|---|---|---|
app_user_id |
Yes | Your user ID. Empty is a 400. |
properties |
No | Object, stored for your reference |
device_id (+ platform and the device fields) |
No | Send the same device_id you sent to track-install and the server matches this exact install. Without it, it falls back to the request's IP and only uses an unambiguous, recent match. |
{ "identified": true, "app_user_id": "user_123", "attribution_backfilled": true }The first identify for a click wins. The same key and user identified again within 60 seconds is answered from memory with "deduplicated": true. This endpoint has its own, lower rate limit — 2 requests per second per IP — so call it once per launch, not in a loop.
POST /sdk/event
Records a custom event for the dashboard funnel. It never creates a conversion or a commission.
| Field | Required | Notes |
|---|---|---|
event_type / eventType |
Yes | For example paywall_viewed, trial_started |
app_user_id / appUserId |
Yes | Your user ID |
properties |
No | Object |
referral_code / referralCode |
No | Code to associate |
event_id / eventId |
No | Your idempotency key — a repeat with the same ID is dropped |
{ "tracked": true, "event_id": "c71a…" }A dropped repeat answers {"tracked": true, "event_id": "…", "duplicate": true, "dedup_reason": "…"}. Besides event_id, the server keeps trial_started, subscription_purchased and subscription_renewed to one per user and property set, and drops an identical event without an event_id repeated within 60 seconds.
POST /sdk/validate-code
Checks a code a user typed, against the app's active creator codes and then its free-access codes. Always 200 (unless the request itself is rejected); look at valid.
| Field | Required | Notes |
|---|---|---|
code |
Yes | The server uppercases it and drops characters outside A–Z 0–9 _ - |
Creator code:
{
"valid": true,
"code": "MAYA",
"campaign": { "id": "a1b2…", "name": "Launch", "description": null, "commission_percentage": 30.0, "campaign_type": "public" },
"influencer": { "name": "Maya R.", "social_handle": "@maya", "follower_count": null },
"custom_data": null,
"message": "Valid code! Referred by Maya R."
}A free-access code returns campaign.name "Free Access", campaign.campaign_type "FREE_ACCESS", commission_percentage 0, influencer: null and the code's custom_data. influencer and message contain the creator's personal name — don't show them to users unless creators agreed.
Invalid: {"valid": false, "error": "Code not found or inactive", "error_code": "CODE_NOT_FOUND"}. The error_code values are INVALID_FORMAT (not 4–20 characters), CODE_NOT_FOUND and CODE_EXPIRED (a free-access code that expired or has no seats left).
POST /sdk/set-referral-code
Applies a code for this user. For a creator code it records the attribution; for a free-access code it grants the free access.
| Field | Required | Notes |
|---|---|---|
code |
Yes | Same cleanup as validate-code |
app_user_id |
For free-access codes | Without it a free-access code fails with MISSING_USER_ID |
Creator code:
{ "success": true, "code": "MAYA", "message": "Referral code set successfully", "campaign": { "id": "a1b2…", "name": "Launch" } }If this creator's link already attributed an install from the same IP within the window, nothing new is recorded: "message": "Code already attributed from link click" with "already_attributed": true. A new manual entry counts as a click and an install for the creator.
Free-access code:
{
"success": true,
"code": "PRESS2026",
"free_access": true,
"grants_access": true,
"already_granted": false,
"entitlement": "premium",
"access_duration_years": 1,
"access_duration_days": 365,
"expires_at": "2027-10-09T08:15:02",
"rc_grant": { "attempted": true, "ok": true, "error": null },
"message": "Free access activated — 1 year of premium."
}- Access is granted in InfluTo whether or not RevenueCat is involved;
rc_grantonly reports the optional RevenueCat promotional-entitlement mirror. Gate premium onGET /sdk/access, not onrc_grant. - Redeeming the same code again for the same user re-grants without using another seat (
already_granted: true). - A free-access failure is still
200, withsuccess: false,free_access: true, anderror_codeCODE_EXPIREDorMISSING_USER_ID. - An unknown or inactive code is
404{"detail": "Referral code not found or inactive"}.
POST /sdk/purchase
Store-direct only. Sends the store's signed proof of a purchase; InfluTo verifies it with Apple or Google, converts the price to USD at the purchase date's rate, and records it exactly like a RevenueCat event. See store-direct.
| Field | Required | Notes |
|---|---|---|
platform |
Yes | ios or android |
signed_transaction / signedTransaction |
iOS | StoreKit 2 jwsRepresentation (the signed JWS) |
purchase_token / purchaseToken |
Android | Play Billing purchaseToken |
product_id / productId |
Android one-time products | Routes the purchase to one-time validation. Omit for subscriptions. |
price, currency |
Android one-time products | Price paid (major units) and ISO currency, from ProductDetails — Google doesn't return a one-time price |
referral_code / referralCode |
First purchase | The stored referral code, so the purchase binds to the creator |
app_user_id / appUserId |
No | Your user ID; also makes the purchase show up in recent-conversions |
{
"success": true,
"validated": "apple",
"environment": "PRODUCTION",
"event_type": "INITIAL_PURCHASE",
"duplicate": false,
"recorded": true,
"result": { "processed": true, "event_id": "…" }
}| Status | Meaning |
|---|---|
200 |
Verified and processed. duplicate: true means it was already recorded — reporting twice is safe. |
400 |
The platform isn't switched to store-direct yet (switching needs a green credential check), credentials are missing, the proof is missing, verification failed, or the platform isn't ios/android. |
409 |
Android one-time purchase that isn't in the PURCHASED state (for example, still pending). |
503 |
The exchange rate isn't available right now. Retry later. |
Apple purchases are keyed on the original transaction ID, Google subscriptions on the purchase token, Google one-time purchases on the order ID — so renewals and refunds found later attach to the same conversion.
GET /sdk/access
/sdk/access?app_user_id=user_123 — whether InfluTo currently grants this user free access from a free-access code.
{ "has_access": true, "source": "comp", "entitlement": "premium", "expires_at": "2027-10-09T08:15:02", "code": "PRESS2026" }Without access: {"has_access": false, "source": null, "entitlement": null, "expires_at": null, "code": null}. Seat limits and expiry are checked on every call; a blocked or expired code removes access. An empty app_user_id is a 400.
GET /sdk/campaigns
The app's active public campaigns, for showing offers in the app.
[{ "id": "a1b2…", "name": "Launch", "description": null, "commission_percentage": 30.0 }]GET /sdk/attribution
/sdk/attribution?app_user_id=user_123 — which creator a paying user is attributed to. It reads conversions, so it answers attributed: true only once an attributed purchase exists.
{
"attributed": true,
"referral_code": "MAYA",
"campaign": { "id": "a1b2…", "name": "Launch" },
"attribution_timestamp": "2026-10-09T09:01:13",
"attribution_method": "ip_device_match"
}Otherwise: {"attributed": false, "message": "User has no attribution (organic user)"}.
GET /sdk/recent-conversions
/sdk/recent-conversions?app_user_id=user_123&limit=10 — the user's latest purchase events in this app, newest first, so a test build can confirm a purchase reached InfluTo. limit is 1–50 (default 10). No amounts and no creator names.
{
"app_user_id": "user_123",
"count": 1,
"attributed_count": 1,
"conversions": [{
"event_type": "INITIAL_PURCHASE", "product_id": "pro_monthly", "store": "APP_STORE",
"environment": "SANDBOX", "is_test": true, "attributed": true, "referral_code": "MAYA",
"status": "active", "event_timestamp": "2026-10-09T09:01:13"
}]
}Inbound webhooks
InfluTo receives purchase events from RevenueCat; on store-direct apps it checks purchases with Apple and Google itself. It sends no webhooks to your servers. To follow what happens, use Real-Time Events in the dashboard or the agent API.
RevenueCat
POST https://influ.to/api/webhooks/revenuecat/{app_id} — Setup & SDK shows the full URL with your app's ID. Set it up as described in RevenueCat.
- Auth: RevenueCat's "Authorization header value" must be
Bearer rc_webhook_…, the secret InfluTo generates under Setup & SDK → Webhook (shown once; regenerate it there or in Settings → Integration). The bare token, a lowercasebearerand stray whitespace are accepted too. Rejected calls are counted and shown on the Setup page. - Body: RevenueCat's standard webhook JSON. InfluTo reads the code from the subscriber attribute
influto_code.influto_referralis for your own RevenueCat targeting; InfluTo doesn't read it. Without a code, it looks for an attributed click with the event's app user ID — see/sdk/identify. A user who already converted for a creator stays with that creator, whatever code arrives later. - Environments: send both.
SANDBOX,XCODEandLOCAL_TESTINGevents go through attribution but never reserve money or count in stats.
| Event | What InfluTo does |
|---|---|
TEST |
Marks the webhook as connected |
INITIAL_PURCHASE |
Creates the conversion when the user is attributed; a paid purchase earns commission, a trial waits until it converts |
RENEWAL |
Commission for each paid renewal |
NON_RENEWING_PURCHASE |
Commission for each one-time purchase |
CANCELLATION, UNCANCELLATION, EXPIRATION |
Updates the subscription's state |
PRODUCT_CHANGE |
Updates the product; the next renewal's price sets the commission |
BILLING_ISSUE |
Logged |
REFUND |
Reverses the commission |
Promotional grants (store PROMOTIONAL, including the ones InfluTo makes for free-access codes) are acknowledged and ignored, as are event types not in the table.
| Response | When |
|---|---|
200 {"received": true, "event_id": "…"} |
Accepted and processed |
200 {"received": true, "error": "…", "event_id": "…"} |
Accepted, but processing failed. Not retried by RevenueCat or by InfluTo — the error is in the response body in RevenueCat's delivery log. |
400 |
The body isn't JSON |
401 |
Missing or wrong Authorization value; the body says how to fix it |
404 |
No app with this ID |
Events are idempotent on app, event type, transaction ID and event timestamp, so a redelivered event is recorded once.
Agent REST API
The API that AI agents and scripts use to run your program — apps, campaigns, creators, analytics, integration status — lives under https://influ.to/api/agent/*. It uses an agent key (it_mcp_…, from Connect AI Agent) or an OAuth token, never the SDK key. The MCP tools that read or change your account are wrappers over these endpoints.
- Endpoints, scopes and errors: MCP tools, scopes and REST API
- OpenAPI spec: https://influ.to/api/agent/openapi.json
- Status codes: Limits and errors