7 PartnerStack API Behaviors That Break Affiliate Reporting

Back to BlogTechnology

7 PartnerStack API Behaviors That Break Affiliate Reporting

Ben Jolly
August 19, 2026
11 min read

Quick Answer: PartnerStack API v2 has seven behaviors that will quietly skew affiliate reporting if you build against it without accounting for them. Reward objects are created exactly 30 days after their transaction, so /rewards returns almost nothing for the current month. Transactions can sit past that window with no reward at all, which means the partner was never credited. Transaction amounts are multi-currency while commissions are always USD. The status filter on /partnerships is accepted and ignored. Pagination returns duplicate records and hits repeatable 500 errors at specific cursor positions. Declining an application through the partnerships endpoint deletes the partnership. And click counts are lifetime totals that vanish when a partner is removed. None of these throw an error. Each one returns a plausible number that is wrong.

If you pull PartnerStack data through the API and the totals do not match the dashboard, the API is usually not broken. It is answering a different question than the one you think you asked.

What follows comes from building reporting against PartnerStack API v2 for B2B SaaS programs, including one with roughly 66,000 reward records. These behaviors are current as of August 2026. Any active API evolves, so treat each one as something to verify against your own program rather than a permanent fact.

The reason these are expensive is that none of them fail loudly. A naive integration returns a number, the number looks reasonable, and nobody catches it until a partner emails asking where their commission went.

1. The /rewards endpoint is blind to the last 30 days

PartnerStack creates the reward object exactly 30 days after the transaction that earned it. Not approximately 30 days. Across roughly 66,000 reward records in a single program, the lag was 30 days in every case.

The dashboard hides this. The PartnerStack UI shows the commission as pending the moment the transaction lands, so a program manager watching the interface sees the current month filling in normally. Pull the same period from /rewards and you get close to zero.

What it breaks: every month-to-date commission report, every answer to "how are we tracking this month," and any dashboard refreshing daily off /rewards.

How to check: pull /rewards for the last 15 days and compare the record count to the same length of window 60 days ago. If the recent window is near-empty and the older one is full, you are looking at the lag, not a slowdown in the program.

The fix: current-month commission reporting has to come from /transactions with an estimated rate applied, or it has to be labelled as trailing by 30 days. Blending the two sources without a stated boundary gives you a figure that is neither.

2. Transactions that never get a reward at all

Once you know about the 30-day lag, a more valuable question follows: what about transactions that are well past that window and still have no reward object attached?

Those are commissions the partner was never credited for.

What it breaks: nothing visible, which is exactly the problem. The money does not appear as a discrepancy anywhere. The partner has no way to know what they should have been paid, and the program manager has no reason to go looking.

How to check: pull /transactions and /rewards over the same date range, build a set of the transaction keys referenced by the rewards, and diff the two. Filter to transactions older than about 33 days (30 for the lag plus a few days of slack) and above whatever dollar floor is worth chasing. Everything left is a candidate.

The work involved: the raw diff is noisy. To turn it into something you can escalate, each orphaned transaction has to be resolved to its customer, the customer to the partnership, and the partnership to an expected commission rate derived from that partner's other rewards. Only then do you have a defensible list to take to PartnerStack support.

This audit has surfaced genuinely unpaid commissions on every program we have run it against. It is the highest-value item on this list by a wide margin.

3. Revenue is multi-currency, commissions never are

Transactions carry an amount in the customer's billing currency and, when PartnerStack has resolved it, an amount_usd. Rewards are always denominated in USD.

Sum reward-associated revenue without checking, and you are adding euros, pounds, and pesos to dollars as though they were the same unit.

What it breaks: every revenue figure in a program with international customers. The error runs in one direction (it almost always inflates revenue) and it scales with your non-US mix.

How to check: group your transactions by currency and look at the non-USD share. Anything above a few percent means the revenue numbers you have built from rewards are overstated.

The fix: map each reward back to its transaction and read amount_usd from there. Because rewards trail transactions by 30 days, the transaction behind an early-window reward sits outside your window, so the transaction fetch needs a lookback of at least 45 days. Any reward whose transaction still cannot be resolved should be dropped from the revenue total and reported as a separate excluded count. Never fall back to the local-currency amount. An excluded row is visible in the output; an inflated one is invisible forever.

4. The status filter on /partnerships is ignored

GET /partnerships?status=approved returns every partnership regardless of status. The parameter is accepted, the call returns 200, and the filter does nothing.

What it breaks: partner counts. If you have ever reported "we have 400 approved partners" from a filtered API call, that number is your total partnership count with pending and declined records folded in.

How to check: call the endpoint twice, once with status=approved and once with status=declined, and compare the totals. Identical counts mean the filter is being ignored.

The fix: fetch the full set and filter client-side on the approved_status field of each record. On programs with 10,000 or more partnerships this gets slow, so pair it with the min_created parameter to bound the window to partners who joined recently.

5. Pagination returns duplicates and dies at specific cursors

Two separate things to handle in the same place.

Duplicates. The same partnership key comes back on more than one page. Concatenate pages without deduplicating on key and your counts drift upward for no visible reason.

The transaction wall. Certain cursor and page-size combinations on /transactions return a 500. Not intermittently. The same cursor at the same page size fails every time, which means retry logic does not rescue it. A naive integration either crashes or, worse, catches the error, stops paginating, and reports whatever it managed to collect as the complete set.

How to check: if a transaction export keeps returning the same suspiciously round record count, or always stops at the same date no matter how you widen the range, you are hitting the wall and silently truncating.

The fix: deduplicate on record key. For the wall, shrink the page size and re-request from the same cursor. Stepping down from 250 to 10, then 2, then 1 gets past it in practice. Once you have shrunk, stay small for the remainder of that fetch instead of stepping back up, because cursors near the failure point tend to fail too. This makes large exports considerably slower, and it is the only approach we have found that reliably finishes.

6. Declining an application through the wrong endpoint deletes the partnership

PATCH /partnerships/{key} with approved_status set to declined does not decline the application. It archives the partnership and removes the record.

The correct call is PATCH /applications/{key}/decision with { "approved": false }. Watch the key format while you are at it: applications are prefixed appl_ and partnerships are prefixed part_. They are not interchangeable, and passing the wrong one fails in ways that are not always obvious.

What it breaks: the partner record disappears, taking its click history with it (see the next section). There is no API path to undo it.

A related trap: /applications?actioned=false returns applications that have already been approved or declined. To get the genuinely pending queue, filter to records where approved is null.

The fix: route every decline through the applications decision endpoint, and treat the partnerships PATCH as a destructive operation that needs a confirmation step in front of it.

7. Click counts are lifetime totals that evaporate

stats.LINK_CLICKS on a partnership is a cumulative lifetime counter. There is no time-series click data anywhere in API v2. You cannot ask the API how many clicks a partner drove last month.

It gets worse. When a partnership is deleted, its counter goes with it, while the dashboard's event-based click report retains the underlying events.

What it breaks: any click total computed from the API will sit below the dashboard's figure for the same period, permanently, by however many clicks belonged to partners who have since been removed. Two reports, each internally correct, that will never reconcile.

How to check: compare an API-derived lifetime click total against the dashboard's all-time click report. A consistent gap in the dashboard's favour is deleted partners, not a bug in your code.

The fix: there is no clean one. Either take clicks from dashboard exports and everything else from the API, or accept the gap and document it wherever the number gets published. What you cannot do is present the two side by side and let the reader assume they measure the same thing.

Three smaller ones worth knowing

The /groups endpoint does not return your default groups. Groups such as affiliatesprogram and referralwidget frequently do not come back from /groups, yet they appear on the partnership records themselves. Build your group list from both sources or you will quietly drop whole segments from group-level reporting.

Customer partnership_key goes stale. When a partner moves between groups or their partnership is recreated, the partnership_key stored on the customer record can point at something that no longer resolves. Falling back to partner_key recovers most of them.

Group attribution is historical on rewards and current on partnerships. A reward carries the group the partner belonged to at the moment it was created. If you want revenue by group as the program is structured today, you have to re-attribute through the current partnership record, and the two views will diverge after any group reshuffle. Neither is wrong. Pick one, and label which one you picked.

How to audit your own integration

Five checks, in the order we would run them:

  1. Pull /rewards for the last 15 days. Near-empty means you are seeing the 30-day lag, and any current-month commission figure you publish is wrong.
  2. Group /transactions by currency. A meaningful non-USD share means your reward-derived revenue is inflated.
  3. Call /partnerships with status=approved, then with status=declined. Matching counts mean the filter is being ignored and your partner numbers are totals.
  4. Diff transaction keys against those referenced by rewards, older than 33 days. Anything left is a potentially unpaid commission.
  5. Compare your API click total against the dashboard's. A persistent gap is deleted partners.

If the first three come back clean, your integration is in better shape than most of the ones we have inherited. Check four is the one that tends to find money.

Frequently Asked Questions

Why don't my PartnerStack API numbers match the dashboard?

Usually one of three causes. Reward objects are created 30 days after their transaction, so the API shows far less recent commission than the dashboard, which displays pending commissions immediately. Revenue derived from rewards mixes currencies unless you map back to each transaction's amount_usd. And click totals from the API exclude partners who have been deleted, while the dashboard's event-based report retains their history. Check the date window and the currency mix before assuming your code is at fault.

How long does PartnerStack take to create a reward after a transaction?

Exactly 30 days. Across roughly 66,000 reward records in one program, the lag was 30 days in every case with no exceptions. The PartnerStack UI displays the commission as pending immediately, which is why the delay is easy to miss until you query the API directly. Any commission report covering the last 30 days that is built from /rewards will be close to empty.

Does the PartnerStack API support filtering partnerships by status?

No. The status parameter on /partnerships is accepted and returns a 200, but it does not filter anything. You get the full set back regardless of the value you pass. Filter client-side on each record's approved_status field instead, and use min_created to keep the fetch size manageable on large programs.

How do I find affiliate commissions PartnerStack failed to pay?

Diff the transaction keys returned by /transactions against the transaction keys referenced by records from /rewards over the same period. Restrict the result to transactions older than 33 days, since anything younger is still inside the normal 30-day reward creation window. Each remaining transaction is a candidate for a commission that was never credited. Resolving them to a partner requires walking the customer to partnership chain, and estimating the amount owed requires deriving that partner's effective rate from their other rewards.

Can I get click data by date range from the PartnerStack API?

No. stats.LINK_CLICKS is a lifetime cumulative counter per partnership and API v2 exposes no time-series equivalent. Date-ranged click data is only available through dashboard report exports. If you need clicks by month, that number has to come from the UI, and it will not match an API-derived total because deleted partners keep their events in the report but lose their counter.

What is the correct way to decline an application in PartnerStack?

Use PATCH /applications/{key}/decision with { "approved": false }, passing an application key prefixed appl_. Do not use PATCH /partnerships/{key} with approved_status set to declined. That call archives the partnership and deletes the record rather than declining the application, and there is no API route to reverse it.

Is PartnerStack's API reliable enough to build client reporting on?

Yes, with the caveat that it needs a defensive layer in front of it. The endpoints are stable and the data is accurate once you account for the behaviors above. What it will not tolerate is a thin wrapper that trusts filters, assumes pagination completes, and sums amounts without checking currency. Budget for that handling layer up front and the data itself holds up well.


If your PartnerStack numbers have never been reconciled against the raw API, contact Jolly Consulting and we will run the unpaid-commission audit against your program.

Related reading:

About the Author

Ben Jolly

Ben Jolly is the founder of Jolly Consulting. He previously led ClickUp's global affiliate program, scaling it to 8-figure annual commissions, and now helps B2B SaaS companies build quality-focused affiliate programs and get cited by AI search engines.

Need Help With Your Affiliate Program?

Let's discuss how we can help you build or optimize your B2B affiliate program.