# Affiliates endpoints

These REST routes manage affiliate partners and report on the clicks their short links receive. Each entry lists the permission, parameters, the response fields and the errors.
Page: https://docs.linkmanagerpro.com/developers/rest-api/affiliates
Use when someone asks: link manager pro affiliates api; affiliate report api; create affiliate rest api
Intent: Reference

Sign-in, the response envelope and paging are explained in the [REST API overview](https://docs.linkmanagerpro.com/developers/rest-api/).

All paths are relative to the base URL `/wp-json/link-manager-pro/v1`. Every route here needs `link_manager_pro_access`. The examples use `https://example.com` and the Application Password placeholder `USER:APPLICATION_PASSWORD`.

A partner owns no short links directly. A short link counts for a partner when its destination is on the partner's domain. The comparison ignores letter case and a leading `www.`, and the hidden links behind [redirects](https://docs.linkmanagerpro.com/developers/rest-api/redirects) never count.

When **Affiliate link prefix** is set, a short link to a partner's domain gets its address under that prefix, for example `https://example.com/recommends/amazon-deal`. Creating, changing or deleting a partner changes which links use it. See [General settings](https://docs.linkmanagerpro.com/guides/settings/general#affiliate-link-prefix).

A missing partner ID gives this `404` on every route with `{id}`:

```json
{
  "code": "not_found",
  "message": "The requested resource was not found.",
  "data": { "status": 404 }
}
```

## The affiliate object

[List affiliates](#list-affiliates) returns the stored fields plus extra counts. [Create an affiliate](#create-an-affiliate) and [Update an affiliate](#update-an-affiliate) return the stored fields under `data.affiliate`. The table is the `link_manager_pro_affiliates` table ([Database tables](https://docs.linkmanagerpro.com/developers/data/tables)).

| Field | Type | What it holds |
|---|---|---|
| `id` | integer | The partner ID. |
| `site_name` | string | The partner's name, up to 100 characters. |
| `domain` | string | The host of `affiliate_link` without a leading `www.`. Read from the link on every save. |
| `affiliate_link` | string | The full `http` or `https` URL. The plugin adds its query string or path to links to this domain. |
| `keywords` | array of strings, or `null` | The auto-linking keywords. `null` when none was ever saved. The create response leaves the field out. |
| `enable_autolink` | boolean | Whether auto-linking uses this partner. |
| `autolink_priority` | integer | `0` to `100`. Default `10`. |
| `attributes` | object, or `null` | The `rel` flags for links to this partner: `nofollow`, `sponsored`, `ugc`, `noopener`, `noreferrer`. Each value is `1` or `0`. |
| `utm_params` | object, or `null` | The UTM values the plugin adds. Any of `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`. `null` when none is set. |
| `redirect_type` | integer | `301`, `302` or `307`. Default `301`. |
| `click_count` | integer | A stored counter column. Default `0`. It counts clicks on short links to the domain, never Simple Redirect hits, and a backup restore recomputes it the same way. The create response leaves it out. |
| `created_at` | string | When the partner was created, as `Y-m-d\TH:i:s.v`. |
| `updated_at` | string | When the partner was last saved, in the same format. |

Rows in the list also carry these fields:

| Field | Type | What it holds |
|---|---|---|
| `_autolink_terms` | array of integers | The WordPress term IDs that limit where auto-linking applies. Empty means everywhere. |
| `link_count` | integer | Short links whose destination is on the partner's domain. |
| `clicks` | integer | All recorded clicks on those links. |
| `unique_clicks` | integer | The unique clicks among them. |
| `keyword_count` | integer | The number of keywords. |
| `autolink` | boolean | The same value as `enable_autolink`. |
| `tag` | string | The part the plugin adds for the partner: the query string with a leading `?`, or else the path. An empty string for a bare domain, or when the URL ends in `?` with no query. |

## Affiliates

### List affiliates

`GET /affiliates`

Returns one page of affiliates with their link and click counts, plus totals for the auto-link filter.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `search` | string | No | none | Matches part of the site name, domain, affiliate link or a keyword. |
| `autolink` | string | No | none | `on` or `off` shows only partners with auto-linking on or off. Any other value is ignored. |
| `page` | integer | No | `1` | The page number. |
| `per_page` | integer | No | `15` | Partners per page. |

**Response:** `200`. `data` is a page object ([Pagination](https://docs.linkmanagerpro.com/developers/rest-api/#pagination)) with one extra field.

| Field | Type | What it holds |
|---|---|---|
| `data` | array | The partners, each as in [the affiliate object](#the-affiliate-object), with the list fields. |
| `counts` | object | `all`, `on` and `off`: partner counts after `search` and before `autolink`. |
| `import_path_tags` | object | Partners an import tagged with a path instead of a query tag. `count` (integer) is how many domains, and `domains` (array of strings) names at most 5 of them. |

There is no sort parameter, and the code sets no order.

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates?autolink=on"
```

```json
{
  "success": true,
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": 3,
        "site_name": "Example Shop",
        "domain": "shop.example.com",
        "affiliate_link": "https://shop.example.com/?tag=mysite-20",
        "keywords": ["blue widget"],
        "enable_autolink": true,
        "autolink_priority": 10,
        "attributes": { "nofollow": 1, "sponsored": 1 },
        "utm_params": null,
        "redirect_type": 301,
        "click_count": 0,
        "created_at": "2026-10-01T09:00:00.000",
        "updated_at": "2026-10-01T09:00:00.000",
        "_autolink_terms": [],
        "link_count": 4,
        "clicks": 57,
        "unique_clicks": 44,
        "keyword_count": 1,
        "autolink": true,
        "tag": "?tag=mysite-20"
      }
    ],
    "first_page_url": "/wp-json/link-manager-pro/v1/affiliates?page=1",
    "from": 1,
    "last_page": 1,
    "last_page_url": "/wp-json/link-manager-pro/v1/affiliates?page=1",
    "links": [
      { "url": null, "label": "Previous", "active": false },
      { "url": "/wp-json/link-manager-pro/v1/affiliates?page=1", "label": 1, "active": true },
      { "url": null, "label": "Next", "active": false }
    ],
    "next_page_url": null,
    "path": "/wp-json/link-manager-pro/v1/affiliates",
    "per_page": 15,
    "prev_page_url": null,
    "to": 1,
    "total": 1,
    "counts": { "all": 1, "on": 1, "off": 0 },
    "import_path_tags": { "count": 0, "domains": [] }
  }
}
```

**Errors:** None beyond the overview's sign-in and permission errors.

### Create an affiliate

`POST /affiliates`

Creates an affiliate partner. The plugin reads the domain from `affiliate_link` and makes sure a short link for that URL exists.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Body parameters:** JSON.

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `site_name` | string | Yes | none | The partner's name. 100 characters or fewer. |
| `affiliate_link` | string | Yes | none | A full `http` or `https` URL with a host, 2048 bytes or fewer. It sets the domain and the tag the plugin adds. |
| `enable_autolink` | string | Yes | none | `yes` or `no`. Stored as a boolean. |
| `attributes` | object | Yes | none | At least one key. Each value is `yes` or `no`. Only `nofollow`, `sponsored`, `ugc`, `noopener` and `noreferrer` are kept. Stored as `1` or `0`. |
| `redirect_type` | integer | Yes | none | `301`, `302` or `307`. |
| `keywords` | array or string | No | none | An array, or a comma-separated string. Duplicates are removed, ignoring case. The first spelling stays. |
| `autolink_priority` | integer | No | `10` | `0` to `100`. A number outside the range is a `422` on this field. |
| `utm_params` | object | No | none | Any of `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`. Each 200 characters or fewer. Empty values are dropped. |
| `autolink_terms` | array of integers | No | none | WordPress term IDs that limit where auto-linking applies. An empty array removes the limit. Without the field, the stored terms stay. |

**Response:** `200`, with `message` set to "Affiliate created." `data` holds:

| Field | Type | What it holds |
|---|---|---|
| `affiliate` | object | The stored fields from [the affiliate object](#the-affiliate-object). It has no `click_count`, and no `keywords` when the request sent none. |

```bash
curl -X POST -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{
    "site_name": "Example Shop",
    "affiliate_link": "https://shop.example.com/?tag=mysite-20",
    "enable_autolink": "yes",
    "attributes": { "nofollow": "yes", "sponsored": "yes" },
    "redirect_type": 301,
    "keywords": ["blue widget"]
  }' \
  https://example.com/wp-json/link-manager-pro/v1/affiliates
```

```json
{
  "success": true,
  "data": {
    "affiliate": {
      "site_name": "Example Shop",
      "affiliate_link": "https://shop.example.com/?tag=mysite-20",
      "enable_autolink": true,
      "attributes": { "nofollow": 1, "sponsored": 1 },
      "redirect_type": 301,
      "autolink_priority": 10,
      "utm_params": null,
      "keywords": ["blue widget"],
      "domain": "shop.example.com",
      "updated_at": "2026-10-23T09:20:00.000",
      "created_at": "2026-10-23T09:20:00.000",
      "id": 3
    }
  },
  "message": "Affiliate created."
}
```

**Errors:**

| Status | When |
|---|---|
| `422` | `errors.affiliate_link`: the URL is not a full `http` or `https` URL with a host ("Enter a full URL, starting with https://"), or its domain already has a partner ("shop.example.com is already an affiliate (Example Shop). Edit that affiliate instead."). |
| `422` | `errors.site_name`, `errors.enable_autolink`, `errors.attributes` or `errors.redirect_type`: the field is missing or has a value outside the rules above. |
| `422` | `errors.autolink_priority`: not a number from `0` to `100`. |
| `422` | `site_name` or a `utm_params.*` value is longer than its limit. |

### Update an affiliate

`PUT /affiliates/{id}`

Saves a partner and returns it. The body needs the same required fields as create.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Body parameters:** JSON. The same fields as [Create an affiliate](#create-an-affiliate), with three differences. A missing `keywords` leaves the stored keywords as they are. A missing `utm_params` clears them. A missing `autolink_priority` becomes `10`. A new `affiliate_link` on another domain changes the domain.

**Response:** `200`, with `message` set to "Affiliate saved." `data.affiliate` holds every stored field of [the affiliate object](#the-affiliate-object), including `click_count`.

```bash
curl -X PUT -u USER:APPLICATION_PASSWORD \
  -H "Content-Type: application/json" \
  -d '{
    "site_name": "Example Shop",
    "affiliate_link": "https://shop.example.com/?tag=mysite-21",
    "enable_autolink": "yes",
    "attributes": { "nofollow": "yes", "sponsored": "yes" },
    "redirect_type": 302
  }' \
  https://example.com/wp-json/link-manager-pro/v1/affiliates/3
```

```json
{
  "success": true,
  "data": {
    "affiliate": {
      "id": 3,
      "site_name": "Example Shop",
      "domain": "shop.example.com",
      "affiliate_link": "https://shop.example.com/?tag=mysite-21",
      "keywords": ["blue widget"],
      "enable_autolink": true,
      "autolink_priority": 10,
      "attributes": { "nofollow": 1, "sponsored": 1 },
      "utm_params": null,
      "redirect_type": 302,
      "click_count": 0,
      "created_at": "2026-10-01T09:00:00.000",
      "updated_at": "2026-10-23T09:25:00.000"
    }
  },
  "message": "Affiliate saved."
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |
| `422` | The same field errors as create. A domain counts as taken only when another partner holds it. |

### Delete an affiliate

`DELETE /affiliates/{id}`

Deletes the partner and its auto-link targeting. Short links to that domain stay.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Response:** `200`, with `message` set to "Affiliate deleted." `data` is `null`.

```bash
curl -X DELETE -u USER:APPLICATION_PASSWORD \
  https://example.com/wp-json/link-manager-pro/v1/affiliates/3
```

```json
{
  "success": true,
  "data": null,
  "message": "Affiliate deleted."
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

## Affiliate reports

The report routes read recorded clicks on the partner's short links. Dates are site-time days written as `Y-m-d`. A value that is not a real date counts as missing.

### Get an affiliate report

`GET /affiliates/{id}/report`

Returns click totals, breakdowns and the 20 newest clicks for one partner, plus the partner's settings and auto-link status.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `start` | string | No | none | First day, `Y-m-d`. Without it the range has no start. |
| `end` | string | No | none | Last day, `Y-m-d`. The whole day counts. Without it the range has no end. |

With neither parameter, the report covers all clicks. The plugin may serve a saved copy of the click part for a short time, so new clicks can take a moment to show.

**Response:** `200`. `data` holds:

| Field | Type | What it holds |
|---|---|---|
| `entity` | object | `type` (`"affiliate"`), `id` (integer), `name` (the site name), `domain` and `url` (the affiliate link). |
| `summary` | object | `clicks`, `unique_clicks` and `qualified_clicks` (integers), `conversion_label` (the string `"Qualified Clicks"`), `first_click_at` and `last_click_at` (UTC `Y-m-d H:i:s` strings, `null` before the first click). Qualified clicks come from a post on this site. |
| `breakdowns` | object | Click counts by group, described in the next table. |
| `recent` | array | The 20 newest clicks in the range, each a [click row](#click-rows). |
| `meta` | object | `matched_links`: the number of short links counted for this partner. |
| `partner` | object | The partner's settings, as the next table shows. |
| `link_count` | integer | Short links counted for this partner. |
| `last_click_at` | string or `null` | The newest click of all time, UTC. Ignores `start` and `end`. |
| `autolink` | object | How much auto-linking sends, as the table below shows. |
| `posts_count` | integer | The number of different posts that sent a click, all time. |
| `tracking` | object | The tracking flags, as the table below shows. |

`breakdowns` holds these keys. Every list is sorted by count, highest first.

| Key | Type | What it holds |
|---|---|---|
| `device`, `os`, `browser`, `country`, `referrer` | array | Rows of `label` (string) and `count` (integer). At most 10 rows for `country` and `referrer`. An unknown country is `Unknown`, and no referrer is `Direct`. |
| `utm` | object | `source`, `medium` and `campaign`: top 10 rows of `label` and `count`. `totals` holds the full tagged-click count for each. |
| `source` | object | `content`, `site` (another page of this site), `referral` and `direct`: counts that add up to all clicks. |
| `posts` | array | Top 10 posts: `post_id`, `label` (the title, or `Post #id`) and `count`. |
| `sites` | array | Top 10 referrer domains for clicks that did not come from a post. This site's own host is left out. |

`partner` holds:

| Field | Type | What it holds |
|---|---|---|
| `id`, `site_name`, `domain`, `affiliate_link` | integer, string | The same values as [the affiliate object](#the-affiliate-object). |
| `tag` | string | The added query string or path, as in the list rows. |
| `created_at` | string or `null` | Creation time, UTC `Y-m-d H:i:s`. |
| `redirect_type`, `autolink_priority` | integer | As in the affiliate object. |
| `enable_autolink` | boolean | As in the affiliate object. |
| `attributes`, `utm_params` | object | The saved values. An empty object `{}` when none. |
| `keywords` | array of strings | The cleaned keyword list. |
| `categories` | array | `{ "id": 4, "name": "Tech" }` for each targeted term that still exists. Both categories and tags appear. |

`autolink` holds:

| Field | Type | What it holds |
|---|---|---|
| `short_link_id` | integer or `null` | The short link for the affiliate link URL, the one auto-linking uses. `null` when none exists. |
| `clicks` | integer | All-time clicks on that link. |
| `share` | integer or `null` | The percentage of all-time partner clicks that came through it, rounded. `null` with no clicks. |
| `ready` | boolean | `true` when auto-linking is on, the partner has keywords and the short link exists. |

`tracking` holds:

| Field | Type | What it holds |
|---|---|---|
| `tracking_enabled` | boolean | Whether click tracking is on. |
| `retention_days` | integer | Days clicks are kept. `0` keeps them all. |
| `autolink_site_enabled` | boolean | Whether auto-linking is on for the site. |
| `history_trusted` | boolean | Whether the last 90 days of clicks were fully recorded. |
| `history_reason` | string | Why not: `tracking_off`, `retention_short`, `tracking_resumed`, `retention_changed`, `clicks_restored` or `gap`. An empty string when trusted. |

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates/3/report?start=2026-10-01&end=2026-10-23"
```

```json
{
  "success": true,
  "data": {
    "entity": {
      "type": "affiliate",
      "id": 3,
      "name": "Example Shop",
      "domain": "shop.example.com",
      "url": "https://shop.example.com/?tag=mysite-20"
    },
    "summary": {
      "clicks": 57,
      "unique_clicks": 44,
      "qualified_clicks": 30,
      "conversion_label": "Qualified Clicks",
      "first_click_at": "2026-10-02 07:11:40",
      "last_click_at": "2026-10-22 18:03:09"
    },
    "breakdowns": {
      "device": [{ "label": "Desktop", "count": 40 }],
      "os": [{ "label": "Windows", "count": 22 }],
      "browser": [{ "label": "Chrome", "count": 35 }],
      "country": [{ "label": "US", "count": 31 }],
      "referrer": [{ "label": "Direct", "count": 20 }],
      "utm": {
        "source": [{ "label": "newsletter", "count": 12 }],
        "medium": [],
        "campaign": [],
        "totals": { "source": 12, "medium": 0, "campaign": 0 }
      },
      "source": { "content": 30, "site": 2, "referral": 5, "direct": 20 },
      "posts": [{ "post_id": 18, "label": "Best widgets", "count": 21 }],
      "sites": [{ "label": "news.example.org", "count": 5 }]
    },
    "recent": [
      {
        "clicked_at": "2026-10-22 18:03:09",
        "is_unique": true,
        "post_id": 18,
        "device": "Desktop",
        "os": "Windows",
        "referrer_domain": null,
        "referrer_url": null,
        "browser": "Chrome",
        "country_code": "US",
        "post_title": "Best widgets"
      }
    ],
    "meta": { "matched_links": 4 },
    "partner": {
      "id": 3,
      "site_name": "Example Shop",
      "domain": "shop.example.com",
      "tag": "?tag=mysite-20",
      "affiliate_link": "https://shop.example.com/?tag=mysite-20",
      "created_at": "2026-10-01 09:00:00",
      "redirect_type": 301,
      "attributes": { "nofollow": 1, "sponsored": 1 },
      "utm_params": {},
      "enable_autolink": true,
      "autolink_priority": 10,
      "keywords": ["blue widget"],
      "categories": [{ "id": 4, "name": "Tech" }]
    },
    "link_count": 4,
    "last_click_at": "2026-10-22 18:03:09",
    "autolink": { "short_link_id": 61, "clicks": 25, "share": 44, "ready": true },
    "posts_count": 6,
    "tracking": {
      "tracking_enabled": true,
      "retention_days": 0,
      "autolink_site_enabled": true,
      "history_trusted": true,
      "history_reason": ""
    }
  }
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

#### Click rows

A click row appears in `recent` of the report and in the rows of [Get recent affiliate clicks](#get-recent-affiliate-clicks).

| Field | Type | What it holds |
|---|---|---|
| `clicked_at` | string | When the click happened, UTC. |
| `is_unique` | boolean | Whether it counted as a unique click. |
| `post_id` | integer | The post the click came from. `0` when none. |
| `device`, `os`, `browser` | string | Read from the visitor's user agent. |
| `referrer_domain` | string or `null` | The referring host. |
| `referrer_url` | string or `null` | The referring page. |
| `country_code` | string or `null` | A two-letter country code. |
| `post_title` | string or `null` | The post's title, or `Post #id`. `null` when `post_id` is `0`. |

### Get recent affiliate clicks

`GET /affiliates/{id}/report/recent`

Returns one page of the partner's clicks, newest first, for a "recent activity" list.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `page` | integer | No | `1` | The page number. |
| `per_page` | integer | No | `10` | Clicks per page. A value below `1` counts as `1`, and a value above `100` counts as `100`. |

**Response:** `200`. `data` is a page object ([Pagination](https://docs.linkmanagerpro.com/developers/rest-api/#pagination)). `data.data` holds [click rows](#click-rows), ordered by `clicked_at`, newest first.

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates/3/report/recent?per_page=10"
```

```json
{
  "success": true,
  "data": {
    "current_page": 1,
    "data": [
      {
        "clicked_at": "2026-10-22 18:03:09",
        "is_unique": true,
        "post_id": 18,
        "device": "Desktop",
        "os": "Windows",
        "referrer_domain": null,
        "referrer_url": null,
        "browser": "Chrome",
        "country_code": "US",
        "post_title": "Best widgets"
      }
    ],
    "from": 1,
    "last_page": 6,
    "per_page": 10,
    "to": 10,
    "total": 57
  }
}
```

The page object also holds the link fields shown under [Pagination](https://docs.linkmanagerpro.com/developers/rest-api/#pagination). The example leaves them out.

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

### Export an affiliate report as CSV

`GET /affiliates/{id}/report/export`

Downloads every recorded click on the partner's short links as a CSV file, newest first. It is the "Download CSV" action of the partner report.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `start` | string | No | none | First day, `Y-m-d` in site time. Without it the export has no start. |
| `end` | string | No | none | Last day, `Y-m-d`. The whole day counts. Without it the export has no end. |

**Response:** `200`. The body is CSV, not JSON, with no envelope. The headers are:

| Header | Value |
|---|---|
| `Content-Type` | `text/csv; charset=utf-8` |
| `Content-Disposition` | `attachment; filename="link-manager-pro-report-{domain}-{Y-m-d}.csv"`. The date is today in site time. |
| `Cache-Control` | `no-cache, no-store, must-revalidate` |

The first row is a header row. Each later row is one click. The file has these columns, in this order:

| Column | What it holds |
|---|---|
| `Clicked At` | The click time in site time, `Y-m-d H:i:s`. |
| `Link Slug` | The slug of the short link that was clicked. |
| `Target URL` | The destination recorded for the click. |
| `Country` | A two-letter country code, or empty. |
| `Referrer Domain` | The referring host, or empty. |
| `Referrer URL` | The referring page, or empty. |
| `Device` | Desktop, mobile or another device type, from the user agent. |
| `OS` | The operating system family. |
| `Browser` | The browser family. |
| `Unique` | `yes` or `no`. |
| `Post ID` | The post the click came from, or empty. |
| `UTM Source`, `UTM Medium`, `UTM Campaign`, `UTM Term`, `UTM Content` | The UTM values recorded for the click, or empty. |

A cell that starts with `=`, `+`, `-`, `@`, a tab or a carriage return gets a leading single quote, so a spreadsheet does not run it as a formula.

```bash
curl -u USER:APPLICATION_PASSWORD -o report.csv \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates/3/report/export?start=2026-10-01&end=2026-10-23"
```

```text
Clicked At,"Link Slug","Target URL",Country,"Referrer Domain","Referrer URL",Device,OS,Browser,Unique,"Post ID","UTM Source","UTM Medium","UTM Campaign","UTM Term","UTM Content"
"2026-10-22 18:03:09",blue-widget,"https://shop.example.com/?tag=mysite-20",US,,,Desktop,Windows,Chrome,yes,18,newsletter,,,,
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. The body is the JSON `not_found` error. |

### Get affiliate clicks over time

`GET /affiliates/{id}/clicks`

Returns one row per day with the total and unique clicks on the partner's short links, for a chart.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `start` | string | No | see below | First day, `Y-m-d` in site time. |
| `end` | string | No | today | Last day, `Y-m-d`. Never later than today. |

With neither parameter, the range runs from the earlier of the partner's creation day and its first click, up to today. With only `end`, it covers the 30 days ending on that day. A reversed range is swapped. A range is capped at 7305 days. Every day has a row, with zeros when there were no clicks.

**Response:** `200`. `data` is an array of days:

| Field | Type | What it holds |
|---|---|---|
| `date` | string | The day, `Y-m-d`, in site time. |
| `total` | integer | Clicks that day. |
| `unique` | integer | Unique clicks that day. |

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates/3/clicks?start=2026-10-21&end=2026-10-23"
```

```json
{
  "success": true,
  "data": [
    { "date": "2026-10-21", "total": 4, "unique": 3 },
    { "date": "2026-10-22", "total": 9, "unique": 7 },
    { "date": "2026-10-23", "total": 0, "unique": 0 }
  ]
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

### List posts sending affiliate clicks

`GET /affiliates/{id}/posts`

Returns one page of the posts whose content sent clicks to the partner, most clicks first, all time.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `page` | integer | No | `1` | The page number. A value below `1` counts as `1`. |
| `per_page` | integer | No | `10` | Posts per page. Kept between `1` and `100`. |

**Response:** `200`. `data` is a small page object, not the standard one:

| Field | Type | What it holds |
|---|---|---|
| `data` | array | The posts, as the next table shows. |
| `total` | integer | The number of different posts. |
| `current_page` | integer | The page returned. |
| `per_page` | integer | The page size used. |
| `last_page` | integer | The last page number. At least `1`. |

Each post row holds:

| Field | Type | What it holds |
|---|---|---|
| `post_id` | integer | The post ID. |
| `title` | string | The post title, `Post #id` when it has none, or `(deleted post)` when the post is gone. |
| `url` | string or `null` | The post's permalink. `null` when the post is gone. |
| `categories` | array of strings | Up to two category names. |
| `clicks` | integer | All clicks from that post to the partner. |
| `auto_clicks` | integer | Clicks through the partner's auto-link short link. |
| `manual_clicks` | integer | The other clicks: `clicks` minus `auto_clicks`. |
| `auto_links` | integer | `1` when the post sent clicks through the auto-link short link, else `0`. |
| `manual_links` | integer | The number of other short links the post sent clicks through. |

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates/3/posts?per_page=10"
```

```json
{
  "success": true,
  "data": {
    "data": [
      {
        "post_id": 18,
        "title": "Best widgets",
        "url": "https://example.com/best-widgets/",
        "categories": ["Reviews"],
        "clicks": 21,
        "auto_clicks": 15,
        "manual_clicks": 6,
        "auto_links": 1,
        "manual_links": 1
      }
    ],
    "total": 6,
    "current_page": 1,
    "per_page": 10,
    "last_page": 1
  }
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

### Get affiliate keywords

`GET /affiliates/{id}/keywords`

Returns the partner's auto-linking keywords with the number of published posts that contain each one, and whether auto-linking is ready.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:** None.

**Response:** `200`. `data` holds:

| Field | Type | What it holds |
|---|---|---|
| `enabled` | boolean | Whether auto-linking is on for this partner. |
| `site_enabled` | boolean | Whether auto-linking is on for the whole site. |
| `priority` | integer | The partner's auto-link priority. |
| `max_per_keyword` | integer | The site setting for links per keyword in a post. At least `1`. |
| `keywords` | array | One row per keyword: `keyword` (string) and `posts` (integer, or `null`). |
| `categories` | array of strings | The names of the targeted categories and tags. Empty means everywhere. |
| `ready` | boolean | `true` when `reason` is empty. |
| `reason` | string | Why keywords are not linking: `off`, `no_keywords`, `site_off` or `no_short_link`. An empty string when ready. |

`posts` counts published posts of the auto-linked post types that contain the keyword as a whole word, with tags stripped. On a very large site (over 1,000 candidate posts for a keyword, or over 5,000 posts read in one count) it falls back to a plain text-contains count. It is "found in", not "linked in". Only the first 50 keywords are counted, and later ones have `posts` set to `null`. The counts are saved for an hour, and a save of the partner clears them.

```bash
curl -u USER:APPLICATION_PASSWORD \
  https://example.com/wp-json/link-manager-pro/v1/affiliates/3/keywords
```

```json
{
  "success": true,
  "data": {
    "enabled": true,
    "site_enabled": true,
    "priority": 10,
    "max_per_keyword": 1,
    "keywords": [{ "keyword": "blue widget", "posts": 12 }],
    "categories": ["Tech"],
    "ready": true,
    "reason": ""
  }
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

### List short links to an affiliate

`GET /affiliates/{id}/links`

Returns one page of the short links counted for the partner, most recorded clicks first.

**Permission:** `link_manager_pro_access` ([Permissions](https://docs.linkmanagerpro.com/developers/rest-api/#permissions))

**Path parameters:**

| Name | Type | What it is |
|---|---|---|
| `id` | integer | The partner ID. |

**Query parameters:**

| Name | Type | Required | Default | What it does |
|---|---|---|---|---|
| `page` | integer | No | `1` | The page number. A value below `1` counts as `1`. |
| `per_page` | integer | No | `10` | Links per page. Kept between `1` and `100`. |

**Response:** `200`. `data` is the same small page object as in [List posts sending affiliate clicks](#list-posts-sending-affiliate-clicks). `data.data` holds:

| Field | Type | What it holds |
|---|---|---|
| `id` | integer | The short link ID. |
| `slug` | string | The slug. |
| `short_url` | string | The public address of the link. When **Affiliate link prefix** is set, it uses that prefix, because the destination is on the partner's domain. |
| `target_url` | string | The destination, on the partner's domain. |
| `clicks` | integer | Recorded clicks on the link. These add up to the partner's clicks. |
| `click_count` | integer | The link's own lifetime counter. |
| `made_by` | string | `autolink` for the short link that auto-linking uses, otherwise `manual`. |

With no matching links, `data.data` is empty, `total` is `0` and `last_page` is `1`.

```bash
curl -u USER:APPLICATION_PASSWORD \
  "https://example.com/wp-json/link-manager-pro/v1/affiliates/3/links?per_page=10"
```

```json
{
  "success": true,
  "data": {
    "data": [
      {
        "id": 61,
        "slug": "example-shop",
        "short_url": "https://example.com/link/example-shop",
        "target_url": "https://shop.example.com/?tag=mysite-20",
        "clicks": 25,
        "click_count": 25,
        "made_by": "autolink"
      }
    ],
    "total": 4,
    "current_page": 1,
    "per_page": 10,
    "last_page": 1
  }
}
```

**Errors:**

| Status | When |
|---|---|
| `404` | No partner has that ID. |

## Related

- [REST API overview](https://docs.linkmanagerpro.com/developers/rest-api/)
- [Short links endpoints](https://docs.linkmanagerpro.com/developers/rest-api/links)
- [Redirects endpoints](https://docs.linkmanagerpro.com/developers/rest-api/redirects)
- [Database tables](https://docs.linkmanagerpro.com/developers/data/tables)
- [Affiliates](https://docs.linkmanagerpro.com/guides/affiliates)
