> AffiliateOS Help Center. Index of every article for AI tools: https://www.affiliateos.ai/help/llms.txt

# Pull conversions instead (Reporting API)

Postbacks and conversions · For publishers, networks · Updated 30 October 2026
Web version: https://www.affiliateos.ai/help/postbacks/pull-conversions-reporting-api

Call `GET /publisher/conversions` with your publisher API key. It returns the conversions on your account in the window you ask for (the last 90 days if you send no dates), newest first, under the same `action_id` your postbacks carry, so you can reconcile against what your server received or catch up after downtime.

Postbacks stay the realtime path: a pull only shows what happened before you called. Use it next to postbacks, not as the only way you credit users, unless a few minutes' delay is fine for you.

## Before you start

- A publisher [API key](https://www.affiliateos.ai/glossary#api-key). An owner or admin creates it under **My Profile**, then **API Keys**, with **Generate Key**. It starts with `pk_` and is shown once.
- Networks: network keys read offers only, so there is no pull endpoint for them today. Use postbacks, and **Download CSV** under **Reporting**, then **Offer Performance**.

## Steps

### Call GET /publisher/conversions

Send the key in the `x-api-key` header. `from` and `to` are ISO-8601 timestamps; a date alone, such as `2026-10-29`, means midnight UTC at the start of that day. Leave both out and you get the last 90 days.

```bash
curl -G "https://affiliateos.xyz/publisher/conversions" \
  -d from=2026-10-29T00:00:00Z \
  -d to=2026-10-30T00:00:00Z \
  -d limit=500 -d page=1 \
  -H "x-api-key: $PEBBLE_PUBLISHER_KEY"
```

Example response, one row, for Pebble Rewards:

```json
{
  "data": [
    {
      "actionId": "act_5Kq2Wc",
      "offerId": "aff_7c2e91b04d3a",
      "offerName": "Harbor Blocks",
      "conversionName": "Install",
      "subId": "u_48213",
      "status": "completed",
      "partnerPayoutUsd": 4.2,
      "userPayoutUsd": 1.5,
      "reversalReason": null,
      "country": "US",
      "createdAt": { "_seconds": 1793300980, "_nanoseconds": 0 },
      "clicktimestamp": "2026-10-29T19:06:00Z",
      "reversedAt": null
    }
  ],
  "pagination": { "page": 1, "limit": 500, "total": 1 }
}
```

`partnerPayoutUsd` is what we pay you for the step. `userPayoutUsd` is the player's reward. Add `offerId` to the query to read one offer.

### Page through the window

`limit` takes 1 to 500 (50 if you leave it out) and `page` starts at 1. Keep asking for the next page until `page` times `limit` reaches `pagination.total`.

> **One call reads the newest 10,000 conversions in its window.** If your window holds more, the oldest ones are left out and `total` stops at 10,000. Ask for a day at a time instead.

### Match each row on actionId and status

`actionId` is the same value as the postback's `action_id`. Store rows under `actionId` and `status` together, as you do for postbacks:

- `completed`: the conversion. Credit it if your server never got its postback.
- `reversed`: taken back. `reversalReason` says why when we have a reason, and `reversedAt` says when the reversal came in. `createdAt` stays the time of the conversion.
- `rejected`: a user our traffic checks blocked. It pays $0 and sends no postback. [Held or rejected traffic](https://www.affiliateos.ai/help/fraud-defense/held-rejected-traffic) explains it.

On the Hosted Offerwall, `subId` is the `uid` you opened the wall with. For conversions first sent before 10 September 2026, the postback's `action_id` can be longer than the report's `actionId`: match those on the end of the ID.

### Check it worked

Send your own test user through one offer, wait for the postback, then pull the same hour. The row shows your test user's `subId`, and its `actionId` equals the `action_id` your server logged.

## Other reads with the same key

| Endpoint | What it returns |
|---|---|
| `GET /publisher/performance/daily` | Conversions and payout per UTC day. `from` and `to` are dates (`2026-10-01`); the last 90 days by default. `status=all` also counts reversed rows as conversions; the money is the same. |
| `GET /publisher/dashboard-report` | The dashboard's numbers, grouped by `day`, `week`, `month`, `quarter`, `year`, `offer`, `platform` or `country` with `groupBy`, between `startDate` and `endDate`. |
| `GET /publisher/stats` | Lifetime clicks, conversions, events and earnings for each offer. |
| `GET /publisher/traffic-quality` | Users whose conversions are rejected right now, with the reason and when it expires. |

Payout in every report is net of [reversals](https://www.affiliateos.ai/glossary#reversal), and every reversal is dated on its conversion's day. [Why our numbers and yours differ](https://www.affiliateos.ai/help/reporting/why-numbers-differ) covers what that does to a closed week.

## What changes when you pull instead of receiving postbacks

- **Nothing is pushed.** You see a conversion on your next call, not when it happens.
- **No signature to check.** Your key proves who you are. Keep it on your server.
- **No secret needed to pull.** Without a [postback secret](https://www.affiliateos.ai/glossary#postback-secret), we send no postbacks, but every conversion still counts and still shows here.
- **Some conversions wait.** A conversion waiting for our review doesn't appear until it's approved.
