Skip to content

Conversions ​

Methods for the conversion log - everything that came in via postbacks from affiliate networks and from the traffic itself (installs, offer opens). Every event is visible here as a separate row: with the payout, the visitor and the status of sending to Facebook. For what the columns and statuses mean in the panel, see the "Conversions" section.

MethodAddressWhat it does
GET/conversionsList conversions for a period
GET/conversions/{id}One conversion and all its visitor's visits
POST/conversions/{id}/re-exportSend a conversion to Facebook again

Replace YOUR_KEY and panel.example.com with your own.

List conversions ​

GET/conversions

Conversions of your PWAs for a period, newest first.

ParameterRequiredWhat it filters
date_from, date_toyesPeriod by the event's received date: YYYY-MM-DD, whole days in UTC, no longer than a month - as in the "Statistics" section
event[]noEvent type, several allowed: install, open_offer, registration, deposit, repeat_deposit, page_view
export_statusnoStatus of sending to Facebook: exported, failed, not_exported or open_offer
pwa_id or pwa_ids[]noOne or several PWAs
split_id or split_ids[]noOne or several splits - conversions from their flows
visitor_idnoPart of the visitor identifier
page, per_pagenoPage and its size: 100 by default, 200 maximum
bash
curl -G "https://panel.example.com/api/conversions" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json" \
  --data-urlencode "date_from=2026-10-01" \
  --data-urlencode "date_to=2026-10-05" \
  --data-urlencode "event[]=deposit" \
  --data-urlencode "event[]=repeat_deposit"
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
    'timeout' => 30,
]);

$page = 1;
do {
    $response = $api->get('conversions', ['query' => [
        'date_from' => '2026-10-01',
        'date_to' => '2026-10-05',
        'event' => ['deposit', 'repeat_deposit'],
        'per_page' => 200,
        'page' => $page,
    ]]);
    $data = json_decode($response->getBody(), true)['data'];

    foreach ($data['conversions'] as $conversion) {
        echo "{$conversion['received_at']} {$conversion['pwa']['name']}: {$conversion['payout']} {$conversion['currency']}\n";
    }
    $page++;
} while ($data['meta']['current_page'] < $data['meta']['last_page']);
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

page = 1
while True:
    response = requests.get(f"{API}/conversions", headers=HEADERS, params={
        "date_from": "2026-10-01",
        "date_to": "2026-10-05",
        "event[]": ["deposit", "repeat_deposit"],
        "per_page": 200,
        "page": page,
    }, timeout=30)
    data = response.json()["data"]

    for conversion in data["conversions"]:
        print(conversion["received_at"], conversion["pwa"]["name"], conversion["payout"], conversion["currency"])

    if data["meta"]["current_page"] >= data["meta"]["last_page"]:
        break
    page += 1
js
// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };

for (let page = 1; ; page++) {
  const params = new URLSearchParams({
    date_from: '2026-10-01',
    date_to: '2026-10-05',
    per_page: 200,
    page,
  });
  params.append('event[]', 'deposit');
  params.append('event[]', 'repeat_deposit');

  const response = await fetch(`${API}/conversions?${params}`, {
    method: 'GET',
    headers,
    signal: AbortSignal.timeout(30000),
  });
  const { data } = await response.json();

  for (const conversion of data.conversions) {
    console.log(conversion.received_at, conversion.pwa.name, conversion.payout, conversion.currency);
  }

  if (data.meta.current_page >= data.meta.last_page) break;
}
Response
json
{
  "data": {
    "meta": { "current_page": 1, "per_page": 200, "last_page": 1, "total": 1 },
    "conversions": [
      {
        "id": "Rt5K",
        "pwa": { "id": "WZAX", "name": "Promo EN", "icon_url": null },
        "pwa_visitor_id": "7f3c2a9e-1b4d-4c8e-9a2f-5d6e7f8a9b0c",
        "event": "deposit",
        "raw_event": "ftd",
        "payout": "45.00",
        "currency": "USD",
        "tid": "tx-55102",
        "external_click_id": null,
        "export_status": "failed",
        "export_error": "[400] Malformed access token",
        "exported_at": null,
        "can_reexport": true,
        "status": "scheduled",
        "received_at": "05 Oct 2026 14:23",
        "created_at": "05 Oct 2026 14:23",
        "updated_at": "05 Oct 2026 14:23"
      }
    ]
  }
}
FieldWhat it is
pwaThe PWA the conversion is credited to; null if the PWA couldn't be determined (only admin sees such rows)
pwa_visitor_idThe visitor through whom the conversion was linked to a visit
eventEvent type (the list is in the parameters table above). null - the network sent a status the panel didn't recognise: such an event isn't counted in statistics and isn't sent to Facebook
raw_eventThe status exactly as the network sent it (ftd, sale, reg…)
payout, currencyPayout from the postback - as a string, as it came
tidTransaction identifier from the postback
external_click_idclick_id from the postback, if the network passed it
export_statusSending to Facebook - see below
export_errorFacebook's answer if sending failed
exported_atWhen the event went to Facebook
can_reexportWhether it can be sent again: not for open_offer and events with event: null
received_atWhen the event arrived in the panel - the period filters by it
statusA service field, irrelevant for scripts

export_status:

  • exported - Facebook accepted the event.
  • not_exported - the event hasn't been sent yet. Sending runs once a minute, but the event stays not_exported if the PWA has no pixel with a token, the event has no Facebook event assigned, event is null or the event is older than 3 days.
  • failed - Facebook refused, the reason is in export_error. If exported_at is empty, the error is temporary (Facebook unavailable, a limit) - the panel retries on its own. If exported_at is set, the refusal is final, a re-export is needed after fixing the cause.
  • open_offer - a service event, it is never sent to Facebook.

Total for a period

To calculate revenue, add up payout for each currency separately - currencies don't combine into one number. Ready totals by currency for a period are in the "Statistics" section (total_deposits.amounts).

The export_status=open_offer filter shows only offer opens, the event[] parameter is ignored with it.

Errors
CodemessageWhen
422The date from field is required. (and 1 more error)No period passed; the other period errors are as in the "Statistics" section
422The selected event.0 is invalid.Unknown event type in event[]
422The selected export status is invalid.Unknown export_status
422The per page field must not be greater than 200.per_page is greater than 200
422Invalid pwa_id / Invalid split_idThe PWA or split identifier isn't recognised
404Not foundA PWA or split from the filter belongs to someone else or doesn't exist

One conversion ​

GET/conversions/{id}

A conversion and up to 100 latest visits of its visitor: which domain and IP they came from, with which tags, and which visit brought the conversion. Handy when a network disputes a conversion or you need to find the creative that brought it.

bash
curl https://panel.example.com/api/conversions/Rt5K \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
    'timeout' => 30,
]);

$details = json_decode($api->get('conversions/Rt5K')->getBody(), true)['data'];

foreach ($details['visits'] as $visit) {
    if ($visit['matched']) {
        echo "The conversion came from the visit at {$visit['created_at']}, sub_1 = {$visit['subs']['sub_1']}\n";
    }
}
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

details = requests.get(f"{API}/conversions/Rt5K", headers=HEADERS, timeout=30).json()["data"]

for visit in details["visits"]:
    if visit["matched"]:
        print("The conversion came from the visit at", visit["created_at"], "sub_1 =", (visit["subs"] or {}).get("sub_1"))
js
// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };

const response = await fetch(`${API}/conversions/Rt5K`, {
  method: 'GET',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

for (const visit of data.visits) {
  if (visit.matched) {
    console.log('The conversion came from the visit at', visit.created_at, 'sub_1 =', visit.subs?.sub_1);
  }
}
Response
json
{
  "data": {
    "conversion": {
      "id": "Rt5K",
      "pwa": { "id": "WZAX", "name": "Promo EN", "icon_url": null },
      "event": "deposit",
      "payout": "45.00",
      "currency": "USD",
      "export_status": "failed",
      "received_at": "05 Oct 2026 14:23"
    },
    "visits_count": 1,
    "visits": [
      {
        "created_at": "05 Oct 2026 13:20",
        "ip_address": "203.0.113.7",
        "from_domain": "fit-tracker.example",
        "country": "US",
        "user_agent": "Mozilla/5.0 (Linux; Android 14)",
        "offer_visitor_id": "aff-91822",
        "offer_redirect_url": "https://offer.example/?sub1=fb-ua-creo1",
        "subs": { "sub_1": "fb-ua-creo1", "sub_2": "adset-7" },
        "third_party_tokens": null,
        "is_unique": true,
        "matched": true
      }
    ]
  }
}

conversion is the same record as in the list (shortened here). visits_count - how many visits the visitor has in total; visits holds no more than the 100 latest, newest first.

Visit fieldWhat it is
from_domainThe domain the visitor came to
offer_visitor_id, offer_redirect_urlThe visitor's identifier at the offer and the link they were sent to
subsFilled tags sub_1 … sub_20; null if there were no tags
third_party_tokensOther parameters from the link: fbclid, gclid and so on
is_uniqueThis visitor's first visit
matchedThe visit that brought the conversion: the last visit to the same flow before the event arrived

If no visit is marked matched, the linked visit wasn't found - most often because of a lost pwa_visitor_id in the postback.

Errors
CodemessageWhen
404Not foundThe conversion belongs to someone else or doesn't exist

Re-export ​

POST/conversions/{id}/re-export

Clears the sent mark and the error, and the next background sending (once a minute) tries to pass the event to Facebook again.

Useful when Facebook refused because of a setting you've already fixed: for example, you updated the pixel token. Fix the cause first - otherwise the retry gets the same error.

bash
curl -X POST https://panel.example.com/api/conversions/Rt5K/re-export \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Accept: application/json"
php
<?php
// composer require guzzlehttp/guzzle
require 'vendor/autoload.php';

$api = new GuzzleHttp\Client([
    'base_uri' => 'https://panel.example.com/api/',
    'headers' => ['Authorization' => 'Bearer YOUR_KEY', 'Accept' => 'application/json'],
    'timeout' => 30,
]);

$response = $api->post('conversions/Rt5K/re-export');
$conversion = json_decode($response->getBody(), true)['data']['conversion'];

echo "{$conversion['id']}: {$conversion['export_status']}" . PHP_EOL;
python
# pip install requests
import requests

API = "https://panel.example.com/api"
HEADERS = {"Authorization": "Bearer YOUR_KEY", "Accept": "application/json"}

response = requests.post(f"{API}/conversions/Rt5K/re-export", headers=HEADERS, timeout=30)
conversion = response.json()["data"]["conversion"]

print(conversion["id"], conversion["export_status"])
js
// Node.js 18+, no dependencies; save as script.mjs
const API = 'https://panel.example.com/api';
const headers = { Authorization: 'Bearer YOUR_KEY', Accept: 'application/json', 'Content-Type': 'application/json' };

const response = await fetch(`${API}/conversions/Rt5K/re-export`, {
  method: 'POST',
  headers,
  signal: AbortSignal.timeout(30000),
});
const { data } = await response.json();

console.log(data.conversion.id, data.conversion.export_status);

The response is the conversion after the reset, its export_status is now not_exported:

Response
json
{
  "data": {
    "conversion": {
      "id": "Rt5K",
      "event": "deposit",
      "export_status": "not_exported",
      "export_error": null,
      "exported_at": null,
      "can_reexport": true
    }
  }
}
Errors
CodemessageWhen
422Event cannot be re-exportedcan_reexport: false - an offer open or an event with event: null
404Not foundThe conversion belongs to someone else or doesn't exist

Only fresh conversions

Background sending takes events no older than 3 days since they were received (received_at). Re-exporting an older conversion resets its status, but it won't go to Facebook anymore.

To resend all errors at once, take the list with export_status=failed and call re-export for each row - no faster than the request limit from "API introduction".