HTTP API
The SDKs make two small JSON requests. If no SDK fits your stack, you can make them yourself. This page is the full contract.
Prefer an SDK where you can. Writing your own client means you own the hard parts: getting Apple's token on the first launch, sending it within its 24 hours, keeping it through crashes and network loss, and retrying without counting an install twice.
Basics
| Base URL | https://ingest.fload.com |
| Auth | Your write key in the X-Fload-Write-Key header |
| Body | JSON, with Content-Type: application/json |
| Calls from | Your app or your own server. Browsers aren't supported: the host answers no CORS requests |
Report an install
POST /attribution/v1/installs, once per install, on its first launch.
iOS
Get Apple's token with AAAttribution.attributionToken() on the phone, then send it:
curl -X POST https://ingest.fload.com/attribution/v1/installs \
-H "Content-Type: application/json" \
-H "X-Fload-Write-Key: fload_pk_…" \
-d '{
"requestId": "b7f3c2a8-5d1e-4c6b-9a0f-2e8d4b1c7a65",
"installReference": "4e9a1f60-2c7b-4d3e-8f5a-91b0c6d2e7f8",
"platform": "ios",
"appId": "com.northwind.pocketbudget",
"appVersion": "2.3.0",
"appBuild": "231",
"osVersion": "18.1",
"deviceModel": "iPhone16,2",
"locale": "en-GB",
"sdk": { "name": "pocketbudget-custom", "version": "1.0.0" },
"firstLaunchAt": "2026-11-06T09:12:44Z",
"appleAds": {
"status": "token",
"token": "<the base64 token from attributionToken()>",
"tokenCreatedAt": "2026-11-06T09:12:45Z"
}
}'Fload swaps the token with Apple on its server, so your app never calls Apple's attribution API.
Android
Read the Play Install Referrer with Google's Install Referrer library, then send what it returns. Convert Google's seconds to ISO 8601 times:
curl -X POST https://ingest.fload.com/attribution/v1/installs \
-H "Content-Type: application/json" \
-H "X-Fload-Write-Key: fload_pk_…" \
-d '{
"requestId": "c1d8e4f2-7a3b-4e9c-b5d0-6f2a8c1e3b47",
"installReference": "9b2e7c41-5f8a-4d6e-a3c1-0d7f2b9e4a58",
"platform": "android",
"appId": "com.northwind.pocketbudget",
"appVersion": "2.3.0",
"appBuild": "231",
"osVersion": "15",
"deviceModel": "Google Pixel 8",
"locale": "en-GB",
"sdk": { "name": "pocketbudget-custom", "version": "1.0.0" },
"firstLaunchAt": "2026-11-06T09:12:44Z",
"playReferrer": {
"status": "ok",
"referrer": "utm_source=google-play&utm_medium=organic",
"referrerClickAt": "2026-11-06T09:10:02Z",
"installBeginAt": "2026-11-06T09:11:30Z",
"googlePlayInstant": false
}
}'Install fields
| Field | Required | What it is |
|---|---|---|
requestId | Yes | An id you make for this request. Send the same one on every retry, so Fload counts the install once |
installReference | Yes | A random id you make once per install and keep. 8 to 128 characters: letters, digits and . _ : - |
platform | Yes | ios or android |
appId | Yes | Bundle id or package name |
sdk | Yes | name and version of your client, up to 64 characters each |
firstLaunchAt | Yes | The phone's time of the first launch, ISO 8601 |
appleAds | iOS only | See below. iOS sends this and never playReferrer |
playReferrer | Android only | See below. Android sends this and never appleAds |
appVersion, appBuild, osVersion, deviceModel, locale | No | Up to 64 characters each |
appUserId | No | Your app user id, if you have it before the first send. Up to 256 characters |
appleAds
| Field | What it is |
|---|---|
status | token when you have one. token_error when Apple's call failed, unsupported_os below iOS 14.3, token_expired when the token is over 24 hours old and you're sending the install without it |
token | Apple's base64 token. Send it only with status: "token", and always with it |
tokenCreatedAt | When the phone got the token, ISO 8601 |
playReferrer
| Field | What it is |
|---|---|
status | ok, unavailable (Play's service didn't answer, or the app isn't from Play), not_supported (no Play Store) or error |
referrer | The raw referrer string, as Google returns it |
referrerClickAt, installBeginAt | Google's click and install-begin times |
referrerClickServerAt, installBeginServerAt | The same times from Google's server, when present |
installVersion | The app version first installed, when present |
googlePlayInstant | Whether the user tried the instant app |
Link a user
POST /attribution/v1/identify, whenever your app user id becomes known or changes, for example after login:
curl -X POST https://ingest.fload.com/attribution/v1/identify \
-H "Content-Type: application/json" \
-H "X-Fload-Write-Key: fload_pk_…" \
-d '{
"requestId": "e5a9b3c7-1d4f-4a8e-9c2b-7f0d3e6a1b84",
"installReference": "4e9a1f60-2c7b-4d3e-8f5a-91b0c6d2e7f8",
"platform": "ios",
"appUserId": "user_123"
}'Pass RevenueCat's app user id, so Fload can match the install to revenue. Fload keeps every id an install has had. An identify that arrives before its install is kept and linked when the install arrives.
Responses
| Status | Meaning | What to do |
|---|---|---|
202 | Accepted. Also returned for an exact repeat | Delete the request from your queue |
400 | The body doesn't match the contract | Don't retry. Fix the request |
401 | Fload doesn't know this key | Keep the request. Check the key, and retry later |
410 | The key was revoked | Stop sending with this key for good |
413 | The body is too large | Don't retry |
429 | Too many requests | Retry after the seconds in Retry-After |
5xx | Fload had a problem | Retry with backoff |
A 202 body is { "accepted": true }. Fload ignores fields it doesn't know, so adding your own never breaks a request.
Retries
- Back off exponentially with jitter. The SDKs start at 2 seconds and double up to 6 hours.
- Repeat the same
requestIdon every retry. - Keep the request on disk until you get a
202, so a crash or a closed app doesn't lose it. - Apple's token lives 24 hours. If you can't send it in time, send the install with
status: "token_expired"and no token. Fload still counts it.