Skip to main content

User Guide to pull stats and raw data using the GraphQL API

Pull your clicks, conversions and aggregated statistics from Swaarm programmatically through the GraphQL API.

This guide covers three reports:

You want…

GraphQL query

Where it lives in the Swaarm UI

Every individual click, one row per click

dataReport (type Click)

Click Report

Every individual conversion (postback), one row each

conversionReport

Conversion Report

Aggregated numbers grouped by any dimensions you pick

rawReport

Custom Report

1. Before you start

You need:

  • Your API endpoint. Every Swaarm platform has its own:

    https://api.<your-platform>.swaarm-clients.com/graphql

    <your-platform> is the same name you see in your Swaarm dashboard address. If you are unsure, ask your account manager.

  • A Swaarm user account with access to reports. We recommend creating a dedicated user for API access (for example api@yourcompany.com) so integrations keep working when team members change.

  • A GraphQL or HTTP client. Any language works. The examples below use curl and jq, plus a Python script in section 9.

Tip — explore interactively. The full schema can be explored with any GraphQL IDE (Altair, Insomnia, Postman). Point it at your endpoint, add the Authorization header from section 2, and use introspection to browse every field and enum value mentioned in this guide.

2. Authentication

The API uses bearer tokens. You log in once, receive an access token, and send it with every request.

2.1 Log in

mutation Login($input: AuthLoginInput!) {   login(input: $input) {     authChallenge     token {       accessToken       refreshToken       expiresIn     }   } }

Variables:

{ "input": { "username": "api@yourcompany.com", "password": "your-password" } }

With curl:

API=https://api.<your-platform>.swaarm-clients.com/graphql  curl -s "$API" \   -H 'Content-Type: application/json' \   -d '{     "query": "mutation($input: AuthLoginInput!){ login(input:$input){ authChallenge token { accessToken refreshToken expiresIn } } }",     "variables": { "input": { "username": "api@yourcompany.com", "password": "your-password" } }   }'

What you get back:

Field

Meaning

token.accessToken

Send this on every request (see 2.2).

token.refreshToken

Use it to get a new access token without sending the password again.

token.expiresIn

Access-token lifetime in seconds.

authChallenge

NONE means you are logged in. NEW_PASSWORD_REQUIRED means this user must set a password first: log in once through the Swaarm dashboard, then retry.

2.2 Send the token

Add this header to every request:

Authorization: Bearer <accessToken>

2.3 Refresh the token

When the access token expires, requests are rejected. Get a new one with the refresh token:

mutation Refresh($input: AuthRefreshTokenInput!) {   refreshToken(input: $input) {     token { accessToken refreshToken expiresIn }   } }

Variables: { "input": { "refreshToken": "<refreshToken>" } }

Always store the new refreshToken from the response; the old one may stop working.

3. Making requests

All reports go through the same endpoint with a POST:

POST https://api.<your-platform>.swaarm-clients.com/graphql Content-Type: application/json Authorization: Bearer <accessToken>  { "query": "...", "variables": { ... } }

The response is always JSON:

{ "data": { ... }, "errors": [ ... ] }

Check for an errors array even when the HTTP status is 200.

Two report styles

The three reports return data in two different shapes. Knowing this up front makes the rest of the guide easier.

Row-level reports (Click Report, Conversion Report) return one row per event. Each row is a list of records, and each record is a field + value pair. The value can be a string, number, boolean, date or list, so you ask for it with inline fragments:

records {   field   value {     ... on StringValue      { s: value }     ... on IntValue         { i: value }     ... on FloatValue       { f: value }     ... on BooleanValue     { b: value }     ... on DateValue        { d: value }     ... on StringArrayValue { a: value }   } }

Only one of s, i, f, b, d, a is present per record. Empty values come back as null.

The aggregated report (Custom Report) returns rows of dimensions and metrics, each a list of { name, value } pairs where value is always a string.

4. Click Report — individual clicks

Use this to get raw click logs: click ID, time, offer, partner, sub IDs, device, geo, IP and so on.

4.1 Discover the available columns

The Click Report's columns can differ per platform, so ask the API for the list:

query {   dataReportFields(type: "Click") {     name      # the value you pass in `fields`, `filters` and `sort`     label     # the column title shown in the dashboard     type      # TEXT | NUMBER | BOOLEAN | DATE     defaultable   } }

Commonly used columns:

Field name

Column in the dashboard

ID

Click ID

TIME

Click Time

MESSAGE_OFFER_ID

Offer ID

OFFER_NAME

Offer Name

ADVERTISER_NAME

Advertiser Name

MESSAGE_PUBLISHER_ID

Publisher ID

PUBLISHER_NAME

Publisher Name

MESSAGE_PUBLISHER_SUB_ID

Publisher Sub ID

MESSAGE_PUBLISHER_SUB_SUB_ID

Publisher Sub Sub ID

MESSAGE_PUBLISHER_CLICK_ID

Publisher Click ID

MESSAGE_PUBLISHER_UNIQUE1 … 9

Publisher Unique 1–9

MESSAGE_USER_GEO_COUNTRY

User Geo Country

MESSAGE_USER_CONNECTION_IP

User Connection IP

MESSAGE_USER_DEVICE_OS

User Device OS

MESSAGE_USER_DEVICE_MODEL

User Device Model

MESSAGE_USER_IDS_GAID / _IDFA

Device advertising IDs

MESSAGE_REDIRECT_URL

Redirect URL

4.2 Query clicks

query Clicks(   $start: LocalDateTime   $end: LocalDateTime   $fields: [String!]   $filters: [DataReportFilter!]   $first: Int   $offset: Int ) {   dataReport(     type: "Click"     start: $start     end: $end     fields: $fields     filters: $filters     first: $first     offset: $offset     sort: { field: "TIME", order: DESC }   ) {     totalCount     csvDownloadUrl     edges {       node {         records {           field           value {             ... on StringValue      { s: value }             ... on IntValue         { i: value }             ... on FloatValue       { f: value }             ... on BooleanValue     { b: value }             ... on DateValue        { d: value }             ... on StringArrayValue { a: value }           }         }       }     }   } }

Variables — all clicks for offer 1234 from partner 56 on 1 October, 100 at a time:

{   "start": "2026-10-01T00:00:00",   "end":   "2026-10-01T23:59:59",   "fields": ["ID", "TIME", "MESSAGE_OFFER_ID", "MESSAGE_PUBLISHER_ID",              "MESSAGE_PUBLISHER_SUB_ID", "MESSAGE_USER_GEO_COUNTRY",              "MESSAGE_USER_CONNECTION_IP", "MESSAGE_USER_DEVICE_OS"],   "filters": [     { "field": "MESSAGE_OFFER_ID",     "operator": "IN", "values": ["1234"] },     { "field": "MESSAGE_PUBLISHER_ID", "operator": "IN", "values": ["56"] }   ],   "first": 100,   "offset": 0 }

4.3 Parameters

Parameter

Required

Notes

type

yes

"Click"

start, end

no

Time range. If omitted, the last 7 days are used.

fields

no

Columns to return. Use names from dataReportFields.

filters

no

List of { field, operator, values }. See operators.

first

no

Page size (default 50).

offset

no

Rows to skip, for paging.

sort

no

{ field: "<FIELD>", order: ASC | DESC }. Default: TIME descending.

csvFields

no

Columns for the CSV download, if different from fields.

4.4 Example response

{   "data": {     "dataReport": {       "totalCount": 18342,       "csvDownloadUrl": "/download/data-report/Click/eyJhbGciOi...",       "edges": [         {           "node": {             "records": [               { "field": "ID",                       "value": { "s": "a1b2c3d4-..." } },               { "field": "TIME",                     "value": { "d": "2026-10-01T23:59:41" } },               { "field": "MESSAGE_OFFER_ID",         "value": { "s": "1234" } },               { "field": "MESSAGE_PUBLISHER_ID",     "value": { "s": "56" } },               { "field": "MESSAGE_PUBLISHER_SUB_ID", "value": { "s": "campaign_a" } },               { "field": "MESSAGE_USER_GEO_COUNTRY", "value": { "s": "DE" } }             ]           }         }       ]     }   } }

totalCount is the number of matching clicks across all pages.

5. Conversion Report — individual conversions

Use this to get every conversion (install, registration, purchase, …) with its status, payout, the click it was attributed to, and the advertiser's postback data.

5.1 Query conversions

query Conversions(   $start: LocalDateTime!   $end: LocalDateTime!   $fields: [ConversionReportField!]   $filters: [ConversionReportFilter!]   $first: Int   $offset: Int ) {   conversionReport(     start: $start     end: $end     fields: $fields     filters: $filters     first: $first     offset: $offset     sort: { field: POSTBACK_TIME, order: DESC }   ) {     totalCount     csvDownloadUrl     edges {       node {         records {           field           value {             ... on StringValue      { s: value }             ... on IntValue         { i: value }             ... on FloatValue       { f: value }             ... on BooleanValue     { b: value }             ... on DateValue        { d: value }             ... on StringArrayValue { a: value }           }         }       }     }   } }

Variables — approved conversions for offer 1234 in October:

{   "start": "2026-10-01T00:00:00",   "end":   "2026-10-31T23:59:59",   "fields": [     "POSTBACK_ID", "POSTBACK_TIME", "CLICK_ID", "CLICK_TIME",     "OFFER_ID", "OFFER_NAME", "OFFER_EVENT_NAME",     "PUBLISHER_ID", "PUBLISHER_NAME", "PUBLISHER_SUB_ID",     "EVALUATION_STATUS", "OFFER_WE_GET", "OFFER_THEY_GET",     "CLICK_USER_GEO_COUNTRY"   ],   "filters": [     { "field": "OFFER_ID",          "operator": "IN", "values": ["1234"] },     { "field": "EVALUATION_STATUS", "operator": "IN", "values": ["APPROVED"] }   ],   "first": 500,   "offset": 0 }

5.2 Parameters

Parameter

Required

Notes

start, end

yes

Conversion time range (POSTBACK_TIME).

fields

no

Columns to return (ConversionReportField enum).

filters

no

List of { field, operator, values }. See operators.

first, offset

no

Paging.

sort

no

{ field: <ConversionReportField>, order: ASC | DESC }

csvFields

no

Columns for the CSV download, if different from fields.

appIds, storeAppIds

no

Shortcuts to filter by app.

5.3 Most useful fields

Conversion

Field

Meaning

POSTBACK_ID

Unique conversion ID

POSTBACK_TIME

When the conversion happened

OFFER_EVENT_ID, OFFER_EVENT_NAME

Which event (install, purchase, …)

EVALUATION_STATUS

Status, e.g. APPROVED, PENDING, REJECTED

EVALUATION_STATUS_FAILED_RULES

Rules that changed the status

EVALUATION_DECISION

Whether the partner was paid / notified

POSTBACK_CTIT

Click-to-install time

POSTBACK_REVERSED

Whether the conversion was later reversed

POSTBACK_WAS_MODIFIED

Whether it was edited after the fact

Money (see section 8 for units)

Field

Meaning

OFFER_WE_GET

Revenue: what the advertiser pays you

OFFER_THEY_GET

Payout: what you pay the partner

OFFER_WE_GET_CURRENCY, OFFER_THEY_GET_CURRENCY

Currency of the amounts

ATTRIBUTION_PARTNER_SALE_AMOUNT

Sale amount reported by the advertiser/MMP

Who and where

Field

Meaning

OFFER_ID, OFFER_NAME

Offer

ADVERTISER_ID, ADVERTISER_NAME

Advertiser

PUBLISHER_ID, PUBLISHER_NAME

Partner (publisher)

PUBLISHER_SUB_ID, PUBLISHER_SUB_SUB_ID, PUBLISHER_UNIQUE1…9

Partner tracking parameters from the click

CLICK_PUBLISHER_CLICK_ID

The partner's own click ID

The originating click

Field

Meaning

CLICK_ID, CLICK_TIME

The attributed click

CLICK_USER_GEO_COUNTRY, CLICK_USER_CONNECTION_IP

Geo and IP at click time

CLICK_USER_DEVICE_OS, CLICK_USER_DEVICE_MODEL

Device at click time

CLICK_USER_IDS_GAID, CLICK_USER_IDS_IDFA

Advertising IDs

Advertiser / MMP data

Field

Meaning

ATTRIBUTION_PARTNER_EVENT_NAME, ATTRIBUTION_PARTNER_EVENT_VALUE

Event name and payload as sent by the advertiser

ATTRIBUTION_PARTNER_INSTALL_TIME

Install time reported by the MMP

ADVERTISER_POSTBACK

The raw postback received

PUBLISHER_POSTBACK_URL, PUBLISHER_POSTBACK_HTTP_STATUS_CODE

The postback we sent to the partner, and their response

The full list is the ConversionReportField enum in the schema (over 200 fields).

6. Custom Report — aggregated statistics

The Custom Report is a pivot table: you choose dimensions (what to group by), metrics (what to count or sum) and a time range. It is the best way to get totals such as clicks, conversions, revenue, payout and profit per day, offer, partner, country, and so on.

It is called rawReport in the API.

6.1 Query

query CustomReport($q: RawQueryInput!) {   rawReport(rawQuery: $q) {     results {       dimensions { name value }       metrics    { name value }     }     totals { name value }     downloadToken   } }

6.2 The query input

input RawQueryInput {   timeRange:     RawDateRangeInput!            # required   dimensions:    [RawStatsDimension!]!         # required, can be []   metrics:       [RawStatsMetric!]!            # required   pagination:    RawQueryPaginationInput!      # required: { limit, offset }   sort:          RawQuerySortInput!            # required   filters:       [RawFilterClauseInput!]       # optional: dimension IN values   metricFilters: [RawMetricFilterClauseInput!] # optional: metric thresholds }

Time range

"timeRange": { "start": "2026-10-01T00:00:00", "end": "2026-10-31T23:59:59", "granularity": "DAY" }

granularity sets how rows are bucketed by time when you include the DATE dimension:

Granularity

Result

HOUR, DAY, WEEK, MONTH

One row per period

ALL

One total for the whole range

Sort — by a metric or a dimension:

"sort": { "field": "CLICKS", "direction": "DESC" } "sort": { "dimension": "DATE", "direction": "ASC" }

Filters — keep only rows where a dimension has one of the given values:

"filters": [   { "dimension": "OFFER_ID",         "value": ["1234", "5678"] },   { "dimension": "USER_GEO_COUNTRY", "value": ["US", "GB"] } ]

Different filters are combined with AND; values inside one filter with OR.

Metric filters — keep only rows whose metric meets a threshold:

"metricFilters": [   { "metric": "CLICKS",       "operator": "GTE",     "values": [1000] },   { "metric": "ACT_WE_GET",   "operator": "BETWEEN", "values": [100, 5000] } ]

Operators: GTE, LTE, EQUALS, BETWEEN.

6.3 Dimensions

Group

Dimensions

Time

DATE (bucketed by granularity), DATE_HOUR, DATE_DAY, DATE_MONTH, DATE_YEAR

Time of click (for conversions)

CLICK_DATE, CLICK_DATE_HOUR, CLICK_DATE_WEEK, CLICK_DATE_MONTH, CLICK_DATE_YEAR

Offer

OFFER_ID, OFFER_NAME, OFFER_STATUS, OFFER_APP_ID, OFFER_TAGS, OFFER_LANDING_PAGE_ID, OFFER_LANDING_PAGE_NAME, OFFER_ADVERTISER_OFFER_ID

Event

EVENT_TYPE_ID, EVENT_TYPE_NAME

Advertiser

ADVERTISER_ID, ADVERTISER_NAME, ADVERTISER_ACCOUNT_MANAGER_NAME, ADVERTISER_TAGS

Partner

PUBLISHER_ID, PUBLISHER_NAME, PUBLISHER_GROUP, PUBLISHER_MANAGER_NAME, PUBLISHER_TAGS

Partner parameters

PUBLISHER_SUB_ID, PUBLISHER_SUB_SUB_ID, PUBLISHER_SITE, PUBLISHER_PLACEMENT, PUBLISHER_APP, PUBLISHER_CREATIVE, PUBLISHER_UNIQUE1–3

Geo

USER_GEO_COUNTRY, USER_GEO_REGION, USER_GEO_CITY

Device

USER_DEVICE_OS, USER_DEVICE_OS_VERSION, USER_DEVICE_TYPE, USER_DEVICE_MAKE, USER_DEVICE_MODEL, USER_DEVICE_BROWSER, USER_DEVICE_LANGUAGE

Connection

USER_CONNECTION_TYPE, USER_CONNECTION_ISP, USER_CONNECTION_CARRIER_CODE

Conversion outcome

POSTBACK_STATUS, POSTBACK_DECISION, REJECTION_REASON

The full list is the RawStatsDimension enum.

6.4 Metrics

Traffic

Metric

Meaning

IMPRESSIONS

Tracked impressions

CLICKS

Tracked (accepted) clicks

UNIQUE_CLICKS

Unique clicks

DISCARDED_CLICKS, DISCARDED_IMPRESSIONS

Traffic blocked by targeting or fraud rules

CTR

Clicks ÷ impressions

Conversions

Metric

Meaning

TOTAL_CONVERSIONS

All conversions, any status

ACT_CONVERSIONS

Approved conversions (advertiser side)

PUB_CONVERSIONS

Conversions reported to partners

CONVERSION_RATE

Approved conversions ÷ clicks

PUB_CONVERSION_RATE

Partner conversions ÷ clicks

REVERSED_CONVERSIONS

Conversions reversed afterwards

Money

Metric

Meaning

ACT_WE_GET

Revenue from approved conversions

ACT_THEY_GET

Payout (cost) for approved conversions

PUB_THEY_GET

Payout on conversions reported to partners

TOTAL_WE_GET, TOTAL_THEY_GET

Revenue / payout across all statuses

PROFIT

Revenue − payout

EARNINGS_PER_CLICK

Revenue ÷ clicks (EPC)

ECPA

Effective cost per action

SALE_AMOUNT

Sale amounts reported by advertisers

REVERSED_WE_GET, REVERSED_THEY_GET

Money on reversed conversions

Caps (current configuration, useful next to OFFER_ID / PUBLISHER_ID)

DAILY_CONVERSION_CAP, DAILY_REVENUE_CAP, DAILY_CLICK_CAP, MONTHLY_CONVERSION_CAP, MONTHLY_REVENUE_CAP, MONTHLY_CLICK_CAP

The full list is the RawStatsMetric enum.

6.5 Example: daily performance per offer

{   "q": {     "timeRange":  { "start": "2026-10-01T00:00:00", "end": "2026-10-07T23:59:59", "granularity": "DAY" },     "dimensions": ["DATE", "OFFER_ID", "OFFER_NAME"],     "metrics":    ["CLICKS", "ACT_CONVERSIONS", "CONVERSION_RATE", "ACT_WE_GET", "ACT_THEY_GET", "PROFIT"],     "pagination": { "limit": 1000, "offset": 0 },     "sort":       { "dimension": "DATE", "direction": "ASC" }   } }

Response:

{   "data": {     "rawReport": {       "results": [         {           "dimensions": [             { "name": "DATE",       "value": "2026-10-01" },             { "name": "OFFER_ID",   "value": "1234" },             { "name": "OFFER_NAME", "value": "Example Game - US" }           ],           "metrics": [             { "name": "CLICKS",          "value": "15230" },             { "name": "ACT_CONVERSIONS", "value": "412" },             { "name": "CONVERSION_RATE", "value": "0.027" },             { "name": "ACT_WE_GET",      "value": "824.00" },             { "name": "ACT_THEY_GET",    "value": "618.00" },             { "name": "PROFIT",          "value": "206.00" }           ]         }       ],       "totals": [         { "name": "CLICKS", "value": "98411" },         { "name": "ACT_CONVERSIONS", "value": "2650" }       ],       "downloadToken": "eyJhbGciOi..."     }   } }

totals holds the totals over all rows, not just the current page.

6.6 More examples

Payout per partner for last month (one row per partner)

{   "q": {     "timeRange":  { "start": "2026-09-01T00:00:00", "end": "2026-09-30T23:59:59", "granularity": "ALL" },     "dimensions": ["PUBLISHER_ID", "PUBLISHER_NAME"],     "metrics":    ["CLICKS", "PUB_CONVERSIONS", "PUB_THEY_GET"],     "pagination": { "limit": 500, "offset": 0 },     "sort":       { "field": "PUB_THEY_GET", "direction": "DESC" }   } }

Hourly clicks and conversions for one offer, by country

{   "q": {     "timeRange":  { "start": "2026-10-15T00:00:00", "end": "2026-10-15T23:59:59", "granularity": "HOUR" },     "dimensions": ["DATE", "USER_GEO_COUNTRY"],     "metrics":    ["CLICKS", "ACT_CONVERSIONS"],     "filters":    [{ "dimension": "OFFER_ID", "value": ["1234"] }],     "pagination": { "limit": 5000, "offset": 0 },     "sort":       { "dimension": "DATE", "direction": "ASC" }   } }

Sub IDs of one partner with at least 500 clicks but no conversions

{   "q": {     "timeRange":  { "start": "2026-10-01T00:00:00", "end": "2026-10-15T23:59:59", "granularity": "ALL" },     "dimensions": ["PUBLISHER_SUB_ID"],     "metrics":    ["CLICKS", "ACT_CONVERSIONS"],     "filters":    [{ "dimension": "PUBLISHER_ID", "value": ["56"] }],     "metricFilters": [       { "metric": "CLICKS",          "operator": "GTE",    "values": [500] },       { "metric": "ACT_CONVERSIONS", "operator": "EQUALS", "values": [0] }     ],     "pagination": { "limit": 1000, "offset": 0 },     "sort":       { "field": "CLICKS", "direction": "DESC" }   } }

Conversions by status and rejection reason

{   "q": {     "timeRange":  { "start": "2026-10-01T00:00:00", "end": "2026-10-15T23:59:59", "granularity": "ALL" },     "dimensions": ["POSTBACK_STATUS", "REJECTION_REASON"],     "metrics":    ["TOTAL_CONVERSIONS"],     "pagination": { "limit": 100, "offset": 0 },     "sort":       { "field": "TOTAL_CONVERSIONS", "direction": "DESC" }   } }

7. Downloading reports as CSV

Every report response includes a ready-made download link for the same query, without the page limit. This is the easiest way to export large data sets.

Report

Response field

Download URL

Click Report

csvDownloadUrl (a path)

https://api.<your-platform>.swaarm-clients.com + csvDownloadUrl

Conversion Report

csvDownloadUrl (a path)

https://api.<your-platform>.swaarm-clients.com + csvDownloadUrl

Custom Report

downloadToken

https://api.<your-platform>.swaarm-clients.com/download/raw-reports/<downloadToken>

curl -s -o clicks.csv "https://api.<your-platform>.swaarm-clients.com/download/data-report/Click/eyJhbGciOi..."

Good to know:

  • No Authorization header is needed. The link itself is signed. Treat it like a password and do not share it.

  • Links expire after 1 hour. Run the query again to get a fresh link.

  • To make the request cheap, ask for a page size of 1 (first: 1 or limit: 1) and just use the link.

  • Use csvFields (Click and Conversion Reports) to put more columns in the CSV than you fetch through GraphQL.

  • In the CSV, dates are in your platform's time zone and money is in currency units (e.g. 1.25).

8. Dates, time zones and money

8.1 Date format

Send dates as LocalDateTime strings, without a time zone:

2026-10-01T00:00:00

Both start and end are inclusive. To cover a full day, use T00:00:00 to T23:59:59.

8.2 Time zone

Time ranges and the dates in the results use your platform's reporting time zone, as configured in the Swaarm platform settings. This is the same time zone you see in the dashboard, so API numbers match the UI.

8.3 Money values

Report

How money is returned

Custom Report (rawReport)

Strings in currency units, e.g. "824.00". Rates such as CONVERSION_RATE and CTR are fractions: "0.027" means 2.7%.

Conversion Report (conversionReport)

OFFER_WE_GET, OFFER_THEY_GET and their _ORIGINAL_ variants are integers in micro-units. Divide by 100,000,000 to get currency units: 125000000 → 1.25.

CSV downloads

Currency units.

When you filter the Conversion Report on a money field, pass plain currency units ("1.25"), not micro-units.

Amounts are in your platform currency unless a *_CURRENCY field says otherwise.

8.4 Filter operators (Click and Conversion Reports)

Operator

values

Meaning

IN

one or more

Field equals any of the values

NOT_IN

one or more

Field equals none of the values

CONTAINS

one

Field contains the text

NOT_CONTAINS

one

Field does not contain the text

GREATER_EQUAL

one

≥ value

LOWER_EQUAL

one

≤ value

BETWEEN_INCLUDING

two

Between the two values, inclusive

IS_EMPTY

none

Field has no value

IS_NOT_EMPTY

none

Field has a value

9. Complete scripts

9.1 Bash: daily custom report to the terminal

#!/usr/bin/env bash set -euo pipefail  API="https://api.<your-platform>.swaarm-clients.com/graphql"  TOKEN=$(curl -s "$API" -H 'Content-Type: application/json' -d "$(jq -n \   --arg u "$SWAARM_USER" --arg p "$SWAARM_PASSWORD" \   '{query: "mutation($i: AuthLoginInput!){ login(input:$i){ token { accessToken } } }",     variables: {i: {username: $u, password: $p}}}')" \   | jq -r '.data.login.token.accessToken')  curl -s "$API" \   -H "Authorization: Bearer $TOKEN" \   -H 'Content-Type: application/json' \   -d '{     "query": "query($q: RawQueryInput!){ rawReport(rawQuery:$q){ results { dimensions{name value} metrics{name value} } totals{name value} } }",     "variables": { "q": {       "timeRange":  {"start":"2026-10-01T00:00:00","end":"2026-10-07T23:59:59","granularity":"DAY"},       "dimensions": ["DATE","OFFER_ID","OFFER_NAME"],       "metrics":    ["CLICKS","ACT_CONVERSIONS","ACT_WE_GET","ACT_THEY_GET","PROFIT"],       "pagination": {"limit":1000,"offset":0},       "sort":       {"dimension":"DATE","direction":"ASC"}     } }   }' \   | jq -r '.data.rawReport.results[]            | [(.dimensions[].value), (.metrics[].value)] | @tsv'

9.2 Python: page through all conversions

import os import requests  API = "https://api.<your-platform>.swaarm-clients.com/graphql" MICRO = 100_000_000 MONEY_FIELDS = {"OFFER_WE_GET", "OFFER_THEY_GET", "OFFER_ORIGINAL_WE_GET", "OFFER_ORIGINAL_THEY_GET"}  LOGIN = """ mutation($i: AuthLoginInput!) { login(input: $i) { token { accessToken } } } """  CONVERSIONS = """ query($start: LocalDateTime!, $end: LocalDateTime!, $fields: [ConversionReportField!],       $filters: [ConversionReportFilter!], $first: Int, $offset: Int) {   conversionReport(start: $start, end: $end, fields: $fields, filters: $filters,                    first: $first, offset: $offset,                    sort: { field: POSTBACK_TIME, order: ASC }) {     totalCount     edges { node { records {       field       value {         ... on StringValue      { s: value }         ... on IntValue         { i: value }         ... on FloatValue       { f: value }         ... on BooleanValue     { b: value }         ... on DateValue        { d: value }         ... on StringArrayValue { a: value }       }     } } }   } } """   def gql(query, variables, token=None):     headers = {"Authorization": f"Bearer {token}"} if token else {}     r = requests.post(API, json={"query": query, "variables": variables}, headers=headers, timeout=300)     r.raise_for_status()     body = r.json()     if body.get("errors"):         raise RuntimeError(body["errors"])     return body["data"]   def flatten(record):     value = record["value"]     if value is None:         return None     v = next(iter(value.values()))     if record["field"] in MONEY_FIELDS and v is not None:         return v / MICRO     return v   token = gql(LOGIN, {"i": {"username": os.environ["SWAARM_USER"],                           "password": os.environ["SWAARM_PASSWORD"]}})["login"]["token"]["accessToken"]  page_size, offset, rows = 1000, 0, [] while True:     data = gql(CONVERSIONS, {         "start": "2026-10-01T00:00:00",         "end": "2026-10-31T23:59:59",         "fields": ["POSTBACK_ID", "POSTBACK_TIME", "CLICK_ID", "OFFER_ID", "PUBLISHER_ID",                    "PUBLISHER_SUB_ID", "EVALUATION_STATUS", "OFFER_WE_GET", "OFFER_THEY_GET"],         "filters": [{"field": "EVALUATION_STATUS", "operator": "IN", "values": ["APPROVED"]}],         "first": page_size,         "offset": offset,     }, token)["conversionReport"]      batch = [{r["field"]: flatten(r) for r in e["node"]["records"]} for e in data["edges"] or []]     rows.extend(batch)     offset += page_size     if not batch or offset >= data["totalCount"]:         break  print(f"Fetched {len(rows)} conversions")

10. Troubleshooting and best practices

Common errors

Symptom

Cause and fix

401 / "Unauthorized"

Access token missing or expired. Refresh it (section 2.3) or log in again.

"Access denied" on a report

The user lacks permission for that report. Ask your platform admin to grant access.

"Unable to generate conversion report" / "Unable to generate data report"

Usually an unknown field name or an invalid filter value (e.g. text in a money filter). Check names against dataReportFields or the schema enums.

"The report would have returned more than 10GB of data…"

The Custom Report is too large. Shorten the time range, remove high-cardinality dimensions (IP, sub IDs, device model) or add filters.

Validation error mentioning an enum

A dimension, metric or field name is misspelled. Names are case-sensitive and use UPPER_SNAKE_CASE.

Download link returns an error

The link is older than 1 hour. Run the query again for a fresh one.

Best practices

  • Pick the right report. Need totals? Use the Custom Report. It is much faster than fetching individual clicks or conversions and adding them up yourself.

  • Use granularity: ALL when you only need one total per group.

  • Ask only for what you need. Fewer dimensions and fields mean faster queries.

  • Page through row-level reports with first/offset, or use the CSV link for full exports.

  • Avoid wide time ranges on click logs. Click volumes can be very large; query a day (or an hour) at a time and loop.

  • Re-sync recent data. Conversions can be approved, rejected or reversed after they arrive. When you sync daily, re-pull the last few days rather than only "yesterday".

  • Reuse your token. Log in once and refresh the token as needed; do not log in on every request.

Need help?

Contact your Swaarm account manager or our support team with the query you sent, the time you sent it, and the error you received.

Did this answer your question?