Reference
Limits and errors
Rate limits, request size, SDK error types and codes, HTTP status codes for the SDK API, webhooks and the agent API, and the rules for codes and app slugs.
This page collects every limit and error you can run into: how fast you can call InfluTo, what each SDK throws, what each status code means, and the format rules for codes and app slugs. For the endpoints themselves, see the HTTP API; for the methods, the SDK reference.
Rate limits
| What | Limit | Over the limit |
|---|---|---|
SDK API (/api/sdk/*) and referral links |
Per IP address, set well above what a real app or campaign produces — including many users behind one mobile-carrier address | 429 |
| MCP tool calls | 240 per minute per connected account | The tool call fails with an error that starts with 429 |
| MCP sign-in and client registration | Per IP address | 429 |
- If you get a
429, back off and retry; a script calling in a tight loop is the usual cause. identifyis de-duplicated on the server: the same SDK key and user ID within 60 seconds is answered without doing the work again ("deduplicated": true). Call it once per launch, never from a render loop.- The RevenueCat webhook and the agent REST API have no per-request limit; the agent API has invite limits instead.
Request size
- Every request body is limited to 20 MB; larger requests get
413. - Asset uploads in the dashboard (Assets) are limited to 20 MB per file.
What throws and what fails soft
Only initialize and reportPurchase throw on network and server errors. The rest return a safe value, so a slow or failed request never crashes your app.
| Method | On failure |
|---|---|
initialize |
Throws |
reportPurchase |
Throws |
checkAttribution |
{ attributed: false, message: "Error checking attribution" }, not stored, retried next launch. Throws when called before initialize succeeded (iOS returns attributed: false instead). |
identifyUser, trackEvent |
Nothing (logged when debug is on) |
getActiveCampaigns |
[] |
validateCode |
{ valid: false, error_code: "NETWORK_ERROR" } |
setReferralCode |
{ success: false, message: … } |
applyCode |
The validation result with applied: false |
checkAccess |
{ hasAccess: false } |
| Automatic capture | Failed reports are counted in failed and retried on a later back-sync. React Native's enableAutoPurchaseCapture and syncExistingPurchases throw when called before initialize. |
SDK error types
| Platform | Thrown type | What it carries |
|---|---|---|
| React Native | Error |
Message API request failed: <status> - <body>, or InfluTo SDK not initialized. Call InfluTo.initialize() first. |
| iOS | InfluToError |
.notInitialized; .notConfigured(String) for any 400; .retryable(status:) for any 5xx, including 503; .http(status:body:) for other statuses; .decoding(Error); .transport(Error) |
| Android | InfluToError (sealed, an Exception) |
NotInitialized; Server(status, body) with retryable (status 500 or higher) and notConfigured (status 400); Transport(cause); Decoding(cause) |
| Flutter | InfluToException |
statusCode, body, message; retryable (500 or higher), notConfigured (400). Network failures have no statusCode. |
| Web | InfluToApiError |
status, body, message for any non-2xx answer. Other failures — not initialized, no apiKey, reportPurchase without platform in a browser — throw a plain Error. |
notConfigured covers every 400 from /sdk/purchase: the platform isn't switched to store-direct yet, or the proof is missing or failed verification. A 503 means the exchange rate wasn't available; retry later.
try {
await InfluTo.reportPurchase({ platform: 'android', purchaseToken });
} catch (e) {
if (String(e).includes('failed: 503')) scheduleRetry();
}do {
_ = try await InfluTo.reportPurchase(jws: jws)
} catch InfluToError.retryable(let status) {
scheduleRetry(after: status)
} catch {
print("InfluTo:", error)
}try {
InfluTo.reportPurchase(purchaseToken = purchase.purchaseToken)
} catch (e: InfluToError.Server) {
if (e.retryable) scheduleRetry()
}try {
await InfluTo.instance.reportPurchase(platform: 'android', purchaseToken: token);
} on InfluToException catch (e) {
if (e.retryable) scheduleRetry();
}import InfluTo, { InfluToApiError } from '@influto/web';
try {
await InfluTo.reportPurchase({ platform: 'android', purchaseToken });
} catch (e) {
if (e instanceof InfluToApiError && e.status === 503) scheduleRetry();
}Code validation errors
validateCode and applyCode return these in error_code (React Native, web) or errorCode (iOS, Flutter as a string; Android as the CodeErrorCode enum, which adds UNKNOWN).
| Code | When | Server text in error |
|---|---|---|
INVALID_FORMAT |
Fewer than 4 or more than 20 characters after cleanup | Code must be 4-20 characters |
CODE_NOT_FOUND |
No active creator code in an active campaign, and no free-access code, matches | Code not found or inactive |
CODE_EXPIRED |
A free-access code that expired or has no seats left | Code expired or usage limit reached |
NETWORK_ERROR |
Set by the SDK: the request failed, or the SDK isn't initialized | Network error or invalid response or SDK not initialized |
/sdk/set-referral-code can also answer MISSING_USER_ID for a free-access code sent without a user ID. The SDKs' SetCodeResult doesn't expose the code; you get success: false and the message "Could not identify your account. Please try again."
HTTP status codes for /api/sdk/*
| Status | Meaning | Endpoints |
|---|---|---|
200 |
Success. "Not attributed", "invalid code" and "duplicate" are all 200 answers — read the body. |
All |
400 |
Empty app_user_id; or, on /sdk/purchase, the platform isn't store-direct, credentials are missing, the proof is missing or failed verification, or the platform isn't ios/android |
identify, access, recent-conversions, purchase |
401 |
Missing, malformed, unknown or deactivated SDK key | All |
404 |
Unknown or inactive code | set-referral-code |
409 |
Android one-time purchase not in the PURCHASED state | purchase |
413 |
Body larger than 20 MB | All |
422 |
A required field or query parameter is missing or has the wrong type | All |
429 |
Rate limit | All |
500 |
Unexpected server error | All |
503 |
Exchange rate unavailable — retry later | purchase |
The store-direct 400 is intentional: until you switch the platform to store-direct, and that switch needs a green credential check, the endpoint rejects reports. So it's safe to ship the SDK first.
RevenueCat webhook responses
| Status | Meaning |
|---|---|
200 |
Accepted. If processing failed, the body also has "error"; the event isn't retried by RevenueCat or by InfluTo. |
400 |
The body isn't valid JSON |
401 |
Missing or wrong Authorization value. The body says how to fix it, and the Setup page shows a warning. |
404 |
No app with the ID in the URL |
A low wallet balance is not a webhook error: the conversion is still recorded. See RevenueCat and inbound webhooks.
Agent API and MCP
The agent REST API (/api/agent/*) and the MCP tools answer with these statuses; an MCP tool error's text starts with the status.
| Status | Meaning |
|---|---|
400 |
Invalid input, for example a batch of more than 500 invites |
401 |
Missing, invalid, expired or revoked agent key or token |
402 |
Not enough unreserved wallet balance: activating a campaign and sending email invites need at least $50; marketplace invites need $20 per open invite |
403 |
The key or token lacks the scope, or the action is closed to agents (money, payouts, credentials, admin) |
404 |
Not found — also returned for anything outside the organizations you granted, so nothing leaks |
429 |
More than 240 MCP tool calls in a minute, or an invite limit: 100 email invites per organization per 24 hours, or one open marketplace invite per $20 of unreserved balance |
Details: MCP tools, scopes and REST API.
Code format
Creator referral codes (the code in influ.to/{app_slug}/{CODE}):
- 4–20 characters:
A–Z,0–9,_and-. Stored uppercase. - Not one of
ADMIN,TEST,API,SYSTEM,NULL,ROOT,DELETE,REMOVE. - Can't start with
INFLU-, which is reserved for creator access codes InfluTo issues itself.
Free-access codes you create on Free Access Codes: the same characters and length, uppercase, unique across InfluTo, and also never starting with INFLU-.
When a user types a code, the SDK trims and uppercases it. The server then ignores spaces and any other character outside the alphabet when it looks the code up, and checks creator codes before free-access codes. The SDK stores and sends to RevenueCat the code it was given, so clean the input in your field — see building your own field.
App slug
The slug is the first part of your referral links, influ.to/{app_slug}/{CODE}.
- 3–20 characters: lowercase letters, digits and
-(the dashboard enforces this as you type). - Unique across InfluTo, and not one of the names the site itself uses (such as
api,dashboardor a language code). My Apps → Add App checks availability as you type.