> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aggrefine.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mirror a channel

> Keep your own copy of a channel's offers up to date, reading only what changed.

To show offers on your site, keep your own copy of the channel's offers (a database table keyed
by the offer's `id`) and serve your pages from it. Load it once through `/offers`, then read only
the changes. This guide uses the routes from the [Quickstart](https://docs.aggrefine.com/quickstart.md).

## 1. Load the channel once

1. Note the time, in UTC: `started_at`.
2. Page through `GET /v1/channels/{channelId}/offers?limit=1000`, passing each page's
   `next_cursor` as `cursor`, until `next_cursor` is null.
3. Store every offer by its `id`.

Offers come in `id` order, so if a request fails you can go on from the last `next_cursor` you
stored. There is no total count: the last page is the one whose `next_cursor` is null.

## 2. Read the changes since

Ask for the changes since `started_at`, then since the `next` of your last answer:

```bash theme={null}
curl "https://api.aggrefine.com/v1/channels/$CHANNEL_ID/changes?since=2026-11-07T06:00:00Z&limit=1000" \
  -H "Authorization: Bearer $AGGREFINE_API_KEY"
```

Apply each item in the order given:

* `offer_changed`: store `offer` in place of what you have for `id` (add it if it is new). It is
  the whole offer as it is now, not only the fields that changed.
* `offer_removed`: delete `id` from your copy. If you don't have it, ignore it.

Changes to what the channel shows come as the same two kinds of item, before the other changes:
when a merchant or feed stops being shown (an approval ends, you ignore it, you disconnect its
network, it is paused over your plan's product limit), every one of its offers comes as
`offer_removed`; when one starts being shown, or a merchant's `name` or `market`, a feed's `name`
or your channel's own id in `link` changes, every one of its offers comes as `offer_changed`. A
merchant with many offers takes one page per 1,000 of them.

Then store `next`. While `has_more` is true, ask again with it straight away; once it is false
you are caught up, and you ask again later with the same `next`. Store `next` only after you have
applied its page, so a failure in between makes you read that page again rather than skip it.

```js theme={null}
async function catchUp(channelId, since) {
  for (;;) {
    const res = await fetch(
      `https://api.aggrefine.com/v1/channels/${channelId}/changes?since=${encodeURIComponent(since)}&limit=1000`,
      { headers: { Authorization: `Bearer ${process.env.AGGREFINE_API_KEY}` } },
    );
    if (res.status === 410) return reload(channelId); // too old, or no longer exact: load the channel again (step 1)
    if (!res.ok) throw new Error(`${res.status}: ${(await res.json()).error}`);
    const page = await res.json();
    for (const change of page.items) {
      if (change.type === "offer_changed") await saveOffer(change.offer);
      else if (change.type === "offer_removed") await deleteOffer(change.id);
    }
    await saveNext(channelId, page.next);
    if (!page.has_more) return;
    since = page.next;
  }
}
```

`next` is opaque: store it as you got it, never build or change it.

### When changes show up

Offers change when Aggrefine imports a merchant's feed again, so changes come in batches, not
evenly through the day. A change is listed 10 minutes after Aggrefine stores it: one stored in the
last 10 minutes comes on a later call, after the ones before it. Asking more often than every few
minutes gains you nothing.

### The same change can come twice

Your copy stays right as long as you apply each item as "this is the offer now": an offer that
changed twice may come twice, each time as it is now, and the first changes after `started_at` can
repeat what your first load already had. Writing the same offer again changes nothing.

### Ask for the first changes right after loading

Your first call, with `started_at`, takes the merchants and feeds the channel shows at that moment
as its start: a change to them between your load and that call isn't listed. So ask for the first
changes as soon as the load ends; from then on every change to them is listed.

## 3. Keep within 7 days

Changes are kept 7 days. Ask for them at least once within 7 days of your last call (or of `started_at`):
an older `since` answers `410` and none of its changes can be given any more. Then load the channel
again (step 1) and go on with the changes since that new start.

When you load again, replace your copy: delete the offers the new load didn't list.

## When the changes can't be given

Some feeds are kept only for your own network login (impact.com and Awin), and are deleted a few
minutes after you disconnect that network. If you ask for changes only after that, their offers
can't be named any more: the answer is `410`, and you load the channel again (step 1, replacing
your copy). Asking for changes right after you disconnect a network, before they are deleted,
gives them as removed instead.

## Requests it takes

Every page is one request against your plan's daily limit (300 on Free). A channel of 100,000
offers takes 100 requests to load; each call for its changes is one more, and one page holds up
to 1,000 changes. See [Errors and limits](https://docs.aggrefine.com/errors-and-limits.md#daily-limit).

## Errors

The ones a mirror meets most; [Errors and limits](https://docs.aggrefine.com/errors-and-limits.md) has them all.

| Status | What to do |
| - | - |
| `410` | `since` is more than 7 days ago, or the changes since it can't be given any more: load the channel again |
| `429` | Wait for `Retry-After` (seconds), then ask again with the same values |
| `5xx` | Ask again with the same values, with backoff: every request is safe to repeat |
| `403` | The channel is over your plan's limit: upgrade, or use a channel your plan covers |
| `404` | The channel no longer exists, or it belongs to another organization than your key's |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.