Core resources

Reports

Clicks, conversions and earnings for a date range, grouped by program, channel, day or sub ID.

The report row

A report row is one slice of your traffic for a date range (a program, a channel, a day or a sub ID) with its clicks, conversions and earnings.

Every row has clicks, conversions, conversionRate and earnings. The key fields in front of them depend on groupBy.

Fields
programIdinteger

groupBy=program only, alongside merchantName (string | null).

channelIdinteger

groupBy=channel only, alongside channelName (string | null).

datestring

groupBy=day only. A UTC day as YYYY-MM-DD.

subIdstring | null

groupBy=subId only. Null collects clicks on links minted without a sub ID.

clicksinteger

Clicks recorded in the range.

conversionsinteger | null

Conversions reported in the range, rejected ones excluded. Null for groupBy=subId.

conversionRatenumber | null

conversions as a percentage of clicks, rounded to one decimal, so 2.3 means 2.3%. Null when the row has no clicks, and always null for groupBy=subId.

earningsobject[]

One entry per currency, never summed across currencies. Empty when the row has no conversions.

earnings[].currencystring | null

ISO 4217 code the network reported the commission in. Null when the network did not say.

earnings[].commissionstring

What you have earned: conversions that are approved, available or ready_for_payout. A two-decimal string.

earnings[].pendingstring

Conversions that are still pending, reported by the network but not yet approved. A two-decimal string.

Money stays in its own currency

A program that pays in two currencies returns two earnings entries. Convert them yourself if you need one total. We never add USD to CAD.
{
  "programId": 4821,
  "merchantName": "Northpeak Outdoors",
  "clicks": 4180,
  "conversions": 96,
  "conversionRate": 2.3,
  "earnings": [
    { "currency": "USD", "commission": "742.18", "pending": "210.40" },
    { "currency": "CAD", "commission": "58.90", "pending": "0.00" }
  ]
}

Performance report

GEThttps://api.linkapprove.com/v1/reports/performancereports:read

Returns a paginated list of report rows for your account across every channel. Only programs you have applied to on one of your channels contribute.

Query parameters
startDatestringrequired

Start of the range as YYYY-MM-DD, inclusive.

endDatestringrequired

End of the range as YYYY-MM-DD, inclusive. On or after startDate, and the range may cover at most 180 days.

groupByenum

program, channel, day or subId. Defaults to program.

pageinteger

1-based page number. Defaults to 1.

sizeinteger

Rows per page, 1–100. Defaults to 10.

When it fails

  • 400 startDate is required or endDate is required: both dates are mandatory.
  • 400 startDate must be YYYY-MM-DD or startDate is not a real date: the same two checks apply to endDate.
  • 400 endDate must not be before startDate or The range may cover at most 180 days: reported against endDate. Split a longer period into several requests.
  • 400 groupBy is invalid: anything other than the four groupings.
  • 403 insufficient_scope: the key does not hold reports:read.
curl "https://api.linkapprove.com/v1/reports/performance?startDate=2026-08-01&endDate=2026-08-18&groupBy=program&page=1&size=10" \
  -H "Authorization: Bearer $LINKAPPROVE_API_KEY"
Try it
GEThttps://api.linkapprove.com/v1/reports/performance?startDate=2026-08-01&endDate=2026-08-18&groupBy=program&page=1&size=10

Held in this tab's session storage only, and reused by every runner on the site.

This panel replays the documented sample response and does not call the API. Copy a code sample to send a real request with your key.

400 Validation failed
{
  "success": false,
  "message": "Validation failed",
  "error": [
    {
      "field": "endDate",
      "code": "invalid_format",
      "message": "The range may cover at most 180 days"
    }
  ]
}

Grouping

groupBy decides what a row is. Each grouping puts its own key in front of the common fields.

  • program: programId and merchantName. The default.
  • channel: channelId and channelName.
  • day: date, one row per UTC day that had a click or a conversion. Clicks count on the day they happened, conversions on the date the network reported for them.
  • subId: subId from the tracking link. Clicks only: sub IDs are not sent to networks, so conversions and conversionRate are null and earnings is empty. See Sub IDs and attribution.

Ordering

day rows come back oldest first. Every other grouping is sorted by clicks, highest first. Days with no activity are not filled in, so a gap in the dates means nothing happened.

groupBy=day
{
  "success": true,
  "message": "Report fetched",
  "data": {
    "data": [
      {
        "date": "2026-08-01",
        "clicks": 233,
        "conversions": 4,
        "conversionRate": 1.7,
        "earnings": [
          { "currency": "USD", "commission": "31.20", "pending": "12.75" }
        ]
      },
      {
        "date": "2026-08-02",
        "clicks": 0,
        "conversions": 1,
        "conversionRate": null,
        "earnings": [
          { "currency": null, "commission": "0.00", "pending": "6.30" }
        ]
      }
    ],
    "count": 2,
    "pages": 1
  }
}

Data freshness

  • Clicks are written in batches, so they show up within about ten minutes.
  • Conversions appear as networks report them, and move from pending into commission as networks approve them. Yesterday's numbers can still change.
  • A conversion the network rejects drops out of conversions and out of both earnings figures.

Re-pull recent days

If you mirror reports into your own store, re-fetch the last few weeks on every run rather than only the newest day, so late approvals and rejections land.