Appearance
S2S impression API
Report affiliate ad impressions server-to-server for view-through attribution
Affiliate partners use this endpoint to report an ad impression from their server, so Linkrunner can credit a view-through install. For the setup guide and the attribution rules, see Server-to-Server Impressions.
Endpoint
GET https://s2s.linkrunner.io/v1/impression/{app_id}
GET https://s2s.linkrunner.io/v1/impression?app_id={app_id}
GET https://s2s.linkrunner.io/v1/impression?d={domain}The advertiser is identified by the app ID (or the tracking link's domain) and the campaign by c. The campaign must belong to your partner account, and the advertiser must have turned on S2S traffic and view-through for you.
Authentication
Every request must carry your partner token. Send it in a header:
X-Linkrunner-Token: <your_token>If your ad server can only fire URLs, send it as the lr_token query parameter instead: &lr_token=<your_token>. Linkrunner removes lr_token before it stores the impression, so it never appears in reports or postbacks.
Generate the token in your affiliate dashboard under Settings → S2S Credentials. It is shown once, so store it as a secret. One token covers every app you're connected to, for both S2S clicks and impressions. A request without a valid token is rejected with 401. For rotation and revocation, see Authenticate your requests.
Parameters
Send all parameters as URL-encoded query parameters. Each value is limited to 256 characters.
| Parameter | Alias | Required | Description |
|---|---|---|---|
app_id | path | One of app_id or d | The app's Android package name (com.example.app) or App Store ID (id1234567890 or 1234567890). Can also be sent in the path: /v1/impression/{app_id}. |
d | One of app_id or d | Domain of your Linkrunner tracking link, for example app.example.com. Wins over app_id when both are sent. | |
c | Yes | Campaign code, the c value of your tracking link. Must match a campaign of that app or domain exactly. | |
lr_token | Only without the header | Your partner token, for ad servers that can't set the X-Linkrunner-Token header. Removed before the request is stored. | |
tid | clickid | Yes | Your unique impression ID. Returned in the {click_id} and {tid} postback macros. |
gaid | advertising_id | One of gaid or idfa | Google Advertising ID, as a UUID. |
idfa | One of gaid or idfa | Apple IDFA, as a UUID. | |
impression_time | No | Unix timestamp of the impression, in milliseconds or seconds. Defaults to the time of the request. | |
vt_lookback | af_viewthrough_lookback | No | Your view-through window for this impression: 1h to 24h, a bare number of hours (6), or 1d. Longer values are capped at 24 hours. It can only shorten the window the advertiser set for you. |
ip | af_ip | No | The user's device IP. Stored for reporting, never used for matching. Only public addresses are kept. |
ua | af_ua | No | The user's device user agent. Stored for reporting. |
s2 | af_sub1 | No | Passthrough value, returned in {s2}. |
s3 | af_sub2 | No | Passthrough value, returned in {s3}. |
s4 | af_sub3 | No | Passthrough value, returned in {s4}. |
Every impression needs a gaid or an idfa. View-through attribution matches on device ID only, so unlike S2S clicks, an IP address alone is rejected.
If both a name and its alias are sent, the name wins. click_time is ignored on this endpoint; use impression_time. pid, af_siteid and af_lang are ignored, so an AppsFlyer impression template works once the host and c are changed and your token is added.
Responses
The response is always JSON.
json
{
"status": "accepted",
"impression_id": "dfa5b5ed-54c2-5f5b-bc48-2385c336fed8"
}json
{
"status": "rejected",
"reason": "missing_device_id"
}json
{
"status": "rejected",
"reason": "invalid_token"
}impression_id is Linkrunner's ID for the impression. It is the same for every retry of the same tid on the same campaign.
Status codes
| Status | Reason | Meaning | Retry |
|---|---|---|---|
| 200 | Impression recorded. | ||
| 400 | missing_app | Neither app_id nor d was sent. | No |
| 400 | missing_c, missing_tid | A required parameter is missing or empty. | No |
| 400 | missing_device_id | No gaid and no idfa. An all-zero ID counts as missing. | No |
| 400 | invalid_device_id | gaid or idfa is not a UUID, for example null or unknown. | No |
| 400 | unreplaced_macro | A value still contains a macro, such as {gaid} or %7Bgaid%7D. | No |
| 400 | value_too_long | A value is longer than 256 characters. | No |
| 400 | invalid_impression_time | impression_time is not a Unix timestamp. | No |
| 400 | impression_too_old | impression_time is more than 24 hours ago. A time in the future is treated as now. | No |
| 400 | invalid_viewthrough_lookback | vt_lookback is 0, negative, fractional, or not in hours or days. | No |
| 400 | unknown_app | No Linkrunner project has this app ID. | No |
| 400 | ambiguous_app | More than one of the advertiser's projects with S2S on for you has this app ID and campaign code (for example test and live). Send d instead. | No |
| 400 | unknown_domain | d is not a Linkrunner click domain. | No |
| 400 | unknown_campaign | c does not match a campaign of that app or domain. | No |
| 401 | missing_token | No X-Linkrunner-Token header and no lr_token parameter. | No |
| 401 | invalid_token | The token is wrong, revoked, or was rotated more than 24 hours ago, or your partner account is suspended. | No |
| 403 | partner_not_enabled | The advertiser hasn't turned on S2S traffic for the partner that owns campaign c. | No |
| 403 | view_through_disabled | S2S traffic is on, but the advertiser hasn't turned on view-through for this partner. | No |
| 404 | s2s_disabled | The S2S impression API is turned off. | No |
| 429 | rate_limited | Too many impressions for this campaign. | Yes, after Retry-After |
| 503 | unavailable | A temporary error. | Yes, after Retry-After |
Examples
The examples read your token from the LINKRUNNER_S2S_TOKEN environment variable.
bash
curl -G "https://s2s.linkrunner.io/v1/impression/com.example.app" \
-H "X-Linkrunner-Token: $LINKRUNNER_S2S_TOKEN" \
--data-urlencode "c=OgWmhiSXhG" \
--data-urlencode "tid=imp-7f3a91" \
--data-urlencode "gaid=38400000-8cf0-11bd-b23e-10b96e40000d" \
--data-urlencode "impression_time=1790585271181" \
--data-urlencode "vt_lookback=6h" \
--data-urlencode "s2=banner_top"javascript
const params = new URLSearchParams({
c: "OgWmhiSXhG",
tid: impressionId,
gaid: advertisingId,
impression_time: String(Date.now()),
vt_lookback: "6h",
s2: "banner_top",
});
const res = await fetch(`https://s2s.linkrunner.io/v1/impression/com.example.app?${params}`, {
headers: { "X-Linkrunner-Token": process.env.LINKRUNNER_S2S_TOKEN },
});
const body = await res.json(); // { status: "accepted", impression_id: "..." }python
import os, time, requests
res = requests.get("https://s2s.linkrunner.io/v1/impression/com.example.app", params={
"c": "OgWmhiSXhG",
"tid": impression_id,
"gaid": advertising_id,
"impression_time": int(time.time() * 1000),
"vt_lookback": "6h",
"s2": "banner_top",
}, headers={"X-Linkrunner-Token": os.environ["LINKRUNNER_S2S_TOKEN"]}, timeout=5)
body = res.json() # {"status": "accepted", "impression_id": "..."}An iOS impression, with the advertiser's default window:
http
GET https://s2s.linkrunner.io/v1/impression/id1234567890?c=OgWmhiSXhG&tid=imp-7f3a92&idfa=6D92078A-8246-4BA4-AE5B-76104861E7DC
X-Linkrunner-Token: <your_token>Retries and deduplication
The same tid on the same campaign always maps to the same impression_id, and the impression is stored once, so it is safe to retry. Retry only 429 and 503, after the delay in the Retry-After header. Keep tid unique per impression: a reused tid is treated as a retry of the earlier impression, so the new one is not recorded.