# API v3

## API v3

SparkLoop's v3 API is organized around your **publications**: most resources are scoped to a publication (`/v3/publications/:publication_uuid/...`), while a few — your account, payouts, and webhooks — are scoped to the whole account.

{% hint style="info" %}
The v3 API returns richer, self-describing payloads. Related objects are referenced by UUID and can be inlined on demand with `expand`, and most performance figures are available inline via `expand=stats`.
{% endhint %}

{% hint style="info" %}
**Building with an LLM or AI agent?** A plain-text index of the entire v3 API — every endpoint with a one-line description and a direct link — is published at [api.sparkloop.app/llms.txt](https://api.sparkloop.app/llms.txt), following the [llms.txt convention](https://llmstxt.org). Point your tooling there for a compact map of the whole API surface.
{% endhint %}

### Base URL

```
https://api.sparkloop.app/v3
```

### Authentication

Send your API key in the `x-api-key` header on every request. All requests act as the team that owns the key.

```bash
curl https://api.sparkloop.app/v3/account \
  -H "x-api-key: YOUR_API_KEY"
```

| Status | Body                                 | When                   |
| ------ | ------------------------------------ | ---------------------- |
| `401`  | `{ "error": "API key is missing!" }` | No `x-api-key` header. |
| `401`  | `{ "error": "Account not found!" }`  | Unknown API key.       |
| `401`  | `{ "error": "API key is invalid!" }` | Invalidated API key.   |

### Pagination

List endpoints are paginated and return a `meta` block alongside the results.

| Parameter  | In    | Type    | Description                                          |
| ---------- | ----- | ------- | ---------------------------------------------------- |
| `page`     | query | integer | Page number. Defaults to `1`.                        |
| `per_page` | query | integer | Results per page. Defaults to `50`, capped at `200`. |

The `meta` block, where `total_<resource>` is named after the listed resource (e.g. `total_publications`, `total_surveys`):

```json
{
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 4,
    "total_<resource>": 187
  }
}
```

### Expanding objects

Related objects are returned as a UUID string by default. Pass `expand` with a comma-separated list of fields to inline the full object instead.

| Parameter | In    | Type   | Description                                                                    |
| --------- | ----- | ------ | ------------------------------------------------------------------------------ |
| `expand`  | query | string | Comma-separated fields to expand, e.g. `expand=recommended_publication,stats`. |

Each endpoint lists the fields it supports expanding.

### Stats & date range

Performance metrics are opt-in via `expand=stats`. When requested, they cover a date window you control:

| Parameter | In    | Type   | Description                                         |
| --------- | ----- | ------ | --------------------------------------------------- |
| `from`    | query | string | Start date (`YYYY-MM-DD`). Defaults to 30 days ago. |
| `to`      | query | string | End date (`YYYY-MM-DD`). Defaults to today.         |

`from`/`to` only narrow the `stats` window, so they require `expand=stats` — passing them without it returns `400`.

### Rate limits

Each API key may make **120 requests per minute**. The limit is shared by every request sent with the same key, whatever the endpoint.

Once you go over it, requests return `429` until the window resets. Successful responses carry no rate-limit headers; the `429` response does:

| Header                | Meaning                                              |
| --------------------- | ---------------------------------------------------- |
| `ratelimit-limit`     | Requests allowed per minute (`120`).                 |
| `ratelimit-remaining` | Always `0` on a `429`.                               |
| `ratelimit-reset`     | Unix timestamp (seconds) at which the window resets. |
| `retry-after`         | Seconds to wait before retrying.                     |

The `429` body is plain text rather than the JSON error shape used elsewhere:

```
You have been rate limited. Please try again in 60 seconds.
```

Wait for `retry-after` seconds before retrying, and spread requests out instead of sending them in bursts.

### Errors

Every error except `429` uses the same shape:

```json
{ "error": "Resource not found." }
```

| Status | Meaning                                                                                        |
| ------ | ---------------------------------------------------------------------------------------------- |
| `400`  | Bad request — an unsupported or malformed parameter.                                           |
| `401`  | Authentication failed — missing, unknown, or invalid API key.                                  |
| `404`  | The resource doesn't exist, or isn't visible to your team.                                     |
| `422`  | The request was understood but couldn't be processed.                                          |
| `429`  | Rate limit exceeded — wait `retry-after` seconds, then retry. See [Rate limits](#rate-limits). |


# Endpoints

## Endpoints

The v3 endpoints. **Account**, **Payouts**, and **Webhooks** are team-scoped; everything under **Publications** — its **Grow**, **Earn**, and **Tools** groups, plus **Earnings** — is scoped to a publication.

Responses reference the shared object shapes listed on the **Objects Schema** page.


# Account

## Account

Your **account** is the team that owns the API key — a single endpoint returning the team's name, ESP, and owner. There's nothing to look up: the account is always the team the key authenticates as.

### Get account

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/account`

**Query Parameters**

| Name   | Type   | Description                                                   |
| ------ | ------ | ------------------------------------------------------------- |
| expand | String | Comma-separated objects to inline. Supported: `team_members`. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "account": <Account Object>
}
```

{% endtab %}

{% tab title="401: Unauthorized" %}

```json
{
  "error": "API key is invalid!"
}
```

{% endtab %}
{% endtabs %}

#### Example: expand the team's members

```bash
curl "https://api.sparkloop.app/v3/account?expand=team_members" \
  -H "x-api-key: YOUR_API_KEY"
```


# Publications

## Publications

A **Publication** is the central resource on your account — the unit most other v3 resources hang off (recommendations, signup flow, and partner program are all scoped to a publication). Use this resource to list your account's publications, or fetch a single one by UUID.

### List publications

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications`

Paginated — accepts `page` and `per_page`. Newest first.

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "publications": [
    <Publication Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_publications": 3
  }
}
```

{% endtab %}
{% endtabs %}

### Get publication

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:uuid`

**Path Parameters**

| Name                                   | Type   | Description             |
| -------------------------------------- | ------ | ----------------------- |
| uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "publication": <Publication Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Earnings

## Earnings

Cross-cutting **earnings** aggregation for a publication, across its recommendations and offers. Earnings combine paid recommendation-referral commission (confirmed plus a confirmation-rate-estimated pending amount) and confirmed offer-conversion commission. Every figure is in US dollars.

The report takes one of three shapes (it never mixes them), controlled by `granularity` and `group_by`:

* `granularity=total`, no `group_by` — one overall figure.
* `granularity=total` with `group_by` — one figure per group, ranked highest-first.
* `granularity=daily`/`weekly`/`monthly` — one figure per time bucket.

### Get earnings

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earnings`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name        | Type   | Description                                                                                                                                                          |
| ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| from        | String | Start date (`YYYY-MM-DD`). Defaults to 30 days ago.                                                                                                                  |
| to          | String | End date (`YYYY-MM-DD`). Defaults to today.                                                                                                                          |
| channel     | String | Restrict to a single referral channel (e.g. `signup_flow`, `recommendations_hub`). Unsupported values return `400` with the list of valid channels.                  |
| group\_by   | String | `utm_campaign`, `recommendation`, or `offer`. Only valid with `granularity=total`.                                                                                   |
| granularity | String | `total` (default), `daily`, `weekly`, or `monthly`.                                                                                                                  |
| expand      | String | Inline the promoted thing on each grouped row instead of a bare uuid: `recommended_publication` (with `group_by=recommendation`) or `offer` (with `group_by=offer`). |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "earnings": <Earnings Report Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "`group_by` is only supported with `granularity=total`."
}
```

{% endtab %}
{% endtabs %}


# Grow

## Grow

Publication-facing **Grow** resources: free recommendations (sending and receiving) and the free directory, the publication's partner program, and its brand offers. All are scoped to a publication under `/v3/publications/:publication_uuid/grow`.


# Free Recommendations

## Free Recommendations

A **free recommendation** is an organic newsletter partnership with no payment (the Grow side). Unlike paid recommendations, the same campaign can be **sending** (this publication recommends the other) or **receiving** (the other recommends this one). This resource unions both sides.

Use it to list free recommendations, fetch a single one by UUID, archive, or re-activate. Filter the list with `role=sending` or `role=receiving`; omit `role` for both. Each row includes `role` relative to this publication, plus `recommending_publication` next to `recommended_publication`. To browse newsletters available to recommend — and to **add a recommendation** or **send a partnership request** — use the **Directory** at `grow/directory/free_recommendations`. Paid recommendations live under **Earn**.

### List free recommendations

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/free_recommendations`

Paginated. Omit `status` to list non-archived rows. Omit `role` to include both sending and receiving.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                                                                                |
| ------ | ------ | ---------------------------------------------------------------------------------------------------------- |
| status | String | Optional filter: `active`, `awaiting_approval`, or `archived`. Omit for all non-archived. Invalid → `400`. |
| role   | String | Optional filter: `sending` or `receiving`. Omit for both. Invalid → `400`.                                 |
| expand | String | `recommended_publication`, `recommending_publication`, `stats`.                                            |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                     |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                                                                       |

{% hint style="info" %}
`stats_grouped_by` is supported on **Get free recommendations stats** and **Get free recommendation**. On list it is ignored.
{% endhint %}

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendations": [
    <Recommendation Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_recommendations": 12
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `status`. Valid values: active, awaiting_approval, archived."
}
```

{% endtab %}
{% endtabs %}

### Get free recommendations stats

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/free_recommendations/stats`

Free-recommendation performance for the entire publication (dashboard Grow → Free Recommendations overview). Returns confirmed referrals sent/received and current active partnership counts. Does **not** require `expand=stats`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                                                                    |
| ------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| from                | String | Start date (`YYYY-MM-DD`). Defaults to 30 days ago.                                                                                                                                                            |
| to                  | String | End date (`YYYY-MM-DD`). Defaults to today.                                                                                                                                                                    |
| stats\_grouped\_by  | String | Optional. Only `country_code` is supported on this collection endpoint. Top 50 groups by combined referrals sent + received (receive-only countries are included). Blank/missing values appear under `"none"`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key `data` map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                                                         |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "stats": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "stats_grouped_by": null,
    "sending_count": 5,
    "receiving_count": 3,
    "referrals_sent": 342,
    "referrals_received": 518
  }
}
```

{% endtab %}

{% tab title="200: OK (grouped)" %}

```json
{
  "stats": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "stats_grouped_by": "country_code",
    "sending_count": 5,
    "receiving_count": 3,
    "data": {
      "US": { "referrals_sent": 200, "referrals_received": 310 },
      "GB": { "referrals_sent": 80, "referrals_received": 120 },
      "CA": { "referrals_sent": 0, "referrals_received": 45 },
      "none": { "referrals_sent": 62, "referrals_received": 43 }
    }
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: country_code."
}
```

{% endtab %}
{% endtabs %}

### Get free recommendation

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/free_recommendations/:uuid`

Fetch a single free recommendation this publication sends or receives.

**Path Parameters**

| Name                                                | Type   | Description                |
| --------------------------------------------------- | ------ | -------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID.    |
| uuid<mark style="color:red;">\*</mark>              | String | The recommendation's UUID. |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                                                         |
| ------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| expand              | String | `recommended_publication`, `recommending_publication`, `stats`.                                                                                                                                     |
| from                | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                              |
| to                  | String | End date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                                |
| stats\_grouped\_by  | String | With `expand=stats`, group the stats map by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by referral volume. Blank/missing values appear under `"none"`. Requires `expand=stats`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                                                     |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Archive a free recommendation

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/free_recommendations/:free_recommendation_uuid/archive`

Soft-archives the recommendation so it stops appearing in active lists. Sending archives as the partner; receiving archives as the sponsor. Reversible with **re-activate** when this publication archived it.

**Path Parameters**

| Name                                                         | Type   | Description                |
| ------------------------------------------------------------ | ------ | -------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark>          | String | The publication's UUID.    |
| free\_recommendation\_uuid<mark style="color:red;">\*</mark> | String | The recommendation's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Re-activate a free recommendation

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/free_recommendations/:free_recommendation_uuid/reactivation`

Restores a soft-archived free recommendation. Only works when **this publication** archived it.

**Path Parameters**

| Name                                                         | Type   | Description                |
| ------------------------------------------------------------ | ------ | -------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark>          | String | The publication's UUID.    |
| free\_recommendation\_uuid<mark style="color:red;">\*</mark> | String | The recommendation's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "This recommendation was archived by the other publication and can't be re-activated."
}
```

{% endtab %}
{% endtabs %}


# Directory

## Directory

Browse the Partner Network catalog of newsletters this publication can recommend for free, then act on an entry — recommend it, or request a mutual partnership. Scoped under `/v3/publications/:publication_uuid/grow/directory`.


# Free Recommendations

## Free Recommendations

The free-recommendations directory: the Partner Network catalog of newsletters this publication can recommend for free, plus the two actions taken from it — **recommend** one, or **request a partnership** (ask them to recommend you back).

### Browse the directory

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/directory/free_recommendations`

Paginated. Returns active newsletters opted into the directory, excluding this publication and any it has archived.

**Path Parameters**

| Name                                                | Type   | Description                                              |
| --------------------------------------------------- | ------ | -------------------------------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication a recommendation would be attributed to. |

**Query Parameters**

| Name       | Type   | Description                                                  |
| ---------- | ------ | ------------------------------------------------------------ |
| q          | String | Free-text search.                                            |
| categories | String | Comma-separated category names (matched case-insensitively). |
| language   | String | Filter by language.                                          |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendations": [
    <Free Recommendation Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 20,
    "total_recommendations": 964
  }
}
```

{% endtab %}
{% endtabs %}

### Get a directory entry

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/directory/free_recommendations/:uuid`

On a single entry the object also carries the per-caller `already_recommended` and `partnership_request_pending` flags.

**Path Parameters**

| Name                                                | Type   | Description                       |
| --------------------------------------------------- | ------ | --------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The viewing publication's UUID.   |
| uuid<mark style="color:red;">\*</mark>              | String | The directory publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Free Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Add a free recommendation

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/directory/free_recommendations/:free_recommendation_uuid/recommend`

Starts recommending the target publication for free. Returns the created **Recommendation**.

**Path Parameters**

| Name                                                         | Type   | Description                          |
| ------------------------------------------------------------ | ------ | ------------------------------------ |
| publication\_uuid<mark style="color:red;">\*</mark>          | String | The recommending publication's UUID. |
| free\_recommendation\_uuid<mark style="color:red;">\*</mark> | String | The directory publication's UUID.    |

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}

### Request a partnership

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/directory/free_recommendations/:free_recommendation_uuid/partnership_request`

Asks the target publication to recommend this one back.

**Path Parameters**

| Name                                                         | Type   | Description                        |
| ------------------------------------------------------------ | ------ | ---------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark>          | String | The requesting publication's UUID. |
| free\_recommendation\_uuid<mark style="color:red;">\*</mark> | String | The invited publication's UUID.    |

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "partnership_request": <Partnership Request Object>
}
```

{% endtab %}
{% endtabs %}


# Partner Program

## Partner Program

A **Partner Program** is how a publication pays other newsletters to refer subscribers to it: it sets the per-referral commission, a monthly spend cap, an engagement screening window, and geographic restrictions. A publication has at most one partner program, so this is a singleton resource. Its partners, pending partner requests, and referrals are sub-resources.

### Get partner program

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                         |
| ------ | ------ | --------------------------------------------------- |
| expand | String | `stats` to include date-ranged program performance. |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.              |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "partner_program": <Partner Program Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Partners

## Partners

A **partner** of a publication's partner program is another newsletter that recommends it in exchange for a per-referral commission — the sponsor-side view of the same relationship a recommendation describes. Use this resource to list the program's partners, or fetch a single one by UUID. Partners awaiting approval are not included here (see **Partner Requests**).

### List partners

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program/partners`

Paginated.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name      | Type   | Description                               |
| --------- | ------ | ----------------------------------------- |
| order\_by | String | Rank partners the way the dashboard does. |
| expand    | String | `recommending_publication`, `stats`.      |
| from      | String | Start date (`YYYY-MM-DD`) for `stats`.    |
| to        | String | End date (`YYYY-MM-DD`) for `stats`.      |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "partners": [
    <Partner Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_partners": 27
  }
}
```

{% endtab %}
{% endtabs %}

### Get partner

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program/partners/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The partner's UUID.     |

**Query Parameters**

| Name   | Type   | Description                          |
| ------ | ------ | ------------------------------------ |
| expand | String | `recommending_publication`, `stats`. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "partner": <Partner Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Partner Requests

## Partner Requests

A **partner request** is a newsletter asking to join a publication's partner program — a partner relationship still awaiting the sponsor's approval (status `awaiting_approval`). It carries the applicant's pitch and, on request, their track record. Approving a request turns it into a **partner**.

### List partner requests

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program/partner_requests`

Paginated.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                         |
| ------ | ------ | --------------------------------------------------- |
| expand | String | `recommending_publication`, `partner_track_record`. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "partner_requests": [
    <Partner Request Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_partner_requests": 4
  }
}
```

{% endtab %}
{% endtabs %}

### Approve a partner request

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program/partner_requests/:partner_request_uuid/approval`

Approves the request, turning it into an active partner. Returns the resulting **Partner**.

**Path Parameters**

| Name                                                     | Type   | Description                 |
| -------------------------------------------------------- | ------ | --------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark>      | String | The publication's UUID.     |
| partner\_request\_uuid<mark style="color:red;">\*</mark> | String | The partner request's UUID. |

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "partner": <Partner Object>
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "This request could not be approved."
}
```

{% endtab %}
{% endtabs %}


# Referrals

## Partner Program Referrals

A **referral** is one subscriber a partner program received from a partner — a reader who subscribed to the sponsor through a partner's recommendation.

### List referrals

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program/referrals`

Paginated, newest first.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                                                       |
| ------ | ------ | --------------------------------------------------------------------------------- |
| expand | String | `recommending_partner` to inline the partner publication that drove the referral. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "referrals": [
    <Referral Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 9,
    "total_referrals": 412
  }
}
```

{% endtab %}
{% endtabs %}

### Get referral

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/partner_program/referrals/:uuid`

**Path Parameters**

| Name                                                | Type   | Description                               |
| --------------------------------------------------- | ------ | ----------------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID.                   |
| uuid<mark style="color:red;">\*</mark>              | String | The referred subscriber's UUID (`sub_…`). |

**Query Parameters**

| Name   | Type   | Description             |
| ------ | ------ | ----------------------- |
| expand | String | `recommending_partner`. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "referral": <Referral Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Offers

## Offers

An **Offer** is a promotion a publication creates as a brand — it funds the offer with a monthly budget and other newsletters recommend it to earn a commission. This resource is the brand/owner view of the offers a publication has created.

### List offers

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/offers`

Paginated.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                            |
| ------ | ------ | -------------------------------------- |
| expand | String | `stats` to include performance.        |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`. |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.   |

{% hint style="info" %}
`stats_grouped_by` is only supported on **Get offer**. On list it is ignored.
{% endhint %}

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offers": [
    <Offer Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_offers": 2
  }
}
```

{% endtab %}
{% endtabs %}

### Get offer

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/grow/offers/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The offer's UUID.       |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                                                           |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| expand              | String | `stats` to include performance.                                                                                                                                                                       |
| from                | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                                |
| to                  | String | End date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                                  |
| stats\_grouped\_by  | String | With `expand=stats`, group the stats map by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by impression volume. Blank/missing values appear under `"none"`. Requires `expand=stats`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                                                       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offer": <Offer Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Earn

## Earn

Publication-facing **Earn** resources: paid recommendations, the paid-recommendations and offers directories, and joined offers. All are scoped to a publication under `/v3/publications/:publication_uuid/earn`.


# Recommendations

## Recommendations

A **paid recommendation** is a partner-program-backed recommendation this publication earns a commission from (the Earn side). Use this resource to list the paid recommendations the publication is currently making, fetch a single one by UUID, archive, or re-activate. Browse and recommend new programs from the **Directory** sub-resource; free recommendations live under **Grow**.

### List recommendations

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/recommendations`

Paginated. Omit `status` to list non-archived rows (`active` + `awaiting_approval`).

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                                                                                                                                        |
| ------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| status | String | Optional filter: `active` (recommendable with budget), `budget_used_up`, `paused`, `awaiting_approval`, or `archived`. Omit for all non-archived. Invalid → `400`. |
| expand | String | `recommended_publication`, `stats`.                                                                                                                                |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                                                                             |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                                                                                                                               |

{% hint style="info" %}
`stats_grouped_by` is supported on **Get recommendations stats** and **Get recommendation**. On list it is ignored.
{% endhint %}

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendations": [
    <Recommendation Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_recommendations": 8
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `status`. Valid values: active, budget_used_up, paused, awaiting_approval, archived."
}
```

{% endtab %}
{% endtabs %}

### Get recommendations stats

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/recommendations/stats`

Paid-recommendation performance for the entire publication (dashboard Earn → Paid Recommendations overview). Returns referrals, earnings (confirmed + estimated pending), and channel breakdowns for signup flow and in-email campaigns. Does **not** require `expand=stats`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                             |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| from                | String | Start date (`YYYY-MM-DD`). Defaults to 30 days ago.                                                                                                                     |
| to                  | String | End date (`YYYY-MM-DD`). Defaults to today.                                                                                                                             |
| stats\_grouped\_by  | String | Optional. Group the full metric shape by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by referral volume. Blank/missing values appear under `"none"`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key `data` map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                  |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "stats": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "stats_grouped_by": null,
    "referrals": { "confirmed": 10, "pending": 4, "total": 14 },
    "earnings": { "confirmed": 50.0, "estimated_pending": 12.5, "total": 62.5 },
    "channels": {
      "signup_flow": {
        "earnings": { "confirmed": 30.0, "estimated_pending": 8.0, "total": 38.0 },
        "impressions": 800,
        "referrals": 9,
        "earnings_per_subscriber": 0.12
      },
      "campaigns": {
        "earnings": { "confirmed": 20.0, "estimated_pending": 4.5, "total": 24.5 },
        "clicks": 200,
        "referrals": 5,
        "average_earnings_per_campaign": 4.9
      }
    }
  }
}
```

{% endtab %}

{% tab title="200: OK (grouped)" %}

```json
{
  "stats": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "stats_grouped_by": "utm_source",
    "data": {
      "twitter": {
        "referrals": { "confirmed": 9, "pending": 7, "total": 16 },
        "earnings": { "confirmed": 40.0, "estimated_pending": 10.0, "total": 50.0 },
        "channels": {
          "signup_flow": { "earnings": {}, "impressions": 600, "referrals": 8, "earnings_per_subscriber": 0.1 },
          "campaigns": { "earnings": {}, "clicks": 150, "referrals": 4, "average_earnings_per_campaign": 3.75 }
        }
      }
    }
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}
{% endtabs %}

### Get recommendation

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/recommendations/:uuid`

**Path Parameters**

| Name                                                | Type   | Description                |
| --------------------------------------------------- | ------ | -------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID.    |
| uuid<mark style="color:red;">\*</mark>              | String | The recommendation's UUID. |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                                                         |
| ------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| expand              | String | `recommended_publication`, `stats`.                                                                                                                                                                 |
| from                | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                              |
| to                  | String | End date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                                |
| stats\_grouped\_by  | String | With `expand=stats`, group the stats map by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by referral volume. Blank/missing values appear under `"none"`. Requires `expand=stats`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                                                     |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Archive a recommendation

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/recommendations/:recommendation_uuid/archive`

Soft-archives the paid recommendation so it stops appearing in active lists. Reversible with **re-activate** when this publication (the partner) archived it.

**Path Parameters**

| Name                                                   | Type   | Description                |
| ------------------------------------------------------ | ------ | -------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark>    | String | The publication's UUID.    |
| recommendation\_uuid<mark style="color:red;">\*</mark> | String | The recommendation's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Re-activate a recommendation

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/recommendations/:recommendation_uuid/reactivation`

Restores a soft-archived paid recommendation. Only works when **this publication** (the partner) archived it.

**Path Parameters**

| Name                                                   | Type   | Description                |
| ------------------------------------------------------ | ------ | -------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark>    | String | The publication's UUID.    |
| recommendation\_uuid<mark style="color:red;">\*</mark> | String | The recommendation's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "This recommendation was archived by the sponsor and can't be re-activated."
}
```

{% endtab %}
{% endtabs %}


# Directory

## Directory

Browse the Partner Network directory of paid programs and the offers directory this publication can join, then recommend (join) an entry. Scoped under `/v3/publications/:publication_uuid/earn/directory`.


# Recommendations

## Recommendations

The Partner Network directory of partner programs a publisher can join to earn by recommending. Browse the catalog, then **recommend** (join) a program. The joined recommendations themselves are listed under **Earn › Recommendations**.

### Browse the directory

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/directory/recommendations`

Paginated. Each row includes `already_recommended` — whether this publication already has a non-archived paid recommendation for that program.

**Path Parameters**

| Name                                                | Type   | Description                                    |
| --------------------------------------------------- | ------ | ---------------------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication a join would be attributed to. |

**Query Parameters**

| Name                 | Type    | Description                                                                         |
| -------------------- | ------- | ----------------------------------------------------------------------------------- |
| order\_by            | String  | `commission` or `max_payout`. Defaults to newest first.                             |
| exclude\_recommended | Boolean | When `true`, omit programs this publication already recommends (non-archived paid). |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendations": [
    <Paid Recommendation Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 4,
    "total_recommendations": 173
  }
}
```

{% endtab %}
{% endtabs %}

### Get a directory entry

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/directory/recommendations/:uuid`

On a single entry the object also carries `pre_approved` — whether this publication is auto-approved to join. `already_recommended` is present as on the list.

**Path Parameters**

| Name                                                | Type   | Description                     |
| --------------------------------------------------- | ------ | ------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The viewing publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The partner program's UUID.     |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "recommendation": <Paid Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Recommend a program

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/directory/recommendations/:recommendation_uuid/recommend`

Starts recommending the program's publication. Returns the created **Recommendation** — `status` is `active` when pre-approved, or `awaiting_approval` when the program screens partners.

**Path Parameters**

| Name                                                   | Type   | Description                          |
| ------------------------------------------------------ | ------ | ------------------------------------ |
| publication\_uuid<mark style="color:red;">\*</mark>    | String | The recommending publication's UUID. |
| recommendation\_uuid<mark style="color:red;">\*</mark> | String | The partner program's UUID.          |

**Request Body**

| Name                      | Type   | Description                                                        |
| ------------------------- | ------ | ------------------------------------------------------------------ |
| reason\_for\_recommending | String | The applicant's pitch. Required when the program screens partners. |

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "You are already recommending this publication."
}
```

{% endtab %}
{% endtabs %}


# Offers

## Offers

The partner-side catalog of offers a publication can join. Browse and filter the available offers, then **recommend** (join) one — which creates a joined offer, listed under **Earn › Offers**.

### Browse the directory

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/directory/offers`

Paginated.

**Path Parameters**

| Name                                                | Type   | Description                                    |
| --------------------------------------------------- | ------ | ---------------------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication a join would be attributed to. |

**Query Parameters**

| Name              | Type   | Description                                                                                                                                                |
| ----------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| q                 | String | Free-text search across offer headline, brand, and publication name.                                                                                       |
| categories        | Array  | Filter by offer category. One or more of: `b2b`, `education`, `finance`, `food_and_drink`, `health_fitness`, `kids_family`, `shopping_lifestyle`, `other`. |
| conversion\_types | Array  | Filter by conversion type. One or more of: `lead`, `click`, `action`.                                                                                      |
| commission\_type  | String | Filter by commission type: `fixed` or `percentage`.                                                                                                        |
| countries         | String | Comma-separated ISO country codes (e.g. `US,CA`). Returns offers that accept referrals from any of them.                                                   |
| sort              | String | `newest` (most recently activated). Defaults to a tiered payout ranking.                                                                                   |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offers": [
    <Directory Offer Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 3,
    "total_offers": 118
  }
}
```

{% endtab %}
{% endtabs %}

### Get a directory offer

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/directory/offers/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The offer's UUID.       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offer": <Directory Offer Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Recommend an offer

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/directory/offers/:offer_uuid/recommend`

Joins the offer, returning it as a newly created joined offer.

**Path Parameters**

| Name                                                | Type   | Description                     |
| --------------------------------------------------- | ------ | ------------------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The joining publication's UUID. |
| offer\_uuid<mark style="color:red;">\*</mark>       | String | The directory offer's UUID.     |

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "offer": <Offer Recommendation Object>
}
```

{% endtab %}

{% tab title="409: Conflict" %}

```json
{
  "error": "Offer already added"
}
```

{% endtab %}
{% endtabs %}


# Offers

## Offers

An **offer** here is one this publication has joined from the Offers Directory. Use this resource to list the offers a publication is recommending, fetch a single one by UUID, archive, re-activate, and read the conversions recorded against each. Omit `status` on the list to exclude archived rows.

### List offers

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/offers`

Paginated. Omit `status` to list non-archived rows.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                                                          |
| ------ | ------ | ------------------------------------------------------------------------------------ |
| status | String | Optional filter: `active` or `archived`. Omit for all non-archived. Invalid → `400`. |
| expand | String | `stats` to include performance.                                                      |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.                                               |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                                                 |

{% hint style="info" %}
`stats_grouped_by` is supported on **Get offers stats** and **Get offer**. On list it is ignored.
{% endhint %}

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offers": [
    <Offer Recommendation Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_offers": 5
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `status`. Valid values: active, archived."
}
```

{% endtab %}
{% endtabs %}

### Get offers stats

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/offers/stats`

Offer performance for the entire publication (dashboard Earn → Offers overview). Returns conversions, confirmed-only earnings, and channel breakdowns for signup flow and in-email campaigns. Does **not** require `expand=stats`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                               |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| from                | String | Start date (`YYYY-MM-DD`). Defaults to 30 days ago.                                                                                                                       |
| to                  | String | End date (`YYYY-MM-DD`). Defaults to today.                                                                                                                               |
| stats\_grouped\_by  | String | Optional. Group the full metric shape by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by conversion volume. Blank/missing values appear under `"none"`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key `data` map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                    |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "stats": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "stats_grouped_by": null,
    "conversions": { "confirmed": 10, "pending": 3, "rejected": 1, "total": 14 },
    "earnings": { "confirmed": 50.0 },
    "channels": {
      "signup_flow": {
        "earnings": { "confirmed": 30.0 },
        "impressions": 800,
        "conversions": { "confirmed": 6, "pending": 1, "rejected": 0, "total": 7 }
      },
      "campaigns": {
        "earnings": { "confirmed": 20.0 },
        "clicks": 200,
        "conversions": { "confirmed": 4, "pending": 2, "rejected": 1, "total": 7 },
        "average_earnings_per_campaign": 10.0
      }
    }
  }
}
```

{% endtab %}

{% tab title="200: OK (grouped)" %}

```json
{
  "stats": {
    "from": "2026-05-01",
    "to": "2026-05-31",
    "stats_grouped_by": "utm_source",
    "data": {
      "twitter": {
        "conversions": { "confirmed": 8, "pending": 3, "rejected": 1, "total": 12 },
        "earnings": { "confirmed": 40.0 },
        "channels": {
          "signup_flow": { "earnings": { "confirmed": 25.0 }, "impressions": 600, "conversions": {} },
          "campaigns": { "earnings": { "confirmed": 15.0 }, "clicks": 150, "conversions": {}, "average_earnings_per_campaign": 7.5 }
        }
      }
    }
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}
{% endtabs %}

### Get offer

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/offers/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The offer's UUID.       |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                                                           |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| expand              | String | `stats` to include performance.                                                                                                                                                                       |
| from                | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                                |
| to                  | String | End date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                                  |
| stats\_grouped\_by  | String | With `expand=stats`, group the stats map by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by impression volume. Blank/missing values appear under `"none"`. Requires `expand=stats`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                                                       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offer": <Offer Recommendation Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Archive an offer

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/offers/:offer_uuid/archive`

Soft-archives the joined offer so it stops appearing in active lists. Reversible with **re-activate** when this publication (the partner) archived it.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| offer\_uuid<mark style="color:red;">\*</mark>       | String | The offer's UUID.       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offer": <Offer Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Re-activate an offer

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/offers/:offer_uuid/reactivation`

Restores a soft-archived joined offer. Only works when **this publication** (the partner) archived it. If the brand archived it, the request returns `422`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| offer\_uuid<mark style="color:red;">\*</mark>       | String | The offer's UUID.       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "offer": <Offer Recommendation Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "You are not allowed to reactivate this offer"
}
```

{% endtab %}
{% endtabs %}

### List conversions

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/earn/offers/:offer_uuid/conversions`

Paginated, newest first.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| offer\_uuid<mark style="color:red;">\*</mark>       | String | The offer's UUID.       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "conversions": [
    <Offer Conversion Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 2,
    "total_conversions": 73
  }
}
```

{% endtab %}
{% endtabs %}


# Tools

## Tools

Publication-facing **Tools** resources: the signup flow, in-email campaigns, and welcome surveys. All are scoped to a publication under `/v3/publications/:publication_uuid/tools`.


# Signup Flow

## Signup Flow

A **Signup Flow** is a publication's on-site subscriber flow — the widget (or hosted page) that greets new subscribers and walks them through recommendations, an optional survey, and offers. Each publication has exactly one, so this is a singleton resource.

### Get signup flow

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name                | Type   | Description                                                                                                                                                                                      |
| ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| expand              | String | `stats` to include per-step performance.                                                                                                                                                         |
| from                | String | Start date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                           |
| to                  | String | End date (`YYYY-MM-DD`) for `stats`.                                                                                                                                                             |
| stats\_grouped\_by  | String | With `expand=stats`, group the stats map by `utm_source`, `utm_campaign`, or `country_code`. Top 50 groups by visit volume. Blank/missing values appear under `"none"`. Requires `expand=stats`. |
| stats\_group\_value | String | Optional. With `stats_grouped_by`, return only that one group key (a one-key map). Use `"none"` for blank/missing. Requires `stats_grouped_by`.                                                  |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "Unsupported `stats_grouped_by`. Valid values: utm_source, utm_campaign, country_code."
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Enable recommendations stage

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow/recommendations/activation`

Shows the recommendations stage to new subscribers.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Disable recommendations stage

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow/recommendations/activation`

Hides the recommendations stage. At least one of recommendations or offers must remain enabled.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "You must display either recommendations or offers"
}
```

{% endtab %}
{% endtabs %}

### Enable offers stage

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow/offers/activation`

Shows the offers stage to new subscribers.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Disable offers stage

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow/offers/activation`

Hides the offers stage. At least one of recommendations or offers must remain enabled.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "You must display either recommendations or offers"
}
```

{% endtab %}
{% endtabs %}

### Enable survey stage

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow/survey/activation`

Shows the survey stage to new subscribers. The publication must already have a survey.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "Display survey can't be enabled without a survey"
}
```

{% endtab %}
{% endtabs %}

### Disable survey stage

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/signup_flow/survey/activation`

Hides the survey stage.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "signup_flow": <Signup Flow Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Campaigns

## Campaigns

A **Campaign** is a uniquely tracked link a publication places in its emails. SparkLoop attributes the clicks each link drives — and the referrals, conversions, and earnings that follow — back to the campaign that produced them, so each placement's performance can be measured independently. (The dashboard still labels these "In-email Campaigns".)

Every campaign has a `type` that determines what it points at: `recommendations_hub`, `specific_recommendation`, `offers_hub`, or `specific_offer`. The two hub types point at the whole hub and have no specific target.

### List campaigns

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/campaigns`

Paginated.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Query Parameters**

| Name   | Type   | Description                                                                   |
| ------ | ------ | ----------------------------------------------------------------------------- |
| expand | String | `stats`. `target` cannot be expanded on the list — only on a single campaign. |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.                                        |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                                          |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "campaigns": [
    <Campaign Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_campaigns": 6
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "`target` can only be expanded on a single campaign."
}
```

{% endtab %}
{% endtabs %}

### Get campaign

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/campaigns/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The campaign's UUID.    |

**Query Parameters**

| Name   | Type   | Description                                                                |
| ------ | ------ | -------------------------------------------------------------------------- |
| expand | String | `target` (the recommendation or joined offer the link points at), `stats`. |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.                                     |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                                       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "campaign": <Campaign Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Create campaign

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/campaigns`

Creates a trackable campaign link. Hub types (`recommendations_hub`, `offers_hub`) need no target UUID. Specific types resolve a publication-scoped recommendation or joined offer by UUID.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

**Body Parameters**

| Name                                             | Type   | Description                                                                                                                              |
| ------------------------------------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| campaign\_type<mark style="color:red;">\*</mark> | String | `recommendations_hub`, `specific_recommendation`, `offers_hub`, or `specific_offer`. Invalid → `400`.                                    |
| name                                             | String | Campaign name. Required for hub types. Optional for specific types — defaults to `"<target> - <date>"` (same as the dashboard new form). |
| recommendation\_uuid                             | String | Joined recommendation UUID. Used when `campaign_type` is `specific_recommendation` (404 if unknown; 422 if missing).                     |
| offer\_recommendation\_uuid                      | String | Joined earn-offer UUID. Used when `campaign_type` is `specific_offer` (404 if unknown; 422 if missing).                                  |

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "campaign": <Campaign Object>
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "`campaign_type` is required."
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "Please select a recommendation"
}
```

{% endtab %}
{% endtabs %}


# Surveys

## Surveys

A **Survey** is a welcome survey a publication shows in its signup flow to collect subscriber profile data. Surveys are addressed by UUID and nested under a publication's `tools`, so the API can serve several per publication (a publication currently has at most one).

A survey's **questions** are a first-class sub-resource, and each question owns the feed of individual **answers** submitted to it. Completed **submissions** — each with the subscriber's email and every answer given — are a sibling sub-resource.

### List surveys

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "surveys": [
    <Survey Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_surveys": 1
  }
}
```

{% endtab %}
{% endtabs %}

### Get survey

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| uuid<mark style="color:red;">\*</mark>              | String | The survey's UUID.      |

**Query Parameters**

| Name   | Type   | Description                                             |
| ------ | ------ | ------------------------------------------------------- |
| expand | String | `stats` to include the survey step's completion funnel. |
| from   | String | Start date (`YYYY-MM-DD`) for `stats`.                  |
| to     | String | End date (`YYYY-MM-DD`) for `stats`.                    |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "survey": <Survey Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### List questions

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys/:survey_uuid/questions`

Questions are returned in ascending `position` order.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| survey\_uuid<mark style="color:red;">\*</mark>      | String | The survey's UUID.      |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "questions": [
    <Survey Question Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_questions": 4
  }
}
```

{% endtab %}
{% endtabs %}

### Get question

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys/:survey_uuid/questions/:uuid`

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| survey\_uuid<mark style="color:red;">\*</mark>      | String | The survey's UUID.      |
| uuid<mark style="color:red;">\*</mark>              | String | The question's UUID.    |

**Query Parameters**

| Name   | Type   | Description                                                                   |
| ------ | ------ | ----------------------------------------------------------------------------- |
| expand | String | `stats` to include the per-option answer distribution. Select questions only. |

{% hint style="info" %}
`expand=stats` is only valid for select questions (`single_select`, `multi_select`). Requesting it on a text question returns `422` — read the individual answers via the answers feed instead.
{% endhint %}

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "question": <Survey Question Object>
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "Text questions have no answer distribution. Fetch the individual answers instead."
}
```

{% endtab %}
{% endtabs %}

### List answers

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys/:survey_uuid/questions/:question_uuid/answers`

A paginated, reverse-chronological feed of the individual answers to one question. Unlike the question's `stats` distribution, this includes text questions — for a free-text question it's the only way to read what subscribers submitted. Answers carry no subscriber identity; to see who gave them, use the submissions feed, which includes each subscriber's `email`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| survey\_uuid<mark style="color:red;">\*</mark>      | String | The survey's UUID.      |
| question\_uuid<mark style="color:red;">\*</mark>    | String | The question's UUID.    |

**Query Parameters**

| Name   | Type   | Description                                                                                 |
| ------ | ------ | ------------------------------------------------------------------------------------------- |
| expand | String | `question` to inline the full question object on each answer (a UUID reference by default). |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "answers": [
    <Survey Answer Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 3,
    "total_answers": 128
  }
}
```

{% endtab %}
{% endtabs %}

### List submissions

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys/:survey_uuid/submissions`

A paginated, reverse-chronological feed of completed submissions, each with the subscriber's `email` and every answer given. A subscriber who completes the survey more than once has one submission per completion.

Pass `email` to return only that subscriber's submissions. The match is case-insensitive, results stay newest first and are still paginated. A blank `email` is ignored and returns the full feed; an email with no submissions returns an empty `submissions` list with a `200`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| survey\_uuid<mark style="color:red;">\*</mark>      | String | The survey's UUID.      |

**Query Parameters**

| Name  | Type   | Description                                                                                                                 |
| ----- | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| email | String | A subscriber's email address. Returns only that subscriber's submissions (case-insensitive). Must be a valid email address. |

{% hint style="info" %}
URL-encode a `+` in the email as `%2B` — for example, `?email=jane%2Bnews@example.com`. An unencoded `+` is read as a space, so the value is no longer a valid email address and the request returns `400`.
{% endhint %}

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "submissions": [
    <Survey Submission Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 5,
    "total_submissions": 214
  }
}
```

{% endtab %}

{% tab title="400: Bad Request" %}

```json
{
  "error": "`email` must be a valid email address (URL-encode `+` as `%2B`)."
}
```

{% endtab %}
{% endtabs %}

#### Example: filter by subscriber email

```bash
curl "https://api.sparkloop.app/v3/publications/PUBLICATION_UUID/tools/surveys/SURVEY_UUID/submissions?email=jane@example.com" \
  -H "x-api-key: YOUR_API_KEY"
```

```json
{
  "submissions": [
    {
      "uuid": "ssub_77cc33dd",
      "email": "jane@example.com",
      "submitted_at": "2026-07-15T10:00:00.000Z",
      "source": "recommendations_hub",
      "answers": [
        {
          "value": "Founder",
          "answered_at": "2026-07-15T10:00:00.000Z",
          "question": {
            "uuid": "sqn_9f8e7d6c",
            "position": 1,
            "label": "What best describes your role?",
            "required": true,
            "type": "single_select",
            "options": ["Founder", "Engineer", "Marketer", "Other"]
          }
        },
        {
          "value": ["Newsletters", "Podcasts"],
          "answered_at": "2026-07-15T10:00:00.000Z",
          "question": {
            "uuid": "sqn_1a2b3c4d",
            "position": 2,
            "label": "What do you like to read?",
            "required": false,
            "type": "multi_select",
            "options": ["Newsletters", "Podcasts", "Blogs", "Books"]
          }
        }
      ]
    },
    {
      "uuid": "ssub_44aa22bb",
      "email": "jane@example.com",
      "submitted_at": "2026-06-02T14:20:00.000Z",
      "source": "signup_flow",
      "answers": [
        {
          "value": "Engineer",
          "answered_at": "2026-06-02T14:20:00.000Z",
          "question": {
            "uuid": "sqn_9f8e7d6c",
            "position": 1,
            "label": "What best describes your role?",
            "required": true,
            "type": "single_select",
            "options": ["Founder", "Engineer", "Marketer", "Other"]
          }
        }
      ]
    }
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_submissions": 2
  }
}
```

### Get submission

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/publications/:publication_uuid/tools/surveys/:survey_uuid/submissions/:uuid`

Returns one submission by UUID, including the subscriber's `email`.

**Path Parameters**

| Name                                                | Type   | Description             |
| --------------------------------------------------- | ------ | ----------------------- |
| publication\_uuid<mark style="color:red;">\*</mark> | String | The publication's UUID. |
| survey\_uuid<mark style="color:red;">\*</mark>      | String | The survey's UUID.      |
| uuid<mark style="color:red;">\*</mark>              | String | The submission's UUID.  |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "submission": <Survey Submission Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Payouts

## Payouts

A **Payout** is a payment SparkLoop has made (or is preparing to make) to your account for partner-network referral commissions. Payouts are account-scoped; this resource exposes only your account's partner-network payouts — affiliate commissions and other adjustments are not returned, by list or by UUID.

### List payouts

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/payouts`

Paginated, newest first.

**Query Parameters**

| Name   | Type   | Description                                                                                                                       |
| ------ | ------ | --------------------------------------------------------------------------------------------------------------------------------- |
| expand | String | `details` (per-publication breakdown of what this payout pays for), `unpaid_details` (confirmations deferred to a future payout). |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "payouts": [
    <Payout Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_payouts": 9
  }
}
```

{% endtab %}
{% endtabs %}

### Get payout

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/payouts/:uuid`

**Path Parameters**

| Name                                   | Type   | Description        |
| -------------------------------------- | ------ | ------------------ |
| uuid<mark style="color:red;">\*</mark> | String | The payout's UUID. |

**Query Parameters**

| Name   | Type   | Description                  |
| ------ | ------ | ---------------------------- |
| expand | String | `details`, `unpaid_details`. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "payout": <Payout Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}


# Webhooks

## Webhooks

A **Webhook** delivers an HTTP `POST` to a URL you control whenever one of the subscribed events happens on your account. Webhooks are account-scoped and fully manageable through the API — list, retrieve, create, update, and delete.

Subscribable events: `new_referral`, `reward_unlocked`, `reward_redeemed`, `sync_subscriber`, `new_partner_referral`, `new_partner_pending_referral`, `new_opportunity`. (`new_offer_lead` is read-only — it's tied to an offer and managed in the dashboard.)

### List webhooks

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/webhooks`

Paginated.

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "webhooks": [
    <Webhook Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 1,
    "total_webhooks": 3
  }
}
```

{% endtab %}
{% endtabs %}

### Get webhook

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v3/webhooks/:uuid`

**Path Parameters**

| Name                                   | Type   | Description         |
| -------------------------------------- | ------ | ------------------- |
| uuid<mark style="color:red;">\*</mark> | String | The webhook's UUID. |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "webhook": <Webhook Object>
}
```

{% endtab %}

{% tab title="404: Not Found" %}

```json
{
  "error": "Resource not found."
}
```

{% endtab %}
{% endtabs %}

### Create webhook

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v3/webhooks`

**Request Body**

| Name                                  | Type   | Description                           |
| ------------------------------------- | ------ | ------------------------------------- |
| url<mark style="color:red;">\*</mark> | String | The destination URL. Must be `https`. |
| events                                | Array  | The event names to enable.            |

```json
{
  "webhook": {
    "url": "https://example.com/hooks/sparkloop",
    "events": ["new_referral", "reward_unlocked"]
  }
}
```

{% tabs %}
{% tab title="201: Created" %}

```json
{
  "webhook": <Webhook Object>
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "Unsupported event(s): foo"
}
```

{% endtab %}
{% endtabs %}

### Update webhook

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v3/webhooks/:uuid`

Supplying `events` replaces the whole set; omit it to change `url` alone.

**Path Parameters**

| Name                                   | Type   | Description         |
| -------------------------------------- | ------ | ------------------- |
| uuid<mark style="color:red;">\*</mark> | String | The webhook's UUID. |

**Request Body**

| Name   | Type   | Description                           |
| ------ | ------ | ------------------------------------- |
| url    | String | The destination URL. Must be `https`. |
| events | Array  | Replacement set of event names.       |

{% tabs %}
{% tab title="200: OK" %}

```json
{
  "webhook": <Webhook Object>
}
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "Unsupported event(s): foo"
}
```

{% endtab %}
{% endtabs %}

### Delete webhook

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v3/webhooks/:uuid`

**Path Parameters**

| Name                                   | Type   | Description         |
| -------------------------------------- | ------ | ------------------- |
| uuid<mark style="color:red;">\*</mark> | String | The webhook's UUID. |

{% tabs %}
{% tab title="204: No Content" %}

```
(empty response)
```

{% endtab %}

{% tab title="422: Unprocessable Entity" %}

```json
{
  "error": "A sponsor on a custom ESP must keep a partner-referral webhook."
}
```

{% endtab %}
{% endtabs %}


# Objects Schema

## Objects Schema

Endpoint responses reference the objects below rather than repeating their fields inline. Each object has its own page listing every field it returns — including those that appear only when requested via `expand` — alongside a full example.


# Account Object

## Account Object

| Field          | Type           | Description                                                                          |
| -------------- | -------------- | ------------------------------------------------------------------------------------ |
| `uuid`         | String         | Account (team) UUID.                                                                 |
| `name`         | String         | Account name.                                                                        |
| `esp`          | String \| null | Name of the connected email service provider (e.g. `Kit`), or `null` if none.        |
| `logo_url`     | String         | URL of the account's logo (a placeholder if none is set).                            |
| `created_at`   | String         | When the account was created (ISO 8601).                                             |
| `owner`        | Object         | The account owner: `email`, `name`.                                                  |
| `team_members` | Array          | Team members, each with `email` and `name`. Present only with `expand=team_members`. |

**Example**

```json
{
  "uuid": "team_7a38e34ed0",
  "name": "The Mermaids Singing",
  "esp": "Kit",
  "logo_url": "https://assets.sparkloop.app/logos/team_7a38e34ed0.png",
  "created_at": "2025-01-12T09:30:00.000Z",
  "owner": {
    "email": "owner@example.com",
    "name": "Ada Lovelace"
  },
  "team_members": [
    { "email": "owner@example.com", "name": "Ada Lovelace" },
    { "email": "editor@example.com", "name": "Grace Hopper" }
  ]
}
```


# Directory Offer Object

## Directory Offer Object

The partner-facing view of a joinable offer in the Offers Directory. Also inlined as `offer` on an **Offer Recommendation Object**.

| Field              | Type           | Description                                                                                         |
| ------------------ | -------------- | --------------------------------------------------------------------------------------------------- |
| `uuid`             | String         | Offer UUID.                                                                                         |
| `headline`         | String         | Offer headline.                                                                                     |
| `description`      | String \| null | Offer description.                                                                                  |
| `terms`            | String \| null | Offer terms.                                                                                        |
| `conversion_type`  | String         | What counts as a conversion (e.g. `lead`).                                                          |
| `category`         | String \| null | Offer category.                                                                                     |
| `image_url`        | String         | Offer image URL.                                                                                    |
| `commissions`      | Array          | Payout tiers: `type` (`fixed_amount`/`percentage`), `amount`, `description`.                        |
| `geo_restrictions` | Object         | Accepted-referral geography: `allow_worldwide_referrals`, `allowed_countries`, `blocked_countries`. |
| `source`           | String         | `external-brand`, `internal`, or `sparkloop-network`.                                               |
| `brand`            | Object         | `name`, `logo_url`.                                                                                 |
| `max_payout`       | Number         | The offer's monthly budget, in dollars.                                                             |

**Example**

```json
{
  "uuid": "offer_1a2b3c",
  "headline": "Get 20% off Acme Pro",
  "description": "Premium productivity tools for newsletter teams.",
  "terms": "New customers only.",
  "conversion_type": "lead",
  "category": "Productivity",
  "image_url": "https://assets.sparkloop.app/offers/offer_1a2b3c.png",
  "commissions": [
    { "type": "fixed_amount", "amount": 2.0, "description": "Per verified lead" }
  ],
  "geo_restrictions": {
    "allow_worldwide_referrals": true,
    "allowed_countries": [],
    "blocked_countries": []
  },
  "source": "external-brand",
  "brand": {
    "name": "Acme Inc.",
    "logo_url": "https://assets.sparkloop.app/brands/acme.png"
  },
  "max_payout": 1000.0
}
```


# Earnings Report Object

## Earnings Report Object

The earnings report echoes the query it was built from and carries the figures under `data`. The shape of `data` depends on `granularity` and `group_by`.

| Field         | Type            | Description                               |
| ------------- | --------------- | ----------------------------------------- |
| `from`        | String          | Start of the window (`YYYY-MM-DD`).       |
| `to`          | String          | End of the window (`YYYY-MM-DD`).         |
| `channel`     | String \| null  | The channel filter applied, or `null`.    |
| `group_by`    | String \| null  | The grouping applied, or `null`.          |
| `granularity` | String          | `total`, `daily`, `weekly`, or `monthly`. |
| `data`        | Object \| Array | The earnings figures (see below).         |

Every earnings figure is an object of `confirmed`, `estimated_pending`, and `total`, in dollars.

* **`granularity=total`, no `group_by`** — `data` is a single earnings object.
* **`granularity=total` with `group_by`** — `data` is an array, one entry per group, ranked highest-first. Each entry carries the earnings figure plus fields naming the group. The two id-based dimensions have the same shape — the group's own uuid plus the promoted thing as an expandable:
  * `recommendation` — `recommendation_uuid`, plus `recommended_publication`: the promoted newsletter as a **Publication Object** expandable (its UUID by default, the full object with `expand=recommended_publication`).
  * `offer` — `offer_recommendation_uuid`, plus `offer`: the promoted offer as an **Offer Object** expandable (its UUID by default, the full object with `expand=offer`).
  * `utm_campaign` — the raw `utm_campaign` string.
* **`granularity=daily`/`weekly`/`monthly`** — `data` is an array, one entry per time bucket, each with a `date`.

**Example (overall total)**

```json
{
  "from": "2026-06-01",
  "to": "2026-06-30",
  "channel": null,
  "group_by": null,
  "granularity": "total",
  "data": { "confirmed": 240.0, "estimated_pending": 37.5, "total": 277.5 }
}
```

**Example (grouped by recommendation)**

By default a recommendation row identifies the promoted newsletter only by `recommended_publication`'s UUID; the `recommendation_uuid` itself is an opaque `partner_campaign_…` id.

```json
{
  "from": "2026-06-01",
  "to": "2026-06-30",
  "channel": null,
  "group_by": "recommendation",
  "granularity": "total",
  "data": [
    { "recommendation_uuid": "partner_campaign_a6224cd65cf3", "recommended_publication": "pub_9a8b7c6d5e4f", "confirmed": 200.0, "estimated_pending": 30.0, "total": 230.0 }
  ]
}
```

**Example (grouped by recommendation, `expand=recommended_publication`)**

Expanding inlines each promoted newsletter's full **Publication Object** — including its `name` — in place of the UUID.

```json
{
  "recommendation_uuid": "partner_campaign_a6224cd65cf3",
  "recommended_publication": {
    "uuid": "pub_9a8b7c6d5e4f",
    "name": "The Leveraged Coach Newsletter",
    "description": "Build a Better Coaching Business",
    "logo_url": "https://cdn.sparkloop.app/publications/pub_9a8b7c6d5e4f/logo.png",
    "url": "https://theleveragedcoach.com"
  },
  "confirmed": 200.0,
  "estimated_pending": 30.0,
  "total": 230.0
}
```

**Example (grouped by offer)**

An offer row mirrors a recommendation row: the publication's OfferRecommendation uuid (addressable via `/earn/offers/:uuid`) plus the promoted offer as an expandable.

```json
{
  "from": "2026-06-01",
  "to": "2026-06-30",
  "channel": null,
  "group_by": "offer",
  "granularity": "total",
  "data": [
    { "offer_recommendation_uuid": "offer_recommendation_ab12cd34ef56", "offer": "offer_1a2b3c4d5e6f", "confirmed": 120.0, "estimated_pending": 0.0, "total": 120.0 }
  ]
}
```

**Example (grouped by offer, `expand=offer`)**

Expanding inlines each promoted offer's full **Offer Object** in place of the UUID.

```json
{
  "offer_recommendation_uuid": "offer_recommendation_ab12cd34ef56",
  "offer": {
    "uuid": "offer_1a2b3c4d5e6f",
    "headline": "Get 20% off your first order",
    "source": "external-brand",
    "brand": { "name": "Acme Cookware", "logo_url": "https://cdn.sparkloop.app/offers/offer_1a2b3c4d5e6f/logo.png" },
    "max_payout": 500.0
  },
  "confirmed": 120.0,
  "estimated_pending": 0.0,
  "total": 120.0
}
```

**Example (monthly time series)**

```json
{
  "from": "2026-04-01",
  "to": "2026-06-30",
  "channel": "signup_flow",
  "group_by": null,
  "granularity": "monthly",
  "data": [
    { "date": "2026-04-01", "confirmed": 180.0, "estimated_pending": 20.0, "total": 200.0 },
    { "date": "2026-05-01", "confirmed": 210.0, "estimated_pending": 25.0, "total": 235.0 },
    { "date": "2026-06-01", "confirmed": 240.0, "estimated_pending": 37.5, "total": 277.5 }
  ]
}
```


# Free Recommendation Object

## Free Recommendation Object

A directory entry in the free-recommendations catalog — a publication you can recommend for free.

| Field                         | Type           | Description                                                                                                             |
| ----------------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `uuid`                        | String         | The directory publication's UUID.                                                                                       |
| `name`                        | String         | Publication name.                                                                                                       |
| `description`                 | String \| null | Short description.                                                                                                      |
| `logo_url`                    | String         | URL of the publication's logo.                                                                                          |
| `url`                         | String         | The publication's public URL.                                                                                           |
| `already_recommended`         | Boolean        | Whether the viewing publication already recommends this one. Present only on the single-entry `show`.                   |
| `partnership_request_pending` | Boolean        | Whether the viewing publication has a pending partnership request to this one. Present only on the single-entry `show`. |

**Example**

```json
{
  "uuid": "pub_9ac1f2",
  "name": "The Morning Brew",
  "description": "Business news you'll actually enjoy.",
  "logo_url": "https://assets.sparkloop.app/logos/pub_9ac1f2.png",
  "url": "https://morningbrew.com",
  "already_recommended": false,
  "partnership_request_pending": false
}
```


# Campaign Object

## Campaign Object

A tracking link a publisher drops into their emails. (Dashboard label: "In-email Campaign".)

| Field        | Type                     | Description                                                                                                                                                        |
| ------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `uuid`       | String                   | Campaign UUID.                                                                                                                                                     |
| `name`       | String                   | Campaign name.                                                                                                                                                     |
| `public_url` | String                   | The public tracking URL.                                                                                                                                           |
| `email_url`  | String                   | The URL to embed in emails.                                                                                                                                        |
| `type`       | String                   | `recommendations_hub`, `specific_recommendation`, `offers_hub`, or `specific_offer`.                                                                               |
| `target`     | String \| Object \| null | What the link points at. A UUID by default; the full recommendation/joined-offer object with `expand=target` (single campaign only). `null` for the two hub types. |
| `stats`      | Object                   | Performance. Present only with `expand=stats`.                                                                                                                     |

**`stats`** (only with `expand=stats`)

Recommendation-side campaigns (`recommendations_hub`, `specific_recommendation`) report `clicks`, `referrals`, and `earnings` (`confirmed`, `estimated_pending`, `total`). Offer-side campaigns (`offers_hub`, `specific_offer`) report `clicks`, `conversions`, and `earnings` (`confirmed` only).

**Example (`specific_recommendation`, `expand=stats`)**

```json
{
  "uuid": "lc_2b3c4d5e",
  "name": "June newsletter — Morning Brew",
  "public_url": "https://sparkloop.app/p/2b3c4d5e",
  "email_url": "https://link.sparkloop.app/p/2b3c4d5e",
  "type": "specific_recommendation",
  "target": "rec_a6224cd65cf3",
  "stats": {
    "clicks": 940,
    "referrals": 120,
    "earnings": { "confirmed": 240.0, "estimated_pending": 37.5, "total": 277.5 }
  }
}
```


# Offer Conversion Object

## Offer Conversion Object

A single conversion recorded against a joined offer.

| Field              | Type           | Description                                                |
| ------------------ | -------------- | ---------------------------------------------------------- |
| `uuid`             | String         | Conversion UUID.                                           |
| `pricing_type`     | String         | The offer's pricing model: `free` or `paid`.               |
| `payable`          | Boolean        | Whether this conversion is payable.                        |
| `rejection_reason` | String \| null | Why it was rejected, if applicable.                        |
| `type`             | String         | `lead`, `click`, or `action`.                              |
| `status`           | String         | Referral status (e.g. `confirmed`, `pending`, `rejected`). |
| `source`           | String         | Referral channel (e.g. `signup_flow`).                     |
| `commission`       | Number         | Commission for this conversion, in dollars.                |
| `referred_at`      | String \| null | When the conversion was referred (ISO 8601).               |
| `confirmed_at`     | String \| null | When it was confirmed (ISO 8601).                          |
| `rejected_at`      | String \| null | When it was rejected (ISO 8601).                           |

**Example**

```json
{
  "uuid": "conv_abc12345",
  "pricing_type": "paid",
  "payable": true,
  "rejection_reason": null,
  "type": "lead",
  "status": "confirmed",
  "source": "signup_flow",
  "commission": 2.0,
  "referred_at": "2026-06-01T10:00:00.000Z",
  "confirmed_at": "2026-06-08T10:00:00.000Z",
  "rejected_at": null
}
```


# Offer Object

## Offer Object

The brand/owner view of an offer a publication has created.

| Field              | Type           | Description                                                                                         |
| ------------------ | -------------- | --------------------------------------------------------------------------------------------------- |
| `uuid`             | String         | Offer UUID.                                                                                         |
| `headline`         | String         | Offer headline.                                                                                     |
| `description`      | String \| null | Offer description.                                                                                  |
| `terms`            | String \| null | Offer terms.                                                                                        |
| `conversion_type`  | String         | What counts as a conversion (e.g. `lead`).                                                          |
| `category`         | String \| null | Offer category.                                                                                     |
| `image_url`        | String         | Offer image URL.                                                                                    |
| `commissions`      | Array          | Payout tiers (see below). Empty for internal offers.                                                |
| `geo_restrictions` | Object         | Accepted-referral geography: `allow_worldwide_referrals`, `allowed_countries`, `blocked_countries`. |
| `name`             | String         | Offer name.                                                                                         |
| `status`           | String         | Offer status (e.g. `active`).                                                                       |
| `brand_origin`     | String         | `own` or `external`.                                                                                |
| `visibility`       | String         | `public` or `private`.                                                                              |
| `created_at`       | String         | When the offer was created (ISO 8601).                                                              |
| `budget`           | Object         | `monthly_cap`, `used`, `remaining` in dollars.                                                      |
| `stats`            | Object         | Performance. Present only with `expand=stats`.                                                      |

**`commissions[]`**

| Field         | Type           | Description                               |
| ------------- | -------------- | ----------------------------------------- |
| `type`        | String         | `fixed_amount` (dollars) or `percentage`. |
| `amount`      | Number         | The payout figure, interpreted by `type`. |
| `description` | String \| null | Human description of the tier.            |

**`stats`** (only with `expand=stats`)

By default, `stats` is a flat object:

| Field             | Type    | Description                                             |
| ----------------- | ------- | ------------------------------------------------------- |
| `impressions`     | Integer | Times shown.                                            |
| `claims`          | Integer | Primary conversions (claims).                           |
| `claim_rate`      | Number  | Claims / impressions, as a percentage.                  |
| `conversions`     | Integer | Confirmed conversions.                                  |
| `conversion_rate` | Number  | Confirmed conversions / subscriptions, as a percentage. |

**Grouped `stats`** (show only, with `stats_grouped_by`)

On **Get offer** (Grow), pass `stats_grouped_by=utm_source`, `utm_campaign`, or `country_code` with `expand=stats` to get the same stats shape nested under each group key. Results are capped at the top 50 groups by impression volume. Blank or missing values appear under `"none"`. Optionally pass `stats_group_value` to return a one-key map for that group only. Grow offer stats have no `earnings` field.

**Example (`expand=stats`)**

```json
{
  "uuid": "offer_1a2b3c",
  "headline": "Get 20% off Acme Pro",
  "description": "Premium productivity tools for newsletter teams.",
  "terms": "New customers only.",
  "conversion_type": "lead",
  "category": "Productivity",
  "image_url": "https://assets.sparkloop.app/offers/offer_1a2b3c.png",
  "commissions": [
    { "type": "fixed_amount", "amount": 2.0, "description": "Per verified lead" }
  ],
  "geo_restrictions": {
    "allow_worldwide_referrals": true,
    "allowed_countries": [],
    "blocked_countries": []
  },
  "name": "Acme Pro",
  "status": "active",
  "brand_origin": "own",
  "visibility": "public",
  "created_at": "2025-05-01T00:00:00.000Z",
  "budget": { "monthly_cap": 1000.0, "used": 250.0, "remaining": 750.0 },
  "stats": {
    "impressions": 5000,
    "claims": 400,
    "claim_rate": 8.0,
    "conversions": 120,
    "conversion_rate": 30.0
  }
}
```

**Example (`expand=stats&stats_grouped_by=utm_source`)**

```json
{
  "uuid": "offer_1a2b3c",
  "headline": "Get 20% off Acme Pro",
  "name": "Acme Pro",
  "status": "active",
  "brand_origin": "own",
  "visibility": "public",
  "created_at": "2025-05-01T00:00:00.000Z",
  "budget": { "monthly_cap": 1000.0, "used": 250.0, "remaining": 750.0 },
  "stats": {
    "twitter": {
      "impressions": 3500,
      "claims": 280,
      "claim_rate": 8.0,
      "conversions": 84,
      "conversion_rate": 30.0
    },
    "none": {
      "impressions": 1500,
      "claims": 120,
      "claim_rate": 8.0,
      "conversions": 36,
      "conversion_rate": 30.0
    }
  }
}
```


# Offer Recommendation Object

## Offer Recommendation Object

An offer a publication has joined. The full directory offer is always inlined as `offer`.

| Field        | Type   | Description                                    |
| ------------ | ------ | ---------------------------------------------- |
| `uuid`       | String | Offer recommendation UUID.                     |
| `status`     | String | Status of the joined offer.                    |
| `created_at` | String | When the offer was joined (ISO 8601).          |
| `offer`      | Object | The joined offer (**Directory Offer Object**). |
| `stats`      | Object | Performance. Present only with `expand=stats`. |

**`stats`** (only with `expand=stats`)

By default, `stats` is a flat object:

| Field             | Type    | Description                                             |
| ----------------- | ------- | ------------------------------------------------------- |
| `impressions`     | Integer | Times shown.                                            |
| `claims`          | Integer | Primary conversions (claims).                           |
| `claim_rate`      | Number  | Claims / impressions, as a percentage.                  |
| `conversions`     | Integer | Confirmed conversions.                                  |
| `conversion_rate` | Number  | Confirmed conversions / subscriptions, as a percentage. |
| `earnings`        | Number  | Confirmed earnings, in dollars.                         |

**Grouped `stats`** (show only, with `stats_grouped_by`)

On **Get offer**, pass `stats_grouped_by=utm_source`, `utm_campaign`, or `country_code` with `expand=stats` to get the same stats shape nested under each group key. Results are capped at the top 50 groups by impression volume. Blank or missing values appear under `"none"`. Optionally pass `stats_group_value` to return a one-key map for that group only.

**Example (`expand=stats`)**

```json
{
  "uuid": "offrec_5d6e7f",
  "status": "active",
  "created_at": "2026-01-15T09:00:00.000Z",
  "offer": {
    "uuid": "offer_1a2b3c",
    "headline": "Get 20% off Acme Pro",
    "description": "Premium productivity tools for newsletter teams.",
    "terms": "New customers only.",
    "conversion_type": "lead",
    "category": "Productivity",
    "image_url": "https://assets.sparkloop.app/offers/offer_1a2b3c.png",
    "commissions": [ { "type": "fixed_amount", "amount": 2.0, "description": "Per verified lead" } ],
    "geo_restrictions": { "allow_worldwide_referrals": true, "allowed_countries": [], "blocked_countries": [] },
    "source": "external-brand",
    "brand": { "name": "Acme Inc.", "logo_url": "https://assets.sparkloop.app/brands/acme.png" },
    "max_payout": 1000.0
  },
  "stats": {
    "impressions": 4200,
    "claims": 330,
    "claim_rate": 7.86,
    "conversions": 90,
    "conversion_rate": 27.27,
    "earnings": 180.0
  }
}
```

**Example (`expand=stats&stats_grouped_by=utm_source`)**

```json
{
  "uuid": "offrec_5d6e7f",
  "status": "active",
  "created_at": "2026-01-15T09:00:00.000Z",
  "offer": {
    "uuid": "offer_1a2b3c",
    "headline": "Get 20% off Acme Pro",
    "source": "external-brand",
    "brand": { "name": "Acme Inc.", "logo_url": "https://assets.sparkloop.app/brands/acme.png" },
    "max_payout": 1000.0
  },
  "stats": {
    "twitter": {
      "impressions": 3000,
      "claims": 240,
      "claim_rate": 8.0,
      "conversions": 70,
      "conversion_rate": 29.17,
      "earnings": 140.0
    },
    "none": {
      "impressions": 1200,
      "claims": 90,
      "claim_rate": 7.5,
      "conversions": 20,
      "conversion_rate": 22.22,
      "earnings": 40.0
    }
  }
}
```


# Paid Recommendation Object

## Paid Recommendation Object

A browse-facing partner program in the Partner Network directory.

| Field                                 | Type           | Description                                                                                                                          |
| ------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `uuid`                                | String         | Partner program UUID.                                                                                                                |
| `created_at`                          | String         | When the program was created (ISO 8601).                                                                                             |
| `terms`                               | String \| null | The program's engagement terms.                                                                                                      |
| `engagement_screening`                | Object         | `days` — how long a referral is held before it can confirm.                                                                          |
| `geo_restrictions`                    | Object         | Accepted-referral geography (see below).                                                                                             |
| `commission`                          | Number         | What the program pays per referral.                                                                                                  |
| `max_payout`                          | Number \| null | Per-partner payout cap. `null` until a budget is set.                                                                                |
| `expected_earning_per_referral`       | Number \| null | Commission scaled by the program's target confirmation rate.                                                                         |
| `screening_strictness`                | String \| null | How aggressively referrals are screened: `very_strict`, `strict`, `fair`, or `very_fair`. Null until there's enough data.            |
| `expected_referral_confirmation_rate` | Number \| null | The program's target confirmation rate (0–100).                                                                                      |
| `already_recommended`                 | Boolean        | Whether the viewing publication already has a non-archived paid recommendation for this program. Present on directory list and show. |
| `pre_approved`                        | Boolean        | Whether the viewing publication is auto-approved to join. Present only on the single-entry `show`.                                   |
| `publication`                         | Object         | The publication running the program (**Publication Object**).                                                                        |

**`geo_restrictions`**

| Field                       | Type    | Description                                   |
| --------------------------- | ------- | --------------------------------------------- |
| `allow_worldwide_referrals` | Boolean | Whether referrals from anywhere are accepted. |
| `allowed_countries`         | Array   | Allowed country codes (empty when worldwide). |
| `blocked_countries`         | Array   | Blocked country codes.                        |
| `allowed_regions`           | Array   | Allowed US state codes, when restricted.      |

**Example**

```json
{
  "uuid": "pp_abcdef123",
  "created_at": "2025-03-01T10:00:00.000Z",
  "terms": "Send engaged US subscribers.",
  "engagement_screening": { "days": 7 },
  "geo_restrictions": {
    "allow_worldwide_referrals": false,
    "allowed_countries": ["us", "ca"],
    "blocked_countries": [],
    "allowed_regions": []
  },
  "commission": 2.5,
  "max_payout": 500.0,
  "expected_earning_per_referral": 1.75,
  "screening_strictness": "very_fair",
  "expected_referral_confirmation_rate": 70,
  "already_recommended": false,
  "pre_approved": true,
  "publication": {
    "uuid": "pub_9ac1f2",
    "name": "The Morning Brew",
    "description": "Business news you'll actually enjoy.",
    "logo_url": "https://assets.sparkloop.app/logos/pub_9ac1f2.png",
    "url": "https://morningbrew.com"
  }
}
```


# Partner Object

## Partner Object

An active partner of a publication's partner program — a newsletter recommending it. The sponsor-side view of the same relationship a **Recommendation Object** describes.

| Field                            | Type             | Description                                                                                             |
| -------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------- |
| `uuid`                           | String           | Partner (campaign) UUID.                                                                                |
| `status`                         | String           | Partner status (e.g. `active`).                                                                         |
| `created_at`                     | String           | When the partnership began (ISO 8601).                                                                  |
| `recommending_publication`       | String \| Object | The partner publication's UUID; the full **Publication Object** with `expand=recommending_publication`. |
| `last_30_days_confirmation_rate` | Number           | The partner's confirmation rate over the last 30 days.                                                  |
| `stats`                          | Object           | Date-ranged performance. Present only with `expand=stats`.                                              |

**`stats`** (only with `expand=stats`)

| Field       | Type   | Description                                                                                |
| ----------- | ------ | ------------------------------------------------------------------------------------------ |
| `referrals` | Object | `confirmed`, `pending`, `rejected`, `total` counts.                                        |
| `spend`     | Object | What the sponsor pays this partner: `confirmed`, `estimated_pending`, `total`, in dollars. |

**Example (`expand=recommending_publication,stats`)**

```json
{
  "uuid": "prt_11aa22bb",
  "status": "active",
  "created_at": "2025-09-01T12:00:00.000Z",
  "recommending_publication": {
    "uuid": "pub_5fc782e24409",
    "name": "The Daily Byte",
    "description": "A daily newsletter about tech and culture.",
    "logo_url": "https://assets.sparkloop.app/logos/pub_5fc782e24409.png",
    "url": "https://thedailybyte.com"
  },
  "last_30_days_confirmation_rate": 58.0,
  "stats": {
    "referrals": { "confirmed": 80, "pending": 20, "rejected": 5, "total": 105 },
    "spend": { "confirmed": 200.0, "estimated_pending": 35.0, "total": 235.0 }
  }
}
```


# Partner Program Object

## Partner Program Object

The owner's view of their own partner program.

| Field                  | Type           | Description                                                                               |
| ---------------------- | -------------- | ----------------------------------------------------------------------------------------- |
| `uuid`                 | String         | Partner program UUID.                                                                     |
| `created_at`           | String         | When the program was created (ISO 8601).                                                  |
| `terms`                | String \| null | Engagement terms.                                                                         |
| `engagement_screening` | Object         | `days` — the screening window.                                                            |
| `geo_restrictions`     | Object         | `allow_worldwide_referrals`, `allowed_countries`, `blocked_countries`, `allowed_regions`. |
| `status`               | String         | Program status.                                                                           |
| `budget`               | Object         | Spend configuration and current-cycle usage (see below).                                  |
| `audience_fit`         | Object         | Target-audience preferences (see below).                                                  |
| `stats`                | Object         | Date-ranged performance. Present only with `expand=stats`.                                |

**`budget`**

| Field          | Type   | Description                                                    |
| -------------- | ------ | -------------------------------------------------------------- |
| `commission`   | Number | Per-referral commission, in dollars.                           |
| `monthly_cap`  | Number | The most the program can be charged per month.                 |
| `used`         | Number | Spend used in the current cycle.                               |
| `remaining`    | Number | Remaining budget in the current cycle.                         |
| `spend_pacing` | String | The spend-pacing strategy: `maximize_volume` or `daily_paced`. |

**`audience_fit`**

| Field          | Type   | Description                                         |
| -------------- | ------ | --------------------------------------------------- |
| `gender`       | String | `skew_male`, `skew_female`, or `any_gender`.        |
| `age`          | String | `younger_audience`, `older_audience`, or `any_age`. |
| `income_level` | String | `low_income`, `high_income`, or `any_income`.       |
| `categories`   | Array  | Preferred partner-network categories.               |

**`stats`** (only with `expand=stats`)

| Field       | Type   | Description                                            |
| ----------- | ------ | ------------------------------------------------------ |
| `referrals` | Object | `confirmed`, `pending`, `rejected`, `total` counts.    |
| `spend`     | Object | `confirmed`, `estimated_pending`, `total`, in dollars. |

**Example (`expand=stats`)**

```json
{
  "uuid": "pp_abcdef123",
  "created_at": "2025-03-01T10:00:00.000Z",
  "terms": "Send engaged US subscribers.",
  "engagement_screening": { "days": 7 },
  "geo_restrictions": {
    "allow_worldwide_referrals": false,
    "allowed_countries": ["us", "ca"],
    "blocked_countries": [],
    "allowed_regions": []
  },
  "status": "active",
  "budget": {
    "commission": 2.5,
    "monthly_cap": 2000.0,
    "used": 640.0,
    "remaining": 1360.0,
    "spend_pacing": "maximize_volume"
  },
  "audience_fit": {
    "gender": "any_gender",
    "age": "any_age",
    "income_level": "any_income",
    "categories": ["finance", "b2b"]
  },
  "stats": {
    "referrals": { "confirmed": 256, "pending": 40, "rejected": 12, "total": 308 },
    "spend": { "confirmed": 640.0, "estimated_pending": 100.0, "total": 740.0 }
  }
}
```


# Partner Request Object

## Partner Request Object

A newsletter awaiting approval to join a publication's partner program — the pre-approval mirror of a **Partner Object**.

| Field                      | Type             | Description                                                                                               |
| -------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------- |
| `uuid`                     | String           | Partner request (campaign) UUID.                                                                          |
| `status`                   | String           | Always `awaiting_approval`.                                                                               |
| `created_at`               | String           | When the request was made (ISO 8601).                                                                     |
| `recommending_publication` | String \| Object | The applicant publication's UUID; the full **Publication Object** with `expand=recommending_publication`. |
| `application_reason`       | String \| null   | The applicant's pitch.                                                                                    |
| `partner_track_record`     | Object           | The applicant's track record. Present only with `expand=partner_track_record`.                            |

**`partner_track_record`** (only with `expand=partner_track_record`)

| Field                   | Type    | Description                                             |
| ----------------------- | ------- | ------------------------------------------------------- |
| `recommendations_count` | Integer | How many publications the applicant already recommends. |
| `total_referrals_made`  | Integer | Total referrals the applicant has driven overall.       |

**Example (`expand=recommending_publication,partner_track_record`)**

```json
{
  "uuid": "prt_99cc88dd",
  "status": "awaiting_approval",
  "created_at": "2026-06-20T14:00:00.000Z",
  "recommending_publication": {
    "uuid": "pub_5fc782e24409",
    "name": "The Daily Byte",
    "description": "A daily newsletter about tech and culture.",
    "logo_url": "https://assets.sparkloop.app/logos/pub_5fc782e24409.png",
    "url": "https://thedailybyte.com"
  },
  "application_reason": "Our audience overlaps closely with yours.",
  "partner_track_record": {
    "recommendations_count": 14,
    "total_referrals_made": 5210
  }
}
```


# Partnership Request Object

## Partnership Request Object

A request this publication has sent asking another publication to recommend it back.

| Field                 | Type   | Description                                                         |
| --------------------- | ------ | ------------------------------------------------------------------- |
| `uuid`                | String | Partnership request UUID.                                           |
| `status`              | String | Request status (e.g. `pending`).                                    |
| `created_at`          | String | When the request was sent (ISO 8601).                               |
| `invited_publication` | Object | The publication invited to recommend back (**Publication Object**). |

**Example**

```json
{
  "uuid": "preq_77f0aa12",
  "status": "pending",
  "created_at": "2026-06-01T08:00:00.000Z",
  "invited_publication": {
    "uuid": "pub_9ac1f2",
    "name": "The Morning Brew",
    "description": "Business news you'll actually enjoy.",
    "logo_url": "https://assets.sparkloop.app/logos/pub_9ac1f2.png",
    "url": "https://morningbrew.com"
  }
}
```


# Payout Object

## Payout Object

A partner-network payout to your account.

| Field            | Type           | Description                                                                                                                     |
| ---------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `uuid`           | String         | Payout UUID.                                                                                                                    |
| `type`           | String         | Payout type.                                                                                                                    |
| `start_period`   | String         | Start of the period the payout covers.                                                                                          |
| `end_period`     | String         | End of the period the payout covers.                                                                                            |
| `paid_at`        | String \| null | When the payout was paid (ISO 8601), or `null` if not yet paid.                                                                 |
| `created_at`     | String         | When the payout was created (ISO 8601).                                                                                         |
| `amount`         | Number         | Payout amount, in dollars.                                                                                                      |
| `paid`           | Boolean        | Whether the payout has been paid.                                                                                               |
| `details`        | Array          | Per-publication breakdown of what this payout pays for. Present only with `expand=details`.                                     |
| `unpaid_details` | Array          | Confirmations from this period deferred to a future payout. Same shape as `details`. Present only with `expand=unpaid_details`. |

**`details[]` / `unpaid_details[]`**

| Field              | Type   | Description                                               |
| ------------------ | ------ | --------------------------------------------------------- |
| `publication_uuid` | String | The recommending publication.                             |
| `publication_name` | String | Its name.                                                 |
| `amount`           | Number | Amount attributed to this publication, in dollars.        |
| `line_items`       | Array  | Contributing recommendation/offer line items (see below). |

**`line_items[]`**

| Field         | Type    | Description                                      |
| ------------- | ------- | ------------------------------------------------ |
| `type`        | String  | `recommendation` or `offer`.                     |
| `uuid`        | String  | The recommendation or offer recommendation UUID. |
| `name`        | String  | Its name.                                        |
| `referrals`   | Integer | Referral count (`recommendation` line items).    |
| `conversions` | Integer | Conversion count (`offer` line items).           |
| `amount`      | Number  | Amount for this line item, in dollars.           |

**Example (`expand=details`)**

```json
{
  "uuid": "payout_3f4g5h",
  "type": "partner_network",
  "start_period": "2026-05-01",
  "end_period": "2026-05-31",
  "paid_at": "2026-06-05T00:00:00.000Z",
  "created_at": "2026-06-01T00:00:00.000Z",
  "amount": 235.0,
  "paid": true,
  "details": [
    {
      "publication_uuid": "pub_5fc782e24409",
      "publication_name": "The Daily Byte",
      "amount": 235.0,
      "line_items": [
        { "type": "recommendation", "uuid": "rec_a6224cd65cf3", "name": "The Morning Brew", "referrals": 80, "amount": 200.0 },
        { "type": "offer", "uuid": "offrec_5d6e7f", "name": "Acme Pro", "conversions": 14, "amount": 35.0 }
      ]
    }
  ]
}
```


# Publication Object

## Publication Object

| Field         | Type           | Description                                  |
| ------------- | -------------- | -------------------------------------------- |
| `uuid`        | String         | Publication UUID.                            |
| `name`        | String         | Publication name.                            |
| `description` | String \| null | Short description.                           |
| `logo_url`    | String         | URL of the publication's logo.               |
| `url`         | String         | The publication's public URL.                |
| `status`      | String         | Lifecycle status (e.g. `active`).            |
| `created_at`  | String         | When the publication was created (ISO 8601). |

{% hint style="info" %}
When a publication is embedded in another resource (e.g. `recommended_publication`, `recommending_publication`, `publication`), only `uuid`, `name`, `description`, `logo_url`, and `url` are returned — `status` and `created_at` are omitted.
{% endhint %}

**Example**

```json
{
  "uuid": "pub_5fc782e24409",
  "name": "The Daily Byte",
  "description": "A daily newsletter about tech and culture.",
  "logo_url": "https://assets.sparkloop.app/logos/pub_5fc782e24409.png",
  "url": "https://thedailybyte.com",
  "status": "active",
  "created_at": "2025-02-01T12:00:00.000Z"
}
```


# Recommendation Object

## Recommendation Object

| Field                            | Type             | Description                                                                                                          |
| -------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| `uuid`                           | String           | Recommendation UUID.                                                                                                 |
| `type`                           | String           | `free` or `paid`.                                                                                                    |
| `status`                         | String           | Recommendation status (e.g. `active`, `awaiting_approval`, `archived`).                                              |
| `name`                           | String           | Display name of the recommended publication.                                                                         |
| `last_30_days_confirmation_rate` | Number           | Referral confirmation rate over the last 30 days.                                                                    |
| `role`                           | String           | *Free only.* `sending` or `receiving`, relative to the publication in the path.                                      |
| `recommended_publication`        | String \| Object | The recommended publication's UUID; the full **Publication Object** with `expand=recommended_publication`.           |
| `recommending_publication`       | String \| Object | *Free only.* The sending publication's UUID; the full **Publication Object** with `expand=recommending_publication`. |
| `commission`                     | Number           | *Paid only.* Commission earned per referral.                                                                         |
| `max_payout`                     | Number \| null   | *Paid only.* Per-cycle payout cap.                                                                                   |
| `partner_program_uuid`           | String \| null   | *Paid only.* The backing partner program.                                                                            |
| `stats`                          | Object           | Date-ranged performance. Present only with `expand=stats`.                                                           |

{% hint style="info" %}
Free recommendations omit `commission`, `max_payout`, and `partner_program_uuid`, and their `stats` contain only the `referrals` block (no `earnings`). They include `role` and `recommending_publication`. Paid recommendations omit `role` and `recommending_publication`.
{% endhint %}

**`stats`** (only with `expand=stats`)

By default, `stats` is a flat object:

| Field       | Type   | Description                                                        |
| ----------- | ------ | ------------------------------------------------------------------ |
| `referrals` | Object | `confirmed`, `pending`, `total` referral counts.                   |
| `earnings`  | Object | *Paid only.* `confirmed`, `estimated_pending`, `total` in dollars. |

**Grouped `stats`** (show only, with `stats_grouped_by`)

On **Get recommendation** (Earn) or **Get free recommendation** (Grow), pass `stats_grouped_by=utm_source`, `utm_campaign`, or `country_code` with `expand=stats` to get the same stats shape nested under each group key. Results are capped at the top 50 groups by referral volume. Blank or missing values appear under `"none"`. Optionally pass `stats_group_value` to return a one-key map for that group only. Free recommendations nest only the `referrals` block (no `earnings`).

**Example (paid, `expand=recommended_publication,stats`)**

```json
{
  "uuid": "rec_a6224cd65cf3",
  "type": "paid",
  "status": "active",
  "name": "The Morning Brew",
  "last_30_days_confirmation_rate": 62.5,
  "recommended_publication": {
    "uuid": "pub_9ac1f2",
    "name": "The Morning Brew",
    "description": "Business news you'll actually enjoy.",
    "logo_url": "https://assets.sparkloop.app/logos/pub_9ac1f2.png",
    "url": "https://morningbrew.com"
  },
  "commission": 2.0,
  "max_payout": 500.0,
  "partner_program_uuid": "pp_abcdef123",
  "stats": {
    "referrals": { "confirmed": 120, "pending": 30, "total": 150 },
    "earnings": { "confirmed": 240.0, "estimated_pending": 37.5, "total": 277.5 }
  }
}
```

**Example (paid, `expand=stats&stats_grouped_by=utm_source`)**

```json
{
  "uuid": "rec_a6224cd65cf3",
  "type": "paid",
  "status": "active",
  "name": "The Morning Brew",
  "commission": 2.0,
  "max_payout": 500.0,
  "partner_program_uuid": "pp_abcdef123",
  "stats": {
    "twitter": {
      "referrals": { "confirmed": 80, "pending": 20, "total": 100 },
      "earnings": { "confirmed": 160.0, "estimated_pending": 25.0, "total": 185.0 }
    },
    "none": {
      "referrals": { "confirmed": 40, "pending": 10, "total": 50 },
      "earnings": { "confirmed": 80.0, "estimated_pending": 12.5, "total": 92.5 }
    }
  }
}
```

**Example (free, receiving)**

```json
{
  "uuid": "rec_b7135de76dg4",
  "type": "free",
  "status": "active",
  "name": "The Daily Brief",
  "role": "receiving",
  "last_30_days_confirmation_rate": 54.0,
  "recommended_publication": "pub_thisone",
  "recommending_publication": "pub_partner"
}
```

**Example (free, `expand=stats&stats_grouped_by=utm_source`)**

```json
{
  "uuid": "rec_b7135de76dg4",
  "type": "free",
  "status": "active",
  "name": "The Daily Brief",
  "role": "sending",
  "last_30_days_confirmation_rate": 54.0,
  "recommended_publication": "pub_partner",
  "recommending_publication": "pub_thisone",
  "stats": {
    "twitter": {
      "referrals": { "confirmed": 9, "pending": 7, "total": 17 }
    },
    "none": {
      "referrals": { "confirmed": 2, "pending": 1, "total": 3 }
    }
  }
}
```


# Referral Object

## Referral Object

One subscriber a partner program received from a partner. The referral is the referred reader — its `uuid` is the subscriber's.

| Field                  | Type           | Description                                                                                                      |
| ---------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------- |
| `uuid`                 | String         | The referred subscriber's UUID (`sub_…`).                                                                        |
| `email`                | String         | Subscriber email.                                                                                                |
| `name`                 | String \| null | Subscriber name.                                                                                                 |
| `country`              | String \| null | Acquisition country (uppercase ISO code, e.g. `US`).                                                             |
| `status`               | String         | Referral status (e.g. `confirmed`, `pending`, `rejected`).                                                       |
| `commission`           | Number         | Commission for this referral, in dollars.                                                                        |
| `rejection_reason`     | String \| null | Why it was rejected, if applicable.                                                                              |
| `payable`              | Boolean        | Whether this referral is payable.                                                                                |
| `referred_at`          | String \| null | When the referral was made (ISO 8601).                                                                           |
| `confirmed_at`         | String \| null | When it was confirmed (ISO 8601).                                                                                |
| `rejected_at`          | String \| null | When it was rejected (ISO 8601).                                                                                 |
| `recommending_partner` | Object         | The partner publication that drove it (**Publication Object**). Present only with `expand=recommending_partner`. |

**Example (`expand=recommending_partner`)**

```json
{
  "uuid": "sub_7b8c9d0e",
  "email": "reader@example.com",
  "name": "Jordan Rivera",
  "country": "US",
  "status": "confirmed",
  "commission": 2.5,
  "rejection_reason": null,
  "payable": true,
  "referred_at": "2026-06-02T11:00:00.000Z",
  "confirmed_at": "2026-06-09T11:00:00.000Z",
  "rejected_at": null,
  "recommending_partner": {
    "uuid": "pub_5fc782e24409",
    "name": "The Daily Byte",
    "description": "A daily newsletter about tech and culture.",
    "logo_url": "https://assets.sparkloop.app/logos/pub_5fc782e24409.png",
    "url": "https://thedailybyte.com"
  }
}
```


# Signup Flow Object

## Signup Flow Object

A publication's on-site subscriber flow.

| Field                  | Type   | Description                                                                    |
| ---------------------- | ------ | ------------------------------------------------------------------------------ |
| `uuid`                 | String | Signup flow UUID.                                                              |
| `status`               | String | Flow status.                                                                   |
| `created_at`           | String | When the flow was created (ISO 8601).                                          |
| `display`              | Object | Which stages the flow shows: `recommendations`, `offers`, `survey` (booleans). |
| `hosted_page_url`      | String | The hosted page URL. Present only when the flow is a hosted page.              |
| `visibility`           | Object | Where the widget shows (see below).                                            |
| `completion_behaviour` | Object | What happens after a subscriber finishes (see below).                          |
| `stats`                | Object | Per-step performance. Present only with `expand=stats`.                        |

**`visibility`**

| Field        | Type    | Description                                                                          |
| ------------ | ------- | ------------------------------------------------------------------------------------ |
| `all_pages`  | Boolean | Whether the widget shows on every page.                                              |
| `page_rules` | Array   | Specific page URLs it shows on (empty when `all_pages` is true or on a hosted page). |

**`completion_behaviour`**

One of four mutually exclusive types via `type`: `show_thank_you`, `redirect_to_url` (adds `redirect_url`), `close_widget`, or `custom_rules` (adds `rules`, a per-page list of `{ page_url, type, redirect_url? }`).

**`stats`** (only with `expand=stats`)

By default, `stats` is a flat object: an overall block (`visits`, `earnings`, `earnings_per_visit`) plus one block per stage — `recommendations` (`impressions`, `earnings`, `earnings_per_visit`, `referrals` split into `confirmed_paid`/`pending_paid`/`free`/`total`), `survey` (`impressions`, `completions`, `completions_per_visit`), and `offers` (`impressions`, `earnings`, `earnings_per_visit`, `claims` split into `confirmed_paid`/`pending_paid`/`internal_free`/`total`).

**Grouped `stats`** (with `stats_grouped_by`)

On **Get signup flow**, pass `stats_grouped_by=utm_source`, `utm_campaign`, or `country_code` with `expand=stats` to get the same per-stage shape nested under each group key. Results are capped at the top 50 groups by visit volume. Blank or missing values appear under `"none"`. Optionally pass `stats_group_value` to return a one-key map for that group only.

**Example**

```json
{
  "uuid": "suf_6a7b8c",
  "status": "active",
  "created_at": "2025-02-10T00:00:00.000Z",
  "display": { "recommendations": true, "offers": true, "survey": true },
  "visibility": { "all_pages": true, "page_rules": [] },
  "completion_behaviour": { "type": "show_thank_you" }
}
```

**Example `stats` (`expand=stats`)**

```json
{
  "visits": 12000,
  "earnings": { "confirmed": 900.0, "estimated_pending": 120.0, "total": 1020.0 },
  "earnings_per_visit": 0.085,
  "recommendations": {
    "impressions": 11000,
    "earnings": { "confirmed": 700.0, "estimated_pending": 120.0, "total": 820.0 },
    "earnings_per_visit": 0.068,
    "referrals": { "confirmed_paid": 300, "pending_paid": 60, "free": 140, "total": 500 }
  },
  "survey": { "impressions": 9000, "completions": 6400, "completions_per_visit": 0.53 },
  "offers": {
    "impressions": 8000,
    "earnings": 200.0,
    "earnings_per_visit": 0.017,
    "claims": { "confirmed_paid": 90, "pending_paid": 20, "internal_free": 30, "total": 140 }
  }
}
```

**Example grouped `stats` (`expand=stats&stats_grouped_by=utm_source`)**

```json
{
  "twitter": {
    "visits": 8000,
    "earnings": { "confirmed": 600.0, "estimated_pending": 80.0, "total": 680.0 },
    "earnings_per_visit": 0.085,
    "recommendations": {
      "impressions": 7500,
      "earnings": { "confirmed": 480.0, "estimated_pending": 80.0, "total": 560.0 },
      "earnings_per_visit": 0.07,
      "referrals": { "confirmed_paid": 200, "pending_paid": 40, "free": 90, "total": 330 }
    },
    "survey": { "impressions": 6200, "completions": 4100, "completions_per_visit": 0.66 },
    "offers": {
      "impressions": 5400,
      "earnings": 120.0,
      "earnings_per_visit": 0.015,
      "claims": { "confirmed_paid": 60, "pending_paid": 12, "internal_free": 18, "total": 90 }
    }
  },
  "none": {
    "visits": 4000,
    "earnings": { "confirmed": 300.0, "estimated_pending": 40.0, "total": 340.0 },
    "earnings_per_visit": 0.085,
    "recommendations": {
      "impressions": 3500,
      "earnings": { "confirmed": 220.0, "estimated_pending": 40.0, "total": 260.0 },
      "earnings_per_visit": 0.065,
      "referrals": { "confirmed_paid": 100, "pending_paid": 20, "free": 50, "total": 170 }
    },
    "survey": { "impressions": 2800, "completions": 2300, "completions_per_visit": 0.58 },
    "offers": {
      "impressions": 2600,
      "earnings": 80.0,
      "earnings_per_visit": 0.02,
      "claims": { "confirmed_paid": 30, "pending_paid": 8, "internal_free": 12, "total": 50 }
    }
  }
}
```


# Survey Answer Object

## Survey Answer Object

One individual answer to a survey question. An answer carries no subscriber identity of its own; when answers are returned inside a **Survey Submission Object**, that submission's `email` identifies the subscriber.

| Field         | Type             | Description                                                                                 |
| ------------- | ---------------- | ------------------------------------------------------------------------------------------- |
| `value`       | String \| Array  | The answer. An array of picked options for `multi_select` questions, otherwise a string.    |
| `answered_at` | String           | When the answer was submitted (ISO 8601).                                                   |
| `question`    | String \| Object | The question's UUID by default; the full **Survey Question Object** with `expand=question`. |

**Example**

```json
{
  "value": "Engineer",
  "answered_at": "2026-06-02T14:20:00.000Z",
  "question": "sqn_9f8e7d6c"
}
```

**Example (`multi_select`)**

```json
{
  "value": ["Newsletters", "Podcasts"],
  "answered_at": "2026-06-02T14:21:00.000Z",
  "question": "sqn_1a2b3c4d"
}
```


# Survey Object

## Survey Object

| Field                   | Type    | Description                                                              |
| ----------------------- | ------- | ------------------------------------------------------------------------ |
| `uuid`                  | String  | Survey UUID.                                                             |
| `created_at`            | String  | When the survey was created (ISO 8601).                                  |
| `active_in_signup_flow` | Boolean | Whether the survey step is switched on in the publication's signup flow. |
| `custom_terms`          | Object  | Custom consent terms: `enabled`, `text`, `url`.                          |
| `stats`                 | Object  | Survey-step completion funnel. Present only with `expand=stats`.         |

**`stats`** (only with `expand=stats`)

| Field                   | Type    | Description                                        |
| ----------------------- | ------- | -------------------------------------------------- |
| `impressions`           | Integer | Times the survey step was shown in the date range. |
| `completions`           | Integer | Times it was completed in the date range.          |
| `completions_per_visit` | Number  | Completions divided by impressions.                |

**Example (`expand=stats`)**

```json
{
  "uuid": "svy_12ab34cd",
  "created_at": "2025-06-01T00:00:00.000Z",
  "active_in_signup_flow": true,
  "custom_terms": { "enabled": false, "text": null, "url": null },
  "stats": { "impressions": 3200, "completions": 2100, "completions_per_visit": 0.66 }
}
```


# Survey Question Object

## Survey Question Object

| Field      | Type    | Description                                                                                      |
| ---------- | ------- | ------------------------------------------------------------------------------------------------ |
| `uuid`     | String  | Question UUID.                                                                                   |
| `position` | Integer | Order within the survey (ascending).                                                             |
| `label`    | String  | The question text shown to subscribers.                                                          |
| `required` | Boolean | Whether an answer is required.                                                                   |
| `type`     | String  | `text_input`, `single_select`, `multi_select`, or `checkbox`.                                    |
| `options`  | Array   | The selectable options. Present only for `single_select` and `multi_select`.                     |
| `stats`    | Object  | Per-option answer distribution. Present only with `expand=stats`, and only for select questions. |

**`stats`** (only with `expand=stats`)

| Field           | Type    | Description                                                                 |
| --------------- | ------- | --------------------------------------------------------------------------- |
| `total_answers` | Integer | Number of respondents to this question.                                     |
| `distribution`  | Array   | One entry per option, each `{ "value": <option>, "count": <respondents> }`. |

**Example (`single_select`, `expand=stats`)**

```json
{
  "uuid": "sqn_9f8e7d6c",
  "position": 1,
  "label": "What best describes your role?",
  "required": true,
  "type": "single_select",
  "options": ["Founder", "Engineer", "Marketer", "Other"],
  "stats": {
    "total_answers": 1800,
    "distribution": [
      { "value": "Founder", "count": 540 },
      { "value": "Engineer", "count": 720 },
      { "value": "Marketer", "count": 360 },
      { "value": "Other", "count": 180 }
    ]
  }
}
```


# Survey Submission Object

## Survey Submission Object

One completed survey submission — the subscriber's email and every answer they gave.

| Field          | Type   | Description                                                                                                          |
| -------------- | ------ | -------------------------------------------------------------------------------------------------------------------- |
| `uuid`         | String | Submission UUID.                                                                                                     |
| `email`        | String | The email address of the subscriber who submitted it.                                                                |
| `submitted_at` | String | When the submission was completed (ISO 8601).                                                                        |
| `source`       | String | Where it came from: `signup_flow`, `recommendations_hub`, `api`, or `client_api`.                                    |
| `answers`      | Array  | The submitted answers, ordered by question position. Each is a **Survey Answer Object** with its `question` inlined. |

**Example**

```json
{
  "uuid": "ssub_44aa22bb",
  "email": "jane@example.com",
  "submitted_at": "2026-06-02T14:20:00.000Z",
  "source": "signup_flow",
  "answers": [
    {
      "value": "Engineer",
      "answered_at": "2026-06-02T14:20:00.000Z",
      "question": {
        "uuid": "sqn_9f8e7d6c",
        "position": 1,
        "label": "What best describes your role?",
        "required": true,
        "type": "single_select",
        "options": ["Founder", "Engineer", "Marketer", "Other"]
      }
    }
  ]
}
```


# Webhook Object

## Webhook Object

| Field        | Type   | Description                                   |
| ------------ | ------ | --------------------------------------------- |
| `uuid`       | String | Webhook UUID.                                 |
| `url`        | String | The destination URL (`https`).                |
| `status`     | String | `active` or `inactive`.                       |
| `created_at` | String | When the webhook was created (ISO 8601).      |
| `updated_at` | String | When the webhook was last updated (ISO 8601). |
| `events`     | Array  | The enabled event names.                      |

Available events: `new_referral`, `reward_unlocked`, `reward_redeemed`, `sync_subscriber`, `new_partner_referral`, `new_partner_pending_referral`, `new_opportunity`, and the read-only `new_offer_lead`.

**Example**

```json
{
  "uuid": "wh_5c6d7e8f",
  "url": "https://example.com/hooks/sparkloop",
  "status": "active",
  "created_at": "2025-07-01T00:00:00.000Z",
  "updated_at": "2026-01-01T00:00:00.000Z",
  "events": ["new_referral", "reward_unlocked"]
}
```


# MCP Server

SparkLoop exposes a Model Context Protocol (MCP) server, so AI assistants — Claude, ChatGPT, Cursor, and any other MCP‑compatible client — can read and act on your SparkLoop account in plain language. Ask "which of my paid recommendations earned the most last month?" or "recommend the three best programs in the directory for my audience," and the assistant answers using live account data instead of guesswork.

## Endpoint

```
https://mcp.sparkloop.app
```

The server speaks the Streamable HTTP transport. Point any MCP‑compatible client at this URL — there is nothing to install or self‑host. Opening the URL in a browser won't work; it only responds to MCP clients.

## Authentication

Authenticate in either of two ways. Most people should use OAuth.

### OAuth (recommended)

Paste the server URL into your AI client, sign in with your SparkLoop credentials in the browser, and approve the connection — there is no key to copy or store. The chat apps (ChatGPT and the Claude apps) connect this way. The server advertises its OAuth metadata at `/.well-known/oauth-protected-resource` and supports dynamic client registration, so most clients configure themselves.

### API key

Developer tools and scripts that let you set request headers can send your SparkLoop API key in an `x-api-key` header instead. You'll find your key in the SparkLoop dashboard under **Settings → Integrations**, in the **Your API Key** section. The **Tools → MCP** page also has a **Copy your API key** link. The key identifies your account, and every tool operates within it.

## Connect your client

The chat apps (ChatGPT and the Claude apps) connect with OAuth: you paste the server URL, then sign in to SparkLoop in your browser. The developer tools (Claude Code, Cursor, VS Code) accept OAuth or an API key.

{% hint style="info" %}
These apps rename their menus often. If a label below doesn't match what you see, look for the option to add a **custom connector** or **custom MCP server**, and paste `https://mcp.sparkloop.app`.
{% endhint %}

### ChatGPT

Custom MCP servers need ChatGPT's **Developer mode**, which OpenAI currently lists for Plus, Pro, Business, Enterprise, and Edu accounts. You set it up in ChatGPT on the web; Developer mode apps don't work in the ChatGPT mobile apps.

1. Open **Settings → Apps** (formerly **Connectors**), open **Advanced settings**, and turn on **Developer mode**.
2. Back in **Apps**, start a new app.
3. Enter a name (for example, `SparkLoop`) and the URL `https://mcp.sparkloop.app`.
4. For the authentication mechanism, pick **OAuth**.
5. Click **Scan Tools** and wait for the scan to finish. If ChatGPT opens a SparkLoop sign‑in window, sign in and approve the connection.
6. Click **Create**.
7. In a chat, click **+** near the message box, select SparkLoop from the tool list, and ask your question. For tools that change your account, ChatGPT may ask you to confirm first.

On a Business, Enterprise, or Edu workspace, only an owner or admin can turn on Developer mode and create the app (under **Workspace settings → Apps**). OpenAI's pages differ on which plans can run tools that make changes (the ✎ tools below), so if those tools don't appear for you, your plan may be limited to the read‑only ones.

### Claude (web and desktop apps)

Custom connectors are available on every Claude plan; the Free plan allows one.

1. Open **Customize → Connectors**.
2. Click **+**, then **Add custom connector**.
3. Enter a name (for example, `SparkLoop`) and the URL `https://mcp.sparkloop.app`. You don't need an OAuth client ID or secret, so leave **Advanced settings** as they are.
4. Click **Add**. If Claude doesn't start the sign‑in by itself, click **Connect** on the SparkLoop connector. Sign in to SparkLoop in the browser window and approve the connection.
5. In a chat, click **+**, open **Connectors**, and switch SparkLoop on. Claude asks for your approval before it runs a tool, unless you choose **Always allow** for that tool.

If the sign‑in fails, open **Advanced settings** in the connector dialog and choose the option that lets Claude register with the server automatically, then try again.

On a Team or Enterprise plan, an owner first adds the connector under **Organization settings → Connectors**. Each member then opens **Customize → Connectors** and clicks **Connect** to sign in with their own SparkLoop account.

**Claude mobile apps.** Add the connector on the web or in the desktop app first. It is saved to your Claude account, so it is available the next time you open Claude on iOS or Android.

Some Claude accounts also show a **Request headers** option (in beta) in the connector dialog, which can send an API key. OAuth is simpler, so you don't need it.

### Claude Code

Add the server, then run `/mcp` inside Claude Code, select `sparkloop`, and follow the sign‑in prompt:

```bash
claude mcp add --transport http sparkloop https://mcp.sparkloop.app
```

To use an API key instead of OAuth, pass it as a header:

```bash
claude mcp add --transport http sparkloop https://mcp.sparkloop.app \
  --header "x-api-key: YOUR_API_KEY"
```

### Cursor

Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project). Cursor opens a browser window for you to sign in:

```json
{
  "mcpServers": {
    "sparkloop": {
      "url": "https://mcp.sparkloop.app"
    }
  }
}
```

To use an API key instead of OAuth, add a `headers` entry:

```json
{
  "mcpServers": {
    "sparkloop": {
      "url": "https://mcp.sparkloop.app",
      "headers": { "x-api-key": "YOUR_API_KEY" }
    }
  }
}
```

Some Cursor versions still start the OAuth sign‑in when a server offers it, even with `headers` set. If that happens, complete the sign‑in.

### VS Code

VS Code uses a different file format from Cursor: the top‑level key is `servers`, and each server needs a `type`. Run **MCP: Add Server** from the Command Palette, or add this to `.vscode/mcp.json` (or to your user configuration, via **MCP: Open User Configuration**). VS Code then asks you to sign in:

```json
{
  "servers": {
    "sparkloop": {
      "type": "http",
      "url": "https://mcp.sparkloop.app"
    }
  }
}
```

To use an API key instead of OAuth, add a header that reads the key from a masked input. VS Code asks for the key once and stores it securely, so the key never sits in the file:

```json
{
  "servers": {
    "sparkloop": {
      "type": "http",
      "url": "https://mcp.sparkloop.app",
      "headers": { "x-api-key": "${input:sparkloop-api-key}" }
    }
  },
  "inputs": [
    {
      "type": "promptString",
      "id": "sparkloop-api-key",
      "description": "SparkLoop API key",
      "password": true
    }
  ]
}
```

### Other clients (via mcp-remote)

Clients that don't yet speak remote MCP natively can bridge through `mcp-remote`. It is also a way to use an API key, rather than OAuth, in Claude Desktop (**Settings → Developer → Edit Config**):

```json
{
  "mcpServers": {
    "sparkloop": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp.sparkloop.app", "--header", "x-api-key:${SPARKLOOP_API_KEY}"],
      "env": { "SPARKLOOP_API_KEY": "YOUR_API_KEY" }
    }
  }
}
```

Keep `x-api-key:${SPARKLOOP_API_KEY}` free of spaces — some clients don't escape spaces in `args`.

## How the tools work

* **Start with a publication.** Almost every tool is scoped to one publication, identified by a `publication_uuid`. Call **`list_publications`** first to get one, then pass it to the tools that need it.
* **Expand related data.** Many read tools accept an `expand` argument to fold in related objects (for example, the recommended publication behind a recommendation) that are otherwise returned as a UUID.
* **Ask for stats.** Where a resource has performance data, `expand` a `stats` block and set the window with `from` and `to` (ISO 8601 dates, `YYYY-MM-DD`). The default window is the last 30 days. For totals across a whole collection (all paid recommendations, all free recommendations, or all Earn offers), use the matching `get_*_stats` tool instead of expanding `stats` on every row.
* **Reads vs. writes.** Every tool is read‑only except the write tools (marked ✎), which change your account.
* Lists are paginated, identifiers are UUIDs, and monetary amounts are in dollars.

## SparkLoop terminology

SparkLoop reuses everyday words with specific meanings. The server hands these definitions to the assistant automatically, but they're worth knowing:

| Term                     | What it means                                                                                                                                                                                                                                          |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Publication**          | A newsletter. Nearly every tool is scoped to one.                                                                                                                                                                                                      |
| **Earn vs. Grow**        | The two sides of an account. **Earn** is how a publication makes money (recommending other newsletters and brand offers for commission). **Grow** is how it gains subscribers (running its own partner program and creating offers).                   |
| **Recommendation**       | Another newsletter this publication promotes. **Paid** recommendations earn commission through a partner program; **free** ones are organic. (Called `partner_campaign` in v2.)                                                                        |
| **Partner program**      | The flip side of a paid recommendation: the program *this* publication runs to pay *other* publications (its **partners**) to recommend it. A **partner request** is a publication applying to join; a **referral** is a subscriber a partner sent in. |
| **Offer**                | A brand/advertiser deal that pays per lead, click, or action — it promotes a product, not a newsletter.                                                                                                                                                |
| **Partner Network**      | SparkLoop's directory / marketplace. The `browse_*` tools search it; the `recommend_*` and `request_*` tools act on an entry.                                                                                                                          |
| **Earnings vs. payouts** | **Earnings** is revenue accrued; **payouts** is money SparkLoop has actually paid you.                                                                                                                                                                 |
| **Signup flow**          | The on‑site welcome flow shown to new subscribers (called `upscribe` in v2).                                                                                                                                                                           |

## Available tools

The server exposes 42 tools — 28 read‑only, and 14 (marked ✎) that make changes. Every tool takes the identifiers named in its description (a `publication_uuid`, and often a resource UUID); the **Optional params** column lists what else you can pass. `from`/`to` set the stats window and only apply alongside `expand=stats`, except on the `get_*_stats` tools, where they always apply.

### Discovery

| Tool                | Description                                                                                    | Optional params    |
| ------------------- | ---------------------------------------------------------------------------------------------- | ------------------ |
| `list_publications` | List the newsletters on your account. Start here — most other tools need a `publication_uuid`. | `page`, `per_page` |
| `get_publication`   | Get one publication's profile and settings by UUID.                                            | —                  |

### Earn — your monetization

| Tool                            | Description                                                                                                                                                                                | Optional params                                                                                                                                                                                    |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_paid_recommendations`     | Newsletters you recommend through a partner program to earn commission.                                                                                                                    | `status` (`active`, `budget_used_up`, `paused`, `awaiting_approval`, `archived`), `expand` (`recommended_publication`, `stats`), `from`, `to`, `page`, `per_page`                                  |
| `get_paid_recommendation_stats` | Paid‑recommendation performance for the whole publication — the totals on the Earn → Paid Recommendations dashboard (referrals, earnings, and signup flow / campaigns channel breakdowns). | `from`, `to`, `stats_grouped_by` (`utm_source`, `utm_campaign`, `country_code`), `stats_group_value`                                                                                               |
| `list_free_recommendations`     | Organic partnerships with no commission — both sending and receiving.                                                                                                                      | `status` (`active`, `awaiting_approval`, `archived`), `role` (`sending`, `receiving`), `expand` (`recommended_publication`, `recommending_publication`, `stats`), `from`, `to`, `page`, `per_page` |
| `get_free_recommendation_stats` | Free‑recommendation performance for the whole publication — confirmed referrals sent and received, plus current sending / receiving partnership counts.                                    | `from`, `to`, `stats_grouped_by` (`country_code`), `stats_group_value`                                                                                                                             |
| `get_recommendation`            | Get one of your recommendations by UUID (looks up both paid and free).                                                                                                                     | `expand` (`recommended_publication`, `recommending_publication`, `stats`)                                                                                                                          |
| `list_earn_offers`              | Brand offers you recommend to earn from.                                                                                                                                                   | `status` (`active`, `archived`), `expand` (`stats`), `from`, `to`, `page`, `per_page`                                                                                                              |
| `get_earn_offer_stats`          | Earn‑offer performance for the whole publication — the totals on the Earn → Offers dashboard (conversions, confirmed earnings, and signup flow / campaigns channel breakdowns).            | `from`, `to`, `stats_grouped_by` (`utm_source`, `utm_campaign`, `country_code`), `stats_group_value`                                                                                               |
| `list_offer_conversions`        | The individual leads / clicks / actions recorded against one offer (by `offer_uuid`).                                                                                                      | `page`, `per_page`                                                                                                                                                                                 |
| `list_payouts`                  | Partner Network payments SparkLoop has sent you.                                                                                                                                           | `page`, `per_page`                                                                                                                                                                                 |
| `get_payout`                    | Get one payout by UUID, with an optional per‑publication breakdown.                                                                                                                        | `expand` (`details`, `unpaid_details`)                                                                                                                                                             |

### Grow — your audience

| Tool                             | Description                                                     | Optional params                                                                                                                            |
| -------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `get_partner_program`            | Your own partner program — how you pay others to recommend you. | `expand` (`stats`), `from`, `to`                                                                                                           |
| `list_partners`                  | The publications that recommend you, rank‑ordered.              | `order_by` (`newest`, `confirmed_referrals`, `earnings`), `expand` (`recommending_publication`, `stats`), `from`, `to`, `page`, `per_page` |
| `list_partner_program_referrals` | Subscribers your partners have driven to you.                   | `expand` (`recommending_partner`), `page`, `per_page`                                                                                      |
| `list_partner_requests`          | Pending applications to become a partner.                       | `expand` (`recommending_publication`, `partner_track_record`), `page`, `per_page`                                                          |
| `list_grow_offers`               | Brand offers you created for others to recommend.               | `expand` (`stats`), `from`, `to`, `page`, `per_page`                                                                                       |

### Analytics

| Tool           | Description                                                                                   | Optional params                                                                                                                        |
| -------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `get_earnings` | Accrued earnings across recommendations and offers, sliceable by campaign, channel, and time. | `group_by` (`utm_campaign`, `recommendation`, `offer`), `channel`, `granularity` (`total`, `daily`, `weekly`, `monthly`), `from`, `to` |

### Tools — signup flow, campaigns, surveys

| Tool                           | Description                                                                                                                                                                           | Optional params                                      |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `get_signup_flow`              | Your on‑site welcome flow and its performance.                                                                                                                                        | `expand` (`stats`), `from`, `to`                     |
| `list_campaigns`               | Trackable links you place in your emails (campaigns).                                                                                                                                 | `expand` (`stats`), `from`, `to`, `page`, `per_page` |
| `get_campaign`                 | Get one campaign by UUID.                                                                                                                                                             | `expand` (`stats`, `target`), `from`, `to`           |
| `list_surveys`                 | Welcome surveys shown to new subscribers.                                                                                                                                             | `page`, `per_page`                                   |
| `get_survey_insights`          | Aggregated results for one survey (by `survey_uuid`) — every question and its answer distribution, in a single call.                                                                  | —                                                    |
| `list_survey_question_answers` | Individual answers to one question (by `survey_uuid` + `question_uuid`) — the only way to read free‑text responses.                                                                   | `expand` (`question`), `page`, `per_page`            |
| `list_survey_submissions`      | Completed submissions for one survey (by `survey_uuid`), newest first — each with the subscriber's email and every answer. Pass `email` to return only that subscriber's submissions. | `email`, `page`, `per_page`                          |

### Partner Network — browse the directory

| Tool                          | Description                                                                                        | Optional params                                                                                              |
| ----------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `browse_paid_recommendations` | Browse partner programs you can recommend for commission. Each row includes `already_recommended`. | `order_by` (`commission`, `max_payout`), `exclude_recommended`, `page`, `per_page`                           |
| `browse_free_recommendations` | Browse newsletters you can recommend for free.                                                     | `q`, `categories`, `language`, `page`, `per_page`                                                            |
| `browse_offers`               | Browse brand offers you can recommend to earn.                                                     | `q`, `categories`, `conversion_types`, `commission_type`, `countries`, `sort` (`newest`), `page`, `per_page` |

### Writes

| Tool                                        | Description                                                                                    | Optional params                                                                                                                                                          |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `recommend_paid_recommendation` ✎           | Start recommending a partner program from the directory (by its UUID).                         | `reason_for_recommending` (required by programs that screen partners)                                                                                                    |
| `archive_paid_recommendation` ✎             | Soft-archive a paid recommendation this publication is sending.                                | —                                                                                                                                                                        |
| `reactivate_paid_recommendation` ✎          | Restore a paid recommendation this publication archived. Fails if the sponsor archived it.     | —                                                                                                                                                                        |
| `recommend_free_recommendation` ✎           | Start recommending a newsletter from the directory for free (by its UUID).                     | —                                                                                                                                                                        |
| `archive_free_recommendation` ✎             | Soft-archive a free recommendation this publication sends or receives.                         | —                                                                                                                                                                        |
| `reactivate_free_recommendation` ✎          | Restore a free recommendation this publication archived. Fails if the other side archived it.  | —                                                                                                                                                                        |
| `request_free_recommendation_partnership` ✎ | Ask a newsletter to recommend you back (by its UUID).                                          | —                                                                                                                                                                        |
| `recommend_offer` ✎                         | Start recommending a brand offer from the directory (by its UUID).                             | —                                                                                                                                                                        |
| `archive_offer` ✎                           | Soft-archive an offer this publication has joined.                                             | —                                                                                                                                                                        |
| `reactivate_offer` ✎                        | Restore a joined offer this publication archived. Fails if the brand archived it.              | —                                                                                                                                                                        |
| `approve_partner_request` ✎                 | Approve a pending partner request (by its UUID), turning the applicant into an active partner. | `expand` (`recommending_publication`)                                                                                                                                    |
| `enable_signup_flow_stage` ✎                | Enable one signup-flow stage so new subscribers see it.                                        | `stage` (`recommendations`, `offers`, `survey`) — required                                                                                                               |
| `disable_signup_flow_stage` ✎               | Disable one signup-flow stage. At least one of recommendations or offers must remain enabled.  | `stage` (`recommendations`, `offers`, `survey`) — required                                                                                                               |
| `create_campaign` ✎                         | Create a tracking campaign link for emails.                                                    | `campaign_type` (required: `recommendations_hub`, `specific_recommendation`, `offers_hub`, `specific_offer`), `name`, `recommendation_uuid`, `offer_recommendation_uuid` |

## FAQ

**How is this different from the REST API?** The MCP server is a conversational front door to the same data as the API v3. The REST API is for code you write; the MCP server lets an AI assistant call those same capabilities on your behalf, choosing the right tool and stitching results together.

**Is my data safe?** The server only ever accesses the single account behind your OAuth sign‑in or API key, and all but fourteen tools are strictly read‑only. The write tools map to the same actions you can already take in the dashboard (recommending or archiving an entry, reactivating one this publication archived, approving a partner, enabling/disabling a signup-flow stage, or creating a campaign).

**Which protocol version does it use?** The server negotiates the MCP protocol version with your client automatically, so any current MCP client will connect.


# Client API

The Client API lets you build your own recommendations and offers experience on top of SparkLoop. Instead of rendering the managed signup-flow widget, the SparkLoop script exposes a small JavaScript SDK — `SL.client` — that your code calls to generate recommendations and offers, display them in your own UI, and report your readers' choices back to SparkLoop.

You keep full control of the markup, styling and flow. SparkLoop keeps handling everything behind the scenes: targeting, tracking, attribution and payouts.

{% hint style="info" %}
The Client API is in early beta and only enabled on request. Read [Early Access](/client-api/early-access) to see what to expect and apply.
{% endhint %}

### In this section

* [Early Access](/client-api/early-access) — who the Client API is for, what to expect from the beta and how to apply.
* [Getting Started](/client-api/getting-started) — load the script, turn on client mode and make your first call.
* [Objects](/client-api/objects) — the [Recommendation](/client-api/objects/recommendation) and [Offer](/client-api/objects/offer) objects the SDK returns.
* [Recommendations](/client-api/recommendations) — `generate`, `fetch` and `subscribe`.
* [Offers](/client-api/offers) — `generate`, `fetch` and `claim`.
* [Testing Your Integration](/client-api/testing-your-integration) — run every call in test mode while you build.
* [Errors & Warnings](/client-api/errors-and-warnings) — error handling and the rules you must follow.


# Early Access

The Client API is in early beta and only enabled on request. Read this page before you start building: it explains who the Client API is for, what to expect as a beta partner and how to apply.

### Do you need the Client API?

Most publications don't. The [Signup Flow](https://www.tella.tv/video/vid_cmqjp1z7j00nt0bj66dks4rqi/view) already shows recommendations, surveys and offers to new subscribers right after they sign up, with no code beyond the SparkLoop script. It's also customizable:

* Want to open it yourself after your own signup or survey step? Use [manual tracking](https://help.sparkloop.app/en/articles/5867950-manually-track-subscribers).
* Want to decide where and when it shows? Use [visibility rules](https://help.sparkloop.app/en/articles/6824529-visibility-rules).

Reach for the Client API when you need to render recommendations and offers in your own UI, with your own markup and flow.

### What to expect from the beta

The Client API lets you build SparkLoop recommendations and offers directly into your own website and signup flow. It's currently in early beta, so before you start, here's what you need to know as a beta partner:

* **It's a beta, so expect change.** The API will evolve over the coming weeks. We'll give you advance notice of any breaking changes, but we'll need you to update your integration within 24 hours of a change going out. If an integration can't be kept current, we may need to pause it until it is.
* **You own your integration.** Building, testing, monitoring and maintaining it is up to you and your team. If an integration breaks or starts sending problematic traffic into the network, we'll pause it until it's fixed. SparkLoop won't compensate you for any lost earnings.
* **Subscriber consent has to be genuine.** Readers must knowingly choose to subscribe or claim an offer, with no misleading copy or dark patterns.
* **This is the path forward.** Sending referrals through the REST API is being deprecated, so if you're currently integrating that way, you'll need to migrate to the Client API regardless.

### How to apply

Fill in the [early access form](https://forms.reform.app/sparkloop/client-api-early-access). We'll ask you for:

* Your publication.
* What you're building and why the Signup Flow isn't a fit.
* Every domain your integration will run on, including staging and test domains. A domain covers all of its subdomains, and calls from any domain not on your list are rejected.

### After approval

1. **Build and test in draft first.** We enable a draft integration on your domains so you can start building. Run every call in [test mode](/client-api/testing-your-integration) while you do.
2. **Record a demo.** When you're done, record a short screen recording of your flow in action, or share a URL where we can try it along with the steps to follow.
3. **We review before you go live.** Share your demo with us. We review the integration and activate it so you can make real referrals on your live website.

Next: [Getting Started](/client-api/getting-started).


# Getting Started

{% hint style="info" %}
You need early access before any call works. Apply through [Early Access](/client-api/early-access) first, then come back here.
{% endhint %}

### 1. Load the SparkLoop script

Add the SparkLoop embed script to your page, using your publication ID:

```html
<script async src="https://js.sparkloop.app/embed.js?publication_id=pub_YOUR_PUBLICATION_UUID" data-sparkloop></script>
```

### 2. Turn on client mode

Set the SparkLoop mode to `client` in its own script tag:

```html
<script>
  window.SL = { mode: "client" };
</script>
```

This script does not need to sit above the embed tag: the embed loads asynchronously and waits for `DOMContentLoaded` plus a short delay (800ms by default, configurable via `window.sparkloop_delay`) before it reads `window.SL.mode`.

In client mode SparkLoop never renders the managed signup-flow widget — your code is fully in charge of the UI.

### 3. Wait until SparkLoop is ready

Once the script has initialized, it fires an `sl:ready` event on `document`. That's your signal to start making calls:

```html
<script>
  window.SL = { mode: "client" };

  document.addEventListener("sl:ready", async () => {
    const recommendations = await SL.client.recommendations.generate({ limit: 5 });
    // render the recommendations in your UI
  });
</script>
```

### Test mode

While you build, turn on test mode so your calls don't count as real traffic: add `test_mode: true` to your config or `?test_mode=true` to your page's URL. You get the same responses, but nothing is recorded. Read more in [Testing Your Integration](/client-api/testing-your-integration).

### Using the npm package

If you bundle your frontend, you can install SparkLoop from npm instead of loading the script tag:

```
npm i sparkloop
```

Then initialize it with your publication ID and client mode — this replaces steps 1 and 2:

```js
import sparkloop from "sparkloop";

sparkloop("pub_YOUR_PUBLICATION_UUID", { mode: "client" });
```

Everything else works exactly the same: the `sl:ready` event fires on `document`, the SDK lives at `SL.client`, and any configuration option goes in the same options object — for example [test mode](/client-api/testing-your-integration):

```js
sparkloop("pub_YOUR_PUBLICATION_UUID", { mode: "client", test_mode: true });
```

### Next steps

* [Recommendations](/client-api/recommendations) — generate recommendations and subscribe your readers to them.
* [Offers](/client-api/offers) — generate offers and claim them.
* [Testing Your Integration](/client-api/testing-your-integration) — run every call in test mode while you build.
* [Errors & Warnings](/client-api/errors-and-warnings) — error handling and the rules you must follow.


# Objects

Every SDK method returns plain JavaScript objects. There are two of them:

* [Recommendation](/client-api/objects/recommendation) — a newsletter your readers can subscribe to.
* [Offer](/client-api/objects/offer) — a sponsored offer your readers can claim.

Objects are meant to be displayed right away and acted on immediately. Never cache or store them for later use — see [Errors & Warnings](/client-api/errors-and-warnings).


# Recommendation

A newsletter recommended to your reader. Returned by the [Recommendations methods](/client-api/recommendations).

| Field           | Type    | Description                                                                                                                                                                                  |
| --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uuid`          | string  | Unique identifier of the recommendation. Pass it back in `subscribe`.                                                                                                                        |
| `type`          | string  | `paid` or `free`. Paid recommendations earn you money for every confirmed referral.                                                                                                          |
| `name`          | string  | Name of the recommended newsletter.                                                                                                                                                          |
| `logo`          | string  | URL of the newsletter's logo.                                                                                                                                                                |
| `description`   | string  | Description of the recommended newsletter.                                                                                                                                                   |
| `recommendable` | boolean | Only present when fetching an individual recommendation. `true` while the recommendation can still be subscribed to. When `false`, don't display it — subscriptions to it will be discarded. |

### Example

```json
{
  "uuid": "partner_campaign_23ac0811bfff",
  "type": "paid",
  "name": "Create&Sell",
  "logo": "https://dash.sparkloop.app/rails/active_storage/representations/.../logo.png",
  "description": "Weekly, in-depth email marketing advice for creators."
}
```


# Offer

A sponsored offer your reader can claim. Returned by the [Offers methods](/client-api/offers).

| Field                 | Type    | Description                                                                                                                                                   |
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uuid`                | string  | Unique identifier of the offer. Pass it back in `claim`.                                                                                                      |
| `headline`            | string  | Headline of the offer.                                                                                                                                        |
| `description`         | string  | Description of the offer.                                                                                                                                     |
| `callout`             | string  | Short callout text highlighting the offer.                                                                                                                    |
| `call_to_action`      | string  | Text for the offer's call-to-action button.                                                                                                                   |
| `conversion_type`     | string  | `lead`, `click` or `action`. Determines how the offer is claimed — see below.                                                                                 |
| `brand_name`          | string  | Name of the brand behind the offer.                                                                                                                           |
| `brand_logo`          | string  | URL of the brand's logo.                                                                                                                                      |
| `landscape_image_url` | string  | URL of the offer's landscape creative.                                                                                                                        |
| `portrait_image_url`  | string  | URL of the offer's portrait creative.                                                                                                                         |
| `redirect_url`        | string  | Only present on `click` and `action` offers. URL to send the reader to when they claim the offer. Lead offers don't include it.                               |
| `recommendable`       | boolean | Only present when fetching an individual offer. `true` while the offer can still be claimed. When `false`, don't display it — claims to it will be discarded. |

### Conversion types

* **`lead`** — the reader claims the offer with their email address. No redirect involved.
* **`click`** — the reader claims the offer by clicking through to the offer's page via `redirect_url`.
* **`action`** — like `click`, but the reader converts by completing an action on the offer's page after the redirect.

### Example

```json
{
  "uuid": "offer_recommendation_9f8e7d6c5b4a",
  "headline": "Try Acme for free",
  "description": "The all-in-one toolkit for newsletter operators.",
  "callout": "Limited time",
  "call_to_action": "Claim your free trial",
  "conversion_type": "click",
  "brand_name": "Acme",
  "brand_logo": "https://dash.sparkloop.app/rails/active_storage/representations/.../logo.png",
  "landscape_image_url": "https://dash.sparkloop.app/rails/active_storage/representations/.../landscape.png",
  "portrait_image_url": "https://dash.sparkloop.app/rails/active_storage/representations/.../portrait.png",
  "redirect_url": "https://offer.example.com/?..."
}
```


# Recommendations

Recommendation methods live under `SL.client.recommendations`. They all return promises and work with the [Recommendation object](/client-api/objects/recommendation).

The expected flow is a single, short-lived pass: `generate` (or `fetch`) → display → `subscribe`. Keep the time between generating and subscribing as short as possible.

{% hint style="warning" %}
Never cache or store recommendations. If a recommendation is no longer recommendable when your `subscribe` call arrives, it is discarded — see [Errors & Warnings](/client-api/errors-and-warnings).
{% endhint %}

### generate({ limit })

Generates fresh recommendations for the current reader.

* `limit` *(optional)* — number of recommendations to generate. Defaults to `5`, maximum `50`.

Returns an array of [Recommendation](/client-api/objects/recommendation) objects.

```js
const recommendations = await SL.client.recommendations.generate({ limit: 5 });
```

{% hint style="warning" %}
Only generate the number of recommendations you're actually going to display. Generating more than you show inflates your metrics and hurts your performance.
{% endhint %}

### fetch({ uuid })

Fetches a single recommendation by its uuid.

* `uuid` *(required)* — the recommendation's uuid.

Returns a single [Recommendation](/client-api/objects/recommendation) object, including the `recommendable` field.

Use `fetch` to spotlight a specific recommendation — for example, one that is contextual to the content surrounding it. It is not meant as an extra step between `generate` and `subscribe`: recommendations you just generated can be subscribed to directly.

Always check `recommendable` before displaying a fetched recommendation. When it's `false`, don't show it — subscriptions to it will be discarded.

```js
const recommendation = await SL.client.recommendations.fetch({ uuid: "partner_campaign_23ac0811bfff" });

if (recommendation.recommendable) {
  // render the recommendation in your UI
}
```

### subscribe({ email, uuids, shownUuids })

Subscribes the reader to the recommendations they picked.

* `email` *(required)* — the reader's email address.
* `uuids` *(required)* — array of uuids of the recommendations the reader picked.
* `shownUuids` *(optional)* — array of uuids of the recommendations the reader actually saw. We strongly recommend sending it: it tells SparkLoop which recommendations actually got in front of your readers, which improves what gets recommended next and maximizes your earnings.

Returns `{ response: "ok" }`.

```js
await SL.client.recommendations.subscribe({
  email: "reader@example.com",
  uuids: pickedUuids,
  shownUuids: displayedUuids,
});
```

{% hint style="danger" %}
Readers must explicitly choose the recommendations you subscribe them to. Misleading readers into subscribing without their consent results in a permanent block of your SparkLoop account.
{% endhint %}

### Tracking shown recommendations

`shownUuids` should contain the recommendations the reader actually saw — not everything you rendered. The simplest reliable way to measure that is an `IntersectionObserver`: count a recommendation as shown once at least half of its card has been visible in the viewport.

```js
const shownUuids = new Set();

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue;
    shownUuids.add(entry.target.dataset.recommendationUuid);
    observer.unobserve(entry.target);
  }
}, { threshold: 0.5 });

// After rendering, tag each card with its uuid and observe it
document.querySelectorAll("[data-recommendation-uuid]").forEach((el) => observer.observe(el));

// When the reader submits
await SL.client.recommendations.subscribe({
  email,
  uuids: pickedUuids,
  shownUuids: [...shownUuids],
});
```


# Offers

Offer methods live under `SL.client.offers`. They all return promises and work with the [Offer object](/client-api/objects/offer).

The expected flow is a single, short-lived pass: `generate` (or `fetch`) → display → `claim`. Keep the time between generating and claiming as short as possible.

{% hint style="warning" %}
Never cache or store offers. If an offer is no longer recommendable when your `claim` call arrives, it is discarded — see [Errors & Warnings](/client-api/errors-and-warnings).
{% endhint %}

### generate()

Generates fresh offers for the current reader. Takes no arguments.

Returns an array of [Offer](/client-api/objects/offer) objects.

```js
const offers = await SL.client.offers.generate();
```

### fetch({ uuid })

Fetches a single offer by its uuid.

* `uuid` *(required)* — the offer's uuid.

Returns a single [Offer](/client-api/objects/offer) object, including the `recommendable` field.

Use `fetch` to spotlight a specific offer — for example, one that is contextual to the content surrounding it. It is not meant as an extra step between `generate` and `claim`: offers you just generated can be claimed directly.

Always check `recommendable` before displaying a fetched offer. When it's `false`, don't show it — claims to it will be discarded.

```js
const offer = await SL.client.offers.fetch({ uuid: "offer_recommendation_9f8e7d6c5b4a" });

if (offer.recommendable) {
  // render the offer in your UI
}
```

### claim({ email, uuid })

Claims an offer for the reader.

* `uuid` *(required)* — the offer's uuid.
* `email` — the reader's email address. Required for `lead` offers, not required for `click` or `action` offers.

The return value depends on the offer's `conversion_type`:

* `lead` offers return `{ response: "ok" }`.
* `click` and `action` offers return `{ redirect_url }`.

```js
// Lead offer
await SL.client.offers.claim({ email: "reader@example.com", uuid: offer.uuid });

// Click or action offer
window.open(offer.redirect_url, "_blank");
SL.client.offers.claim({ uuid: offer.uuid });
```

{% hint style="info" %}
For `click` and `action` offers, the redirect URL is already included in the [Offer object](/client-api/objects/offer). Browsers only allow opening a new tab for a short moment after a click, so don't make the reader wait for a network roundtrip: redirect or open the new tab immediately using the offer's `redirect_url`, then call `claim` — it's what registers the claim with SparkLoop.
{% endhint %}

{% hint style="danger" %}
Readers must explicitly choose the offers you claim for them. Misleading readers into claiming offers without their consent results in a permanent block of your SparkLoop account.
{% endhint %}


# Testing Your Integration

While you build, run every call in test mode. When test mode is on, the SDK flags every call it makes, and SparkLoop returns the same responses as in real traffic without recording anything — no impressions, no subscriptions, no claims.

That makes it safe to exercise the full flow end to end: generate or fetch, render your UI, and subscribe or claim as many times as you need.

There are two ways to turn it on:

### In your SparkLoop config

Set `test_mode` alongside the client mode:

```html
<script>
  window.SL = { mode: "client", test_mode: true };
</script>
```

### As a URL parameter

Add `?test_mode=true` to the URL of the page where your integration runs — handy for a one-off dry run without touching your code:

```
https://yoursite.com/signup?test_mode=true
```

{% hint style="warning" %}
Use test mode for all development and automated testing, including anything an AI agent runs on your behalf. Calls made without it count as real traffic and pollute your data and your partners' data.
{% endhint %}


# Errors & Warnings

### Error handling

When a call fails, its promise rejects with a `ClientApiError` carrying three properties:

* `error` — machine-readable error code.
* `message` — human-readable explanation.
* `status` — the HTTP status of the failed request.

```js
try {
  await SL.client.recommendations.subscribe({ email, uuids });
} catch (error) {
  if (error.name === "ClientApiError") {
    console.error(error.error, error.message);
  }
}
```

The errors you're most likely to run into:

* `bad_request` (400) — a required argument is missing or invalid: a missing or malformed `email`, or a missing `uuid` or `uuids`.
* `not_found` (404) — the `uuid` you passed doesn't match any of your recommendations or offers.
* `forbidden` (403) — the Client API is not enabled for your publication, or the page's origin is not registered as an allowed origin. See [Early Access](/client-api/early-access) to get it enabled or add a domain.

If the SparkLoop script itself fails to initialize, `SL.client` methods reject with a regular `Error` instead of a `ClientApiError`.

### Subscribe and claim only with consent

{% hint style="danger" %}
Readers must explicitly choose the recommendations you subscribe them to and the offers you claim for them, without being misled in any way. Failure to comply results in a permanent block of your SparkLoop account.
{% endhint %}

### Never cache recommendations or offers

{% hint style="warning" %}
Recommendations and offers are generated for a specific reader at a specific moment, and their availability changes continuously. If a recommendation or offer is no longer recommendable by the time your `subscribe` or `claim` call arrives, it is discarded: the call still succeeds, but nothing is registered and nothing is earned.
{% endhint %}

To stay safe:

* Never cache or store recommendations or offers — not even for a few hours.
* `generate` right before displaying, and `subscribe`/`claim` right after the reader acts.
* When spotlighting an individual recommendation or offer, `fetch` it right before displaying it and check its `recommendable` field.


# Webhooks

## Webhooks

Webhooks are used to send events from SparkLoop to your server. We send a `POST HTTP` request with a JSON payload when specific events occur.

### Create a webhook

To create a webhook:

* go to your [*Account Settings > Integrations*](https://dash.sparkloop.app/settings/integrations)
* enter the URL of your webhook and choose which events you want to receive

<figure><img src="https://307340109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvjCB8kc4yvMRscfT0m%2Fuploads%2FJGbn5JEDnDI6osBst4Zm%2Fimage.png?alt=media&#x26;token=9cac738b-4945-499f-896a-597e9ba30f88" alt=""><figcaption></figcaption></figure>

### Update/delete a webhook

To update or delete a webhook hover over the three dots to the right of the webhook to open the settings menu and click on the link to update or delete.

<figure><img src="https://307340109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvjCB8kc4yvMRscfT0m%2Fuploads%2F6KQi5p6foitetARyDgPI%2Fimage.png?alt=media&#x26;token=05ff05c8-088b-4f35-af64-363de029524f" alt=""><figcaption></figcaption></figure>

### Test a webhook

To test a webhook hover over the three dots to the right of the webhook to open the settings menu and click on "Send test webhook"

![](https://307340109-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LvjCB8kc4yvMRscfT0m%2Fuploads%2FhoH0v7BKT7IWilrJaSTm%2Fimage.png?alt=media\&token=dfbe0caf-7a61-48ff-ad6c-1a338512de7d)

### Authentication token

SparkLoop authenticates all webhooks with a token in the `SparkLoop-Token` request header. This allows you to verify that the events were sent by SparkLoop, not by a third party.

While this is entirely optional, it adds an extra layer of security: simply check the token in the `SparkLoop-Token` header is the same as your authentication token. If not, ignore the webhook!

You can find your authentication token in [your Webhooks page](https://dash.sparkloop.app/settings/integrations).

### Events

#### **new\_referral**

Is sent when a new referral made by a subscriber in a referral program is tracked

```json
{
  "type": "new_referral",
  "campaign_id": "MF6319db9890",
  "subscriber": {
    "id": "sub_hjdgc74u",
    "first_name": "John",
    "name": "John Doe",
    "email": "john.doe@email.com",
    "referral_code": "ada90f39",
    "referral_link": "https://mywebsite.com?rh_ref=ada90f39",
    "referred": true,
    "tot_referrals": 0,
    "next_reward": 2,
    "created_at": 1564400550
  },
  "referrer": {
    "id": "sub_93ehus8h",
    "first_name": "John",
    "name": "John Smith",
    "email": "john.smith@email.com",
    "referral_code": "f9464199",
    "referral_link": "https://mywebsite.com?rh_ref=f9464199",
    "referred": false,
    "tot_referrals": 4,
    "next_reward": 1,
    "created_at": 1564403873
  }
}
```

#### **new\_partner\_pending\_referral**

Is sent when a referral made by a partner is starts the screening period

```json
{
  "type": "new_partner_pending_referral",
  "campaign": {
    "id": "partner_campaign_de9c1c7c7bbc",
    "referral_code": "1b5b5c4094",
    "type": "partner_program",
    "upscribe": true,
    "partner_program_id": "partner_program_5b925f623c28"
    "publication_id": "pub_a6db3f666c55",
    "created_at": 1658780774
  },
  "subscriber": {
    "id": "sub_042448acb02a",
    "first_name": null,
    "name": null,
    "email": "test12345@test.com",
    "referral_code": "6363a0b5",
    "referred": true,
    "tot_referrals": 0,
    "referral_status": "pending",
    "last_reward": null,
    "partner_id": "partner_0a8a45c403f8",
    "created_at": 1659536359
  },
  "partner": {
    "id": "partner_0a8a45c403f8",
    "name": "Jason Resnickss",
    "logo": "http://dash.sparkloop.app/rails/active_storage/representations/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBPQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--36ac8d234a450db1db32c62fe9e399b29a9e42f0/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9VY21WemFYcGxYM1J2WDJ4cGJXbDBXd2RwQVpacEFaWT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--b97dd663f95aeeb4752185ef091f0728b0f28f4a/active_campaign.png"
  }
}
```

#### **new\_partner\_referral**

Is sent when a referral made by a partner is confirmed

```json
{
  "type": "new_partner_referral",
  "campaign": {
    "id": "partner_campaign_de9c1c7c7bbc",
    "referral_code": "1b5b5c4094",
    "type": "partner_program",
    "upscribe": true,
    "partner_program_id": "partner_program_5b925f623c28"
    "publication_id": "pub_a6db3f666c55",
    "created_at": 1658780774
  },
  "subscriber": {
    "id": "sub_042448acb02a",
    "first_name": null,
    "name": null,
    "email": "test12345@test.com",
    "referral_code": "6363a0b5",
    "referred": true,
    "tot_referrals": 0,
    "referral_status": "verified",
    "last_reward": null,
    "partner_id": "partner_0a8a45c403f8",
    "created_at": 1659536359
  },
  "partner": {
    "id": "partner_0a8a45c403f8",
    "name": "Jason Resnickss",
    "logo": "http://dash.sparkloop.app/rails/active_storage/representations/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBPQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--36ac8d234a450db1db32c62fe9e399b29a9e42f0/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9VY21WemFYcGxYM1J2WDJ4cGJXbDBXd2RwQVpacEFaWT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--b97dd663f95aeeb4752185ef091f0728b0f28f4a/active_campaign.png"
  }
}
```

#### **new\_offer\_lead**

Is sent when a new lead is confirmed

```json
{
  "type": "new_offer_lead",
  "offer_id": "offer_de9c1c7c7bbc",
  "lead": {
    "id": "offer_conversion_an324nf332f",
    "email": "test12345@test.com",
    "source": "sparkloop-upscribe",
    "partner_id": "partner_0a8a45c403f8",
    "referral_status": "confirmed",
    "offer_recommendation_id": "offer_recommendation_dsfhgfb476f,
    "created_at": 1659536359,
    "referred_at": 1659536359
  },
  "partner": {
    "id": "partner_0a8a45c403f8",
    "name": "Jason Resnickss",
    "logo": "http://dash.sparkloop.app/rails/active_storage/representations/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBPQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--36ac8d234a450db1db32c62fe9e399b29a9e42f0/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCam9VY21WemFYcGxYM1J2WDJ4cGJXbDBXd2RwQVpacEFaWT0iLCJleHAiOm51bGwsInB1ciI6InZhcmlhdGlvbiJ9fQ==--b97dd663f95aeeb4752185ef091f0728b0f28f4a/active_campaign.png"
  }
}
```

#### **new\_opportunity**

Is sent when an enriched subscriber's sponsorship opportunity score first reaches the opportunity threshold (70 or higher). SparkLoop sends this webhook only once per subscriber — when they first qualify — not on subsequent score updates.

You can enable this event from **Account Settings > Integrations** or from the **Opportunities** page in your dashboard.

```json
{
  "type": "new_opportunity",
  "publication_id": "publication_123",
  "opportunity": {
    "score": 85,
    "detected_at": "2026-07-09T10:00:00Z"
  },
  "person": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "job_title": "Head of Marketing",
    "seniority": "Executive",
    "gender": "Female",
    "age_range": "35-44",
    "income_level": "$150k-$200k",
    "linkedin": "https://linkedin.com/in/janedoe",
    "twitter": null,
    "facebook": null,
    "github": null
  },
  "company": {
    "name": "Acme Inc",
    "domain": "acme.com",
    "industry": "Software",
    "employees_range": "51-200",
    "revenue_range": "$10M-$50M"
  }
}
```

The payload includes the same person, company, and score fields as the Opportunities CSV export. Internal scoring reasoning is not included. The `company` object may be omitted if no company data is available.

#### new\_survey\_response

Is sent when a subscriber completes a Welcome Survey. SparkLoop sends this webhook for every new survey submission.

```json
{
  "type": "new_survey_response",
  "publication_id": "publication_123",
  "survey_id": "survey_123",
  "email": "jane@example.com",
  "submitted_at": "2026-07-15T10:00:00Z",
  "responses": [
    {
      "label": "Full name",
      "value": "Jane Doe"
    },
    {
      "label": "Company name",
      "value": "Acme Inc"
    }
  ]
}
```

#### **reward\_unlocked** <a href="#reward_unlocked" id="reward_unlocked"></a>

Is sent when a subscriber hits a milestone and unlocks a reward

```json
{
  "type": "reward_unlocked",
  "campaign_id": "MF6319db9890",
  "referrals": 4,
  "coupon_code": "9wusha0",
  "subscriber": {
    "id": "sub_hjdgc74u",
    "first_name": "John",
    "name": "John Doe ",
    "email": "john.doe@email.com",
    "referral_code": "f9464199",
    "referral_link": "https://mywebsite.com?rh_ref=f9464199",
    "referred": true,
    "tot_referrals": 4,
    "next_reward": 2,
    "created_at": 1564403873
  },
  "reward": {
    "id": "reward_cdebf4f013",
    "name": "2018 Private Report: State of the Ecommerce",
    "referrals": 4,
    "recurring": false
  }
}
```

#### **reward\_redeemed** <a href="#reward_unlocked" id="reward_unlocked"></a>

Is sent when a subscriber redeems a reward by submitting their address details.

```json
{
  "type": "reward_redeemed",
  "campaign_id": "MF6319db9890",
  "referrals": 4,
  "coupon_code": "9wusha0",
  "subscriber": {
    "id": "sub_hjdgc74u",
    "first_name": "John",
    "name": "John Doe ",
    "email": "john.doe@email.com",
    "referral_code": "f9464199",
    "referral_link": "https://mywebsite.com?rh_ref=f9464199",
    "referred": true,
    "tot_referrals": 4,
    "next_reward": 2,
    "created_at": 1564403873
  },
  "reward": {
    "id": "reward_cdebf4f013",
    "name": "2018 Private Report: State of the Ecommerce",
    "referrals": 4,
    "recurring": false
  },
  "reward_variant_name": "T-Shirt XXL",
  "reward_variant_sku": "t_shirt_xxl",
  "shipping_details": {
    "shipping_name": "John Doe",
    "shipping_phone": "+01 407-889-2040",
    "shipping_address_1": "7838 Pearl Drive",
    "shipping_address_2": null,
    "shipping_city": "Sun City",
    "shipping_state_code": "AZ",
    "shipping_zipcode": "85351",
    "shipping_country_code": "US"
  }
}
```

#### **sync\_subscriber** <a href="#sync_subscriber" id="sync_subscriber"></a>

Is sent when a referral is created or updated.

```json
{
  "type": "sync_subscriber",
  "subscriber": {
    "id": "sub_hjdgc74u",
    "first_name": "John",
    "name": "John Doe ",
    "email": "john.doe@email.com",
    "referral_code": "f9464199",
    "referral_link": "https://mywebsite.com?rh_ref=f9464199",
    "referred": true,
    "tot_referrals": 4,
    "referral_status": "rejected",
    "next_reward": 2,
    "created_at": 1564403873
  }
}
```

### Errors

Please send back a blank response with a status code of `200`.\
All not-200 responses will be considered errors.


# Postbacks

How to confirm Action offers via postbacks

When you create an Action offer, conversions must be explicitly confirmed after the user completes the desired action on your website (e.g., signs up for a trial, makes a purchase, downloads a resource).\
​\
This guide explains how to confirm Action conversions using a postback.\
​\
**​How It Works**

1. User clicks offer link
2. User is redirected to your website → You capture the Click ID parameter
3. User completes the action -> Purchase/Signup/Download/etc..
4. You call the postback URL → Confirm the conversion with SparkLoop
5. Conversion is processed

***

### Step 1: Capture the Tracking Parameters <a href="#h_1639a94dd2" id="h_1639a94dd2"></a>

When SparkLoop redirects users to your website, we append a `sl_click_id` query parameter to your redirect URL.

If your offer URL is `https://example.com/signup`, the user will be redirected to:\
​\
​`https://example.com/signup?sl_click_id=ABC---submission_1234abcd`\
​\
​Important: You must capture and store the `sl_click_id` parameter when the user lands on your site. You'll need it to confirm the conversion later.

***

### Step 2: ﻿Confirm the Conversion <a href="#h_91e03547bb" id="h_91e03547bb"></a>

Once the user completes your desired action, send a call to your unique postback URL.

```
GET https://postbacks.sparkloop.app/{:UNIQUE_TOKEN}?sl_click_id={sl_click_id}
```

You'll find your unique postback URL under your offer's "Conversion Pixel" instructions.

Replace  `{sl_click_id}` with the `sl_click_id` parameter from Step 1.

***

Important Notes

* Action conversions expire after 45 days if not confirmed
* Make sure to confirm conversions as soon as the user completes your desired action


# API v2 (Deprecated)

## API v2

{% hint style="warning" %}
**API v2 is being deprecated.** API v3 is now available and is the recommended way to build on SparkLoop. v2 will keep working for now, but new integrations should use v3 — v2 will be retired soon.
{% endhint %}

### Overview

Calls for SparkLoop API v1 are relative to the url `https://api.sparkloop.app/v2`

API v2 is in active development. Whilst we won't add any breaking changes, expect new endpoint to be added at any time.

### Auth

You can find your API key under your [Account Page](https://dash.sparkloop.app/settings/integrations).

#### Using the API key

Every request sent to Sparkloop API must contain the `X-Api-Key` header with the value set to your API key. Example:

```bash
curl -H "X-Api-Key: 12cf8cf8538c3685b0f36308" https://api.sparkloop.app/v1/campaigns
```

{% hint style="danger" %}
If you forget to include the X-Api-Key header or use the wrong API key, API will respond with 401 HTTP status code.
{% endhint %}

### Pagination

For any endpoint that can return multiple objects, e.g. **/subscribers, /partner\_programs** etc. pagination can be used.

Pagination params are sent as query parameters — **page** and **per\_page**

**per\_page** — how many objects you want to fetch. The default is 50, maximum is 200.

***page** —* what page you want to fetch. The default is 1.

### Requests

For every POST and PUT request set the *Content-Type* header to:

`Content-Type: application/json; charset=utf-8`

### Responses

When an API call succeeds, the API will return a 200 or 201 HTTP response and a JSON response body unless otherwise noted.

If there's an error, the API will return an HTTP response in the 400 or 500 range and a response body indicating what the error was. For example:

`{ error: "Subscriber uuid blank!"}` with a 400 error.

### **Rate limiting**

Our rate limit is no more than 120 requests over a rolling 60 second period, for a given api key.

If your request rate exceeds our limits, you will receive a 429 response, which your code should gracefully handle. We recommend spacing out your requests and performing an [exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff) to keep within the limit.

### **Internal server errors**

If the server is overloaded or you encounter a bug, you will get a 500 error. Try again after a short period, and if you continue to encounter an error, please raise the issue with support.


# Endpoints

{% content-ref url="/pages/x1DZZ3vDRLDybmOnG84c" %}
[Subscribers](/api-v2-deprecated/endpoints/subscribers)
{% endcontent-ref %}

{% content-ref url="/pages/IMEG5xjAsr39hpwXJGaJ" %}
[Referral Campaigns](/api-v2-deprecated/endpoints/campaigns)
{% endcontent-ref %}

{% content-ref url="/pages/vfWvqAHiXYDZayJAmlPc" %}
[Partner Profile](/api-v2-deprecated/endpoints/partner-profile)
{% endcontent-ref %}

{% content-ref url="/pages/hJkNsBXW23J1J4J2Hhaf" %}
[Publications](/api-v2-deprecated/endpoints/subscribers-1)
{% endcontent-ref %}

{% content-ref url="/pages/mEPbPoq5abkaSeDgq8tA" %}
[Upscribe](/api-v2-deprecated/endpoints/upscribe)
{% endcontent-ref %}

{% content-ref url="/pages/JBIedqjXzyabOwBGoGZ1" %}
[Partner Programs](/api-v2-deprecated/endpoints/subscribers-2)
{% endcontent-ref %}

{% content-ref url="/pages/r21sCQzw9vVZvc81wtba" %}
[Publications](/api-v2-deprecated/endpoints/partner-network/publications)
{% endcontent-ref %}

{% content-ref url="/pages/chbaM6i799LLZRIcdfKG" %}
[Webhooks](/api-v2-deprecated/endpoints/webhooks)
{% endcontent-ref %}

{% content-ref url="/pages/A58fTt9vPCQsN064BS3f" %}
[Offers](/api-v2-deprecated/endpoints/offers)
{% endcontent-ref %}


# Subscribers

## Create a subscriber

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/subscribers`

#### Request Body

| Name                                    | Type   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| --------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| email<mark style="color:red;">\*</mark> | string | Email                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| name                                    | string | Name                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| referrer\_code                          | string | The referral code of the person who referred this subscriber. If Joanne with ref\_code ***joanne123*** referred this subscriber, you should set this value to ***joanne123***                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ip\_address                             | string | IP Address                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| user\_agent                             | string | User Agent                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| utm\_source                             | string | UTM Source                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| utm\_campaign                           | string | UTM Campaign                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| created\_at                             | string | <p>Timestamp when subscriber signed up. You can set this value to a date in the past.<br><br>Expected format: <br><em><strong>YYYY-MM-DDThh:mm:ssTZD</strong></em><br><em><strong>e</strong></em>.<em><strong>g. 2021-03-22T19:20:30+01:00</strong></em><br><br>Where:<br>YYYY = four-digit year<br>MM    = two-digit month (01=January, etc.)<br>DD      = two-digit day of month (01 through 31)<br>hh       = two digits of hour (00 through 23) <br>mm     = two digits of minute (00 through 59)<br>ss        = two digits of second (00 through 59)<br>s          = one or more digits representing a decimal fraction of a second<br>TZD     = time zone designator (Z or +hh:mm or -hh:mm)</p> |
| country\_code                           | string | Alpha-2 ountry code of the subscriber                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

{% tabs %}
{% tab title="201: Created " %}

```
{
    "subscriber": <Subscriber Object>
}
```

{% endtab %}

{% tab title="400 " %}
{% tabs %}
{% tab title="Email taken" %}

```
{
    "error": "Email has already been taken"
}
```

{% endtab %}

{% tab title="Email invalid" %}

```
{
    "error": "Email is invalid"
}
```

{% endtab %}

{% tab title="Email not provided" %}

```
{
    "error": "You have to provide at least an email"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Get subscribers

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/subscribers`

Subscribers are ordered in desceding order by *created\_at*, meaning the first page shows the latest subscribers.

#### Query Parameters

| Name   | Type   | Description                                                                                            |
| ------ | ------ | ------------------------------------------------------------------------------------------------------ |
| type   | string | Type of subscribers you want to get. Default is *all*, allowed values are *all, referrals, advocates.* |
| expand | string | If you pass "campaigns" the response will include referral\_programs data                              |

{% tabs %}
{% tab title="200 Used query params ?page=2\&per\_page=2" %}

```json
{
  "subscribers": [
    <Subscriber Object>,
    <Subscriber Object>,
    <Subscriber Object>,
  ],
  "meta": {
    "per_page": 2,
    "page": 2,
    "total_pages": 3,
    "total_subscribers": 6
  }
}
```

{% endtab %}
{% endtabs %}

## Get a subscriber

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/subscribers/:identifier`

Returns a single subscriber by email address or UUID

#### Path Parameters

| Name                                         | Type   | Description                        |
| -------------------------------------------- | ------ | ---------------------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Subscriber's UUID or email address |

#### Query Parameters

| Name   | Type   | Description                                                               |
| ------ | ------ | ------------------------------------------------------------------------- |
| expand | String | If you pass "campaigns" the response will include referral\_programs data |

{% tabs %}
{% tab title="200 " %}

```json
{
  "subscriber": <Subscriber Object>
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Subscriber not found" %}

```
{
    "error": "Subscriber sub_4bc10562d97 not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Update subscriber

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/subscribers/:identifier`

Update a subscriber's name, email address or status

#### Path Parameters

| Name                                         | Type   | Description                        |
| -------------------------------------------- | ------ | ---------------------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Subscriber's UUID or email address |

#### Request Body

<table><thead><tr><th>Name</th><th width="60">Type</th><th>Description</th></tr></thead><tbody><tr><td>status</td><td>string</td><td>Subscriber's status. Allowed values are <em><strong>unsubscribed</strong></em> and <em><strong>confirmed</strong></em>.</td></tr><tr><td>name</td><td>string</td><td>Subscriber's name.</td></tr><tr><td>email</td><td>string</td><td>Subscriber's email address.</td></tr></tbody></table>

{% tabs %}
{% tab title="200 Returns the subscriber." %}

```
{
    "subscriber": <Subscriber Object>
}
```

{% endtab %}

{% tab title="400 When you try setting status to something other than unsubscribed or confirmed." %}
{% tabs %}
{% tab title="Unsupported status" %}

```
{
    "error": "Invalid status!"
}
```

{% endtab %}

{% tab title="Empty PUT body" %}

```
{
    "error": "You have to update at least 1 field."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Unsubscribe subscriber

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/subscribers/:identifier/unsubscribe`

Unsubscribe a subscriber

#### Path Parameters

| Name                                         | Type   | Description                        |
| -------------------------------------------- | ------ | ---------------------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Subscriber's UUID or email address |

{% tabs %}
{% tab title="200" %}

```
{
    "subscriber": <Subscriber Object>
}
```

{% endtab %}

{% tab title="409" %}
Subscriber is not pending

<pre><code><strong>{
</strong>    "error": "Subscriber subscriber_fsd62dr6 has already been unsubscribed"
}
</code></pre>

{% endtab %}
{% endtabs %}

## Reject subscriber

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/subscribers/:identifier/reject`

Reject a pending subscriber (acquired via partner program or referral program)

#### Path Parameters

| Name                                         | Type   | Description                        |
| -------------------------------------------- | ------ | ---------------------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Subscriber's UUID or email address |

{% tabs %}
{% tab title="200" %}

```
{
    "subscriber": <Subscriber Object>
}
```

{% endtab %}

{% tab title="409" %}
Subscriber is not pending

<pre><code><strong>{
</strong>    "error": "Subscriber subscriber_fsd62dr6 must be pending"
}
</code></pre>

{% endtab %}
{% endtabs %}


# Events

## Create an event

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/subscribers/:identifier/events`

#### Path Parameters

| Name                                         | Type   | Description                        |
| -------------------------------------------- | ------ | ---------------------------------- |
| identifier<mark style="color:red;">\*</mark> | String | Subscriber's UUID or email address |

#### Request Body

| Name                                             | Type   | Description                                                                           |
| ------------------------------------------------ | ------ | ------------------------------------------------------------------------------------- |
| event<mark style="color:red;">\*</mark>          | Object | An object whose fields are campaign\_uuid, value, and description.                    |
| value<mark style="color:red;">\*</mark>          | float  | Monetary value of the event. For example 23.9                                         |
| description                                      | string | Description of the event. For internal use only. Eg: "Purchase of Premium membership" |
| campaign\_uuid<mark style="color:red;">\*</mark> | String | Referral campaign UUID, e.g. MF9a78983d1c.                                            |

{% tabs %}
{% tab title="201: Created " %}

```json
{
    "event": {
        "value": 1490,    
        "subscriber_uuid": "sub_3138d33419",
        "created_at": "2021-11-29T10:14:20.955Z"
    }
}
```

{% endtab %}

{% tab title="400 " %}
{% tabs %}
{% tab title="Subscriber UUID missing" %}

```
{
    "error": "Subscriber uuid or email blank!"
}
```

{% endtab %}

{% tab title="Value missing" %}

```
{
    "error": "You have to provide a value"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="404: Not Found " %}

```javascript
{
    "error": "Subscriber john.doe@email.com not found!"
}
```

{% endtab %}
{% endtabs %}


# Referral Campaigns

Endpoints that allow you to work with your referral campaigns.

## Get Campaigns

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/campaigns`

Returns all of your campaigns. At the moment you're only allowed to have one campaign per Sparkloop account.

{% tabs %}
{% tab title="200 Campaigns successfully retrieved." %}

```json
{ 
  "campaigns": [
    <Campaign Object>,
    <Campaign Object>,
    <Campaign Object>
  ]
}
```

{% endtab %}

{% tab title="401 " %}
{% tabs %}
{% tab title="Missing API key" %}

```
{ "error": "API key is missing!" }
```

{% endtab %}

{% tab title="User not found for API key" %}

```
{ "error": "User not found!" }
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Get Campaign

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/campaigns/:identifier`

Returns a campaign.

#### Path Parameters

| Name                                         | Type   | Description                               |
| -------------------------------------------- | ------ | ----------------------------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Campaign's UUID, for example MF9a78983d1c |

#### Headers

| Name                                        | Type   | Description |
| ------------------------------------------- | ------ | ----------- |
| X-Api-Key<mark style="color:red;">\*</mark> | string | API key     |

{% tabs %}
{% tab title="200 " %}

```json
{ 
  "campaign": <Campaign Object>
}
```

{% endtab %}

{% tab title="401 " %}
{% tabs %}
{% tab title="Missing API key" %}

```
{ "error": "API key is missing!" }
```

{% endtab %}

{% tab title="User not found for API key" %}

```
{ "error": "User not found!" }
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="404 " %}

```
{ "error": "Campaign MF9a78983d1c not found!"}
```

{% endtab %}
{% endtabs %}

## Update Campaign

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/campaigns/:identifier`

Updates the campaign

#### Path Parameters

| Name                                         | Type   | Description                               |
| -------------------------------------------- | ------ | ----------------------------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Campaign's UUID, for example MF9a78983d1c |

#### Headers

| Name                                        | Type   | Description |
| ------------------------------------------- | ------ | ----------- |
| X-Api-Key<mark style="color:red;">\*</mark> | string | API key     |

#### Request Body

| Name | Type   | Description     |
| ---- | ------ | --------------- |
| name | string | Campaign's name |

{% tabs %}
{% tab title="200 " %}

```json
{
  "campaign": <Campaign Object>
}
```

{% endtab %}

{% tab title="401 " %}
{% tabs %}
{% tab title="Missing API key" %}

```
{ "error": "API key is missing!" }
```

{% endtab %}

{% tab title="User not found for API key" %}

```
{ "error": "User not found!" }
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="404 " %}

```
{ "error": "Campaign MF9a78983d1c not found!"}
```

{% endtab %}
{% endtabs %}


# Campaign Memberships

Campaign Memberships handle the relationship between subscribers and referral programs. For example, to add a subscriber to a referral program you must create a Campaign Membership relationship.

## Create a campaign membership

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/campaigns/:identifier/campaign_memberships`

Use to endpoint to add a subscriber to a referral program

#### Path Parameters

| Name                                          | Type   | Description                                |
| --------------------------------------------- | ------ | ------------------------------------------ |
| :identifier<mark style="color:red;">\*</mark> | String | Referral campaign UUID, e.g. MF9a78983d1c. |

#### Request Body

| Name                                               | Type   | Description                        |
| -------------------------------------------------- | ------ | ---------------------------------- |
| subscriber\_uuid<mark style="color:red;">\*</mark> | string | Subscriber's UUID or email address |

{% tabs %}
{% tab title="201: Created " %}

```json
{
    "campaign_membership": {
        "uuid": "membership_0cf7ec1e76d1",
        "campaign_uuid": "MF3c31fb034e",
        "subscriber_uuid": "sub_509eafef8d9d",
        "created_at": "2022-12-19T14:45:30.593Z"
    }
}
```

{% endtab %}

{% tab title="400 " %}
{% tabs %}
{% tab title="Campaign Membership already present" %}

```
{
    "error": "manuel@sparkloop.app has already been added to this campaign"
}
```

{% endtab %}

{% tab title="Subscriber not found" %}

```
{
    "error": "manuel@sparkloop not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Delete a campaign membership.

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v2/campaigns/:identifier/campaign_memberships/:uuid`

Use this endpoint to remove a subscriber from a campaign.

#### Path Parameters

| Name                                          | Type   | Description                                                                  |
| --------------------------------------------- | ------ | ---------------------------------------------------------------------------- |
| :identifier<mark style="color:red;">\*</mark> | string | Subscriber's Referral campaign UUID, e.g. MF9a78983d1c.UUID or email address |
| :uuid<mark style="color:red;">\*</mark>       | String | Membership's UUID.                                                           |

{% tabs %}
{% tab title="200 " %}

```
{
    "message": "Successfully deleted membership membership_34c8e39cf4"
}
```

{% endtab %}

{% tab title="400 " %}

```
{
    "error": "Failed to delete membership_34c8e39cf4"
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Subscriber not found" %}

```
{
    "error": "Membership membership_34c8e39cf4 not found!"
}
```

{% endtab %}

{% tab title="Campaign not found" %}

```
{
    "error": "Campaign MF9a7d8983d1c not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Subscribers

## Get subscribers

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/campaigns/:identifier/subscribers`

Subscribers are ordered in descending order by *created\_at*, meaning the first page shows the latest subscribers.

#### Path Parameters

| Name                                         | Type   | Description                                |
| -------------------------------------------- | ------ | ------------------------------------------ |
| identifier<mark style="color:red;">\*</mark> | String | Referral campaign UUID, e.g. MF9a78983d1c. |

#### Query Parameters

| Name   | Type   | Description                                                                                            |
| ------ | ------ | ------------------------------------------------------------------------------------------------------ |
| type   | string | Type of subscribers you want to get. Default is *all*, allowed values are *all, referrals, advocates.* |
| expand | String | If you pass "campaigns" the response will include referral\_programs data                              |

{% tabs %}
{% tab title="200 Used query params ?page=2\&per\_page=2" %}

```json
{
  "subscribers": [
    {
      "uuid": "sub_4bc1056297",
      "email": "drago@sparkloop.app",
      "name": null,
      "ref_code": "c784ef4f",
      "referrer_code": null,
      "referred": true,
      "referral_status": "verified",
      "ip_address": null,
      "user_agent": null,
      "origin": "signup",
      "utm_source": null,
      "utm_campaign": null,
      "created_at": "2021-03-24T11:55:02.113Z"
    },
    {
      "uuid": "sub_869976268c",
      "email": "manuel@sparkloop.com",
      "name": null,
      "ref_code": "d163ac6e",
      "referrer_code": null,
      "referred": false,
      "referral_status": "verified",
      "ip_address": null,
      "user_agent": null,
      "origin": "signup",
      "utm_source": null,
      "utm_campaign": null,
      "created_at": "2021-03-24T11:55:02.096Z"
    }
  ],
  "meta": {
    "per_page": 2,
    "page": 2,
    "total_pages": 3,
    "total_subscribers": 6
  }
}
```

{% endtab %}
{% endtabs %}

## Get a subscriber

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/campaigns/:identifier/subscribers/:subscriber_identifier`

Returns a single subscriber by email address or UUID

#### Path Parameters

| Name                                                     | Type   | Description                                |
| -------------------------------------------------------- | ------ | ------------------------------------------ |
| subscriber\_identifier<mark style="color:red;">\*</mark> | string | Subscriber's UUID or email address         |
| identifier<mark style="color:red;">\*</mark>             | string | Referral campaign UUID, e.g. MF9a78983d1c. |

#### Query Parameters

| Name   | Type   | Description                                                               |
| ------ | ------ | ------------------------------------------------------------------------- |
| expand | String | If you pass "campaigns" the response will include referral\_programs data |

{% tabs %}
{% tab title="200 " %}

```json
{
  "subscriber": {
    "uuid": "sub_4bc1056297",
    "email": "test36@drago.com",
    "name": null,
    "ref_code": "c784ef4f",
    "referrer_code": null,
    "referred": true,
    "referral_status": "verified",
    "ip_address": null,
    "user_agent": null,
    "origin": "signup",
    "utm_source": null,
    "utm_campaign": null,
    "created_at": "2021-03-24T11:55:02.113Z"
  }
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Subscriber not found" %}

```
{
    "error": "Subscriber sub_4bc10562d97 not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Get referrals for a subscriber

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/campaigns/:``identifier/subscribers/:subscriber_identifier/referrals`

Get the referrals of a specific subscriber.

Subscribers are ordered descendingly by *created\_at*, meaning the first page shows the latest subscribers.

#### Path Parameters

| Name                                                     | Type   | Description                                |
| -------------------------------------------------------- | ------ | ------------------------------------------ |
| subscriber\_identifier<mark style="color:red;">\*</mark> | String | Subscriber's UUID or email address         |
| identifier<mark style="color:red;">\*</mark>             | String | Referral campaign UUID, e.g. MF9a78983d1c. |

#### Query Parameters

| Name      | Type    | Description                                                                   |
| --------- | ------- | ----------------------------------------------------------------------------- |
| per\_page | integer | Number of subscribers returned in a single request. Default is 50, max is 200 |
| page      | integer | The page you want to get. Default is 1                                        |
| expand    | String  | If you pass "campaigns" the response will include referral\_programs data     |

{% tabs %}
{% tab title="200 Used query params ?page=2\&per\_page=3" %}

```
{
    "subscribers": [
        {
            "uuid": "sub_4bc1056297",
            "email": "test36@drago.com",
            "name": null,
            "ref_code": "c784ef4f",
            "referrer_code": null,
            "referrals_count": 0,
            "referred": true,
            "referral_status": "verified",
            "ip_address": null,
            "user_agent": null,
            "origin": "signup",
            "utm_source": null,
            "utm_campaign": null,
            "created_at": "2021-03-24T11:55:02.113Z"
        },
        {
            "uuid": "sub_869976268c",
            "email": "test35@drago.com",
            "name": null,
            "ref_code": "d163ac6e",
            "referrer_code": null,
            "referrals_count": 0,
            "referred": true,
            "referral_status": "pending",
            "ip_address": null,
            "user_agent": null,
            "origin": "signup",
            "utm_source": null,
            "utm_campaign": null,
            "created_at": "2021-03-24T11:55:02.096Z"
        },
        {
            "uuid": "sub_d4795d06e6",
            "email": "test34@drago.com",
            "name": null,
            "ref_code": "57b1fae2",
            "referrer_code": null,
            "referrals_count": 0,
            "referred": true,
            "referral_status": "rejected",
            "ip_address": null,
            "user_agent": null,
            "origin": "api",
            "utm_source": null,
            "utm_campaign": null,
            "created_at": "2021-03-24T11:55:02.066Z"
        }
    ],
    "meta": {
        "per_page": 3,
        "page": 2,
        "total_pages": 12,
        "total_subscribers": 36
    }
}
```

{% endtab %}
{% endtabs %}


# Partner Profile

## Get the partner profile

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_profile`

Returns the partner profile

{% tabs %}
{% tab title="200 " %}

```json
{
  "partner_profile": <Partner Profile Object>
}
```

{% endtab %}

{% tab title="404 When partner profile doesn't exist" %}
{% tabs %}
{% tab title="Subscriber not found" %}

```
{
    "error": "Partner profile not found."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Recommendations

## Get recommendation

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_profile/partner_campaigns/:identifier`

#### Path Parameters

| Name                                         | Type   | Description           |
| -------------------------------------------- | ------ | --------------------- |
| identifier<mark style="color:red;">\*</mark> | String | Recommendation's UUID |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
  "partner_campaign": {
    "uuid": "partner_campaign_a6224cd65cf3",
    "publication_uuid": "pub_5fc782e24409",
    "partner_profile_uuid": "partner_df80bd29bf34",
    "status": "draft",
    "referral_link": "http://link.sparkloop.eu.ngrok.io/p/c3088257c7",
    "cpa": 2.0,
    "max_payout": 500.0,
    "partner_program_uuid": "partner_program_abcdef123",
    "campaign_type": null,
    "thankyou_page_url": null,
    "accepted_terms_at": null,
    "name": "My partner campaign",
    "referral_pending_period": null
  }
}
```

{% endtab %}

{% tab title="404: Not Found " %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}

## Get recommendations

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_profile/partner_campaigns`

{% tabs %}
{% tab title="200: OK " %}

```json
{
  "partner_campaigns": [
    {
      "uuid": "partner_campaign_a6224cd65cf3",
      "publication_uuid": "pub_5fc782e24409",
      "partner_profile_uuid": "partner_df80bd29bf34",
      "status": "draft",
      "referral_link": "http://sparkloop.app/p/c3088257c7",
      "cpa": 2.0,
      "max_payout": 500.0,
      "partner_program_uuid": "partner_program_abcdef123",
      "campaign_type": null,
      "thankyou_page_url": null,
      "accepted_terms_at": null,
      "name": "My partner campaign",
      "referral_pending_period": null
    },
    {
      "uuid": "partner_campaign_1232424",
      "publication_uuid": "pub_5fc782e24409",
      "partner_profile_uuid": "partner_df80bd29bf34",
      "status": "draft",
      "referral_link": "http://sparkloop.app/p/abcde8257c7",
      "cpa": 2.0,
      "max_payout": 500.0,
      "partner_program_uuid": "partner_program_abcdef123",
      "campaign_type": null,
      "thankyou_page_url": null,
      "accepted_terms_at": null,
      "name": "My partner campaign",
      "referral_pending_period": null
    }
  ],
  "meta": {
    "per_page": 3,
    "page": 2,
    "total_pages": 12,
    "total_partner_campaigns": 36
  }    
}
```

{% endtab %}
{% endtabs %}

### Archive recommendation

Use this endpoint to archive a recommendation.

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v2/partner_profile/partner_campaigns/:identifier`

#### Path Parameters

| Name                                         | Type   | Description           |
| -------------------------------------------- | ------ | --------------------- |
| identifier<mark style="color:red;">\*</mark> | String | Recommendation's UUID |

**Response**

{% tabs %}
{% tab title="200" %}

```javascript
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}

### Reactivate recommendation

Use this endpoint to restore an archived recommendation, unless it was archived by a sponsor, in which case reactivation is not possible.

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/partner_profile/partner_campaigns/:identifier/reactivate`

#### Path Parameters

| Name                                         | Type   | Description           |
| -------------------------------------------- | ------ | --------------------- |
| identifier<mark style="color:red;">\*</mark> | String | Recommendation's UUID |

**Response**

{% tabs %}
{% tab title="200" %}

```javascript
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}

{% tab title="422" %}

```json
{
  "error": "This recommendation was archived by the sponsor and can't be re-activated."
}
```

{% endtab %}
{% endtabs %}


# Payouts

## Get payouts

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_profile/payouts`

{% tabs %}
{% tab title="200: OK " %}

```json
{
  "payouts": [
    {
      "uuid": "payout_9b59dc73ee6f",
      "amount_in_dollars": 50.0,
      "start_period": "2022-09-07T19:50:51.381Z",
      "end_period": "2022-10-07T19:50:51.381Z",
      "paid": false
    }
  ],
   "meta": {
     "per_page": 50, 
     "page": 1, 
     "total_pages": 1, 
     "total_payours": 1
   }
}
```

{% endtab %}
{% endtabs %}

## Get payout

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_profile/payouts/:identifier`

#### Path Parameters

| Name       | Type   | Description   |
| ---------- | ------ | ------------- |
| identifier | String | Payout's UUID |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
  "payout": {
    "uuid": "payout_9b59dc73ee6f",
    "amount_in_dollars": 50.0,
    "start_period": "2022-09-07T19:50:51.381Z",
    "end_period": "2022-10-07T19:50:51.381Z",
    "paid": false
  }
}
```

{% endtab %}
{% endtabs %}


# Publications

## Get publications

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/publications`

Publications are ordered descendingly by *created\_at*, meaning the first page shows the latest publications.

{% tabs %}
{% tab title="200 Used query params ?page=1\&per\_page=2" %}

```json
{
  "publications": [
    <Publication Object>,
    <Publication Object>
  ],
  "meta": {
    "per_page": 2,
    "page": 1,
    "total_pages": 2,
    "total_publications": 4
  }
}
```

{% endtab %}
{% endtabs %}

## Get a publication

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/publications/:identifier`

Returns a single publication by UUID

#### Path Parameters

| Name                                         | Type   | Description        |
| -------------------------------------------- | ------ | ------------------ |
| identifier<mark style="color:red;">\*</mark> | string | Publication's UUID |

{% tabs %}
{% tab title="200 " %}

```json
{
  "publication": <Publication Object>
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Publication not found" %}

```
{
    "error": "Publication pub_4bc10562d97 not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Update publication

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/publications/:identifier`

#### Path Parameters

| Name                                         | Type   | Description        |
| -------------------------------------------- | ------ | ------------------ |
| identifier<mark style="color:red;">\*</mark> | string | Publication's UUID |

#### Request Body

| Name             | Type    | Description                                                                                                                                                                                                                                                                                                                                                     |
| ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name             | string  | Name                                                                                                                                                                                                                                                                                                                                                            |
| url              | string  | Publication website URL                                                                                                                                                                                                                                                                                                                                         |
| description      | string  | Description                                                                                                                                                                                                                                                                                                                                                     |
| categories       | array   | <p>Categories to which this publication belongs, at least one and at most three categories are allowed.<br><br>e.g. \[marketing, tech]<br><br>List of all available categories: marketing, tech, food & drink, sports, health & fitness, news, finance, web3.0, TV & movie, literature, science, politics, lifestyle, business, art & design, travel, media</p> |
| logo             | String  | Logo of the publication. Must be png, jpeg, or jpg, and smaller than 1MB in size.                                                                                                                                                                                                                                                                               |
| auto\_pilot      | boolean | Set to `true` to enable auto-pilot. Set to `false` to disable it.                                                                                                                                                                                                                                                                                               |
| audience\_gender | string  | <p>Gender composition of the publication audience.<br></p><p>It can be <code>almost\_all\_male</code>, <code>mainly\_male</code>, <code>mixed</code>, <code>mainly\_female</code> or <code>almost\_all\_female</code></p>                                                                                                                                       |
| income\_levels   | array   | <p>Income levels of the publication audience. At least one is required.</p><p>e.g. \[$150k+]</p><p>List of all available income levels:</p><p><code>less than $20k</code>, <code>$20k - $50k</code>, <code>$50k - $75k</code>, <code>$75k - $100k</code>, <code>$100k - $150k</code>, <code>$150k+</code><br></p>                                               |
| age\_ranges      | array   | <p>Age ranges the publication audience belongs to. At least one is required.<br><br>e.g. \[under 18]<br><br>List of all available age ranges:</p><p><code>under 18</code>, <code>18 - 24</code>, <code>25 - 39</code>, <code>40 - 59</code>, <code>60+</code></p>                                                                                               |

{% tabs %}
{% tab title="200 Returns the publication." %}

```json
{
  "publication": <Publication Object>
}
```

{% endtab %}

{% tab title="400 Invalid categories" %}

```json
{
  "error": "You must set at least 1, and at most 3 categories."
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Publication not found" %}

```json
{
    "error": "Publication pub_34c8e39ca4 not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Upscribe

## Get all Upscribes

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/upscribes`

Returns all Upscribes

{% tabs %}
{% tab title="200 " %}

```json
{
  "upscribes": [
    <Upscribe Object>,
    <Upscribe Object>,
    <Upscribe Object>
  ]
}
```

{% endtab %}
{% endtabs %}

## Get an Upscribe

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier`

Returns a single upscribe by UUID

#### Path Parameters

| Name                                         | Type   | Description     |
| -------------------------------------------- | ------ | --------------- |
| identifier<mark style="color:red;">\*</mark> | string | Upscribe's UUID |

{% tabs %}
{% tab title="200 " %}

```json
{
  "upscribe": <Upscribe Object>
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Not found" %}

```
{
    "error": "Upscribe upscribe_4bc10562d97 not found."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Update Upscribe

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier`

Updates an Upscribe record

#### Path Parameters

| Name                                         | Type   | Description     |
| -------------------------------------------- | ------ | --------------- |
| identifier<mark style="color:red;">\*</mark> | string | Upscribe's UUID |

#### Body parameters

<table><thead><tr><th width="213">Name</th><th width="140">Type</th><th>Description</th></tr></thead><tbody><tr><td>auto_pilot</td><td>boolean</td><td>Enable/disable Upscribe auto-pilot feature.</td></tr></tbody></table>

{% tabs %}
{% tab title="200 " %}

```json
{
  "upscribe": <Upscribe Object>
}
```

{% endtab %}
{% endtabs %}

## Subscribe to recommendations

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/subscribe`

{% hint style="info" %}
This endpoint is only available upon request. Reach out to <support@sparkloop.app> to get access.
{% endhint %}

#### Path Parameters

| Name                                         | Type   | Description     |
| -------------------------------------------- | ------ | --------------- |
| identifier<mark style="color:red;">\*</mark> | string | Upscribe's UUID |

#### Request Body

| Name                                                | Type   | Description                                                                                                                |
| --------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| subscriber\_email<mark style="color:red;">\*</mark> | string | The subscriber's email address                                                                                             |
| country\_code<mark style="color:red;">\*</mark>     | string | <p>Subscriber's <a href="https://www.iban.com/country-codes">ISO 3166-1 Alpha2 country code</a>.<br><br>Eg: US, CA, IT</p> |
| recommendations<mark style="color:red;">\*</mark>   | string | A string of comma separated recommendations' ref\_codes. ie: "a724e922a3,a338cbf66a,ed149464cb"                            |
| utm\_source                                         | string | Optional. This is used as a filter in the Advanced reports.                                                                |
| utm\_campaign                                       | string | Optional. This is used as a filter in the Advanced reports.                                                                |

{% tabs %}
{% tab title="200: OK " %}

```json
{
  "response": "ok"
}
```

{% endtab %}

{% tab title="400 " %}
{% tabs %}
{% tab title="Parameters missing" %}

```json
{ "error" => "Parameters are missing." }
```

{% endtab %}

{% tab title="Subscriber param missing" %}

```json
{ "error" => "Subscriber param is missing or empty." }
```

{% endtab %}

{% tab title="Recommendations param missing" %}

```json
{ "error" => "Recommendations param is missing or empty." }
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Recommendations

## Get All Upscribe Recommendations

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations`

Returns all recommendations for an Upscribe, including paused ones. Recommendations are ordered in descending order by *created\_at*, meaning the first page shows the latest recommendations.

#### Path Parameters

| Name                                         | Type   | Description   |
| -------------------------------------------- | ------ | ------------- |
| identifier<mark style="color:red;">\*</mark> | String | Upscribe UUID |

#### Query Parameters

| Name      | Type    | Description                                        |
| --------- | ------- | -------------------------------------------------- |
| page      | integer | Results page to return. Default is 1               |
| per\_page | integer | How many results to return per page. Default is 50 |

{% tabs %}
{% tab title="200 " %}

```json
{
  "recommendations": [
    <Recommendation Object>,
    <Recommendation Object>,
    <Recommendation Object>,
  ],
  "meta": {
    "per_page": 50,
    "page": 2,
    "total_pages": 3,
    "total_recommendations": 62
  }
}
```

{% endtab %}

{% tab title="404: Not Found " %}

```json
{ "error": "Upscribe upscribe_38yhewu92 not found." }
```

{% endtab %}

{% tab title="400: Bad Request When Upscribe is not active" %}

```json
{ "error": "Upscribe upscribe_9uewihsi9 is not active." }
```

{% endtab %}
{% endtabs %}

## Generate recommendations

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations`

#### Path Parameters

| Name                                         | Type   | Description   |
| -------------------------------------------- | ------ | ------------- |
| identifier<mark style="color:red;">\*</mark> | String | Upscribe UUID |

#### Request Body

| Name          | Type    | Description                                                                                                                                                                                                                                                                                                                                                              |
| ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| country\_code | String  | <p>Subscriber's <a href="https://www.iban.com/country-codes">ISO 3166-1 Alpha2 country code</a>.<br><br>Eg: US, IT<br><br>Optional but VERY important. Without it, we can't generate the correct recommendations.<br><br></p>                                                                                                                                            |
| region\_code  | String  | <p>Subscriber's ISO 3166-2 state or region code.<br><br>Eg: TX, CA<br><br>Less important than the country code but some of our advertisers only target specific states and if you don’t pass the state code our system will not generate recommendations for those advertisers</p>                                                                                       |
| limit         | Integer | <p>How many recommendations you want to generate. To avoid inflating numbers, which then negatively impact your performance, always generate the number of recommendations you're going to display.<br><br>If no limit is passed 1 recommendation will be returned.<br><br>The maximum allowed value is 20. If a higher value is provided, it will be clamped to 20.</p> |

{% tabs %}
{% tab title="200 " %}

```json
{
  "recommendations": [
    <Recommendation Object>,
    <Recommendation Object>,
    <Recommendation Object>,
  ]
}
```

{% endtab %}
{% endtabs %}

## Edit Recommendation

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations/:recommendation_identifier`

#### Path Parameters

| Name                                                         | Type   | Description         |
| ------------------------------------------------------------ | ------ | ------------------- |
| identifier<mark style="color:red;">\*</mark>                 | String | Upscribe UUID       |
| recommendation\_identifier<mark style="color:red;">\*</mark> | String | Recommendation UUID |

#### Request Body

| Name                                     | Type   | Description                                                  |
| ---------------------------------------- | ------ | ------------------------------------------------------------ |
| reason<mark style="color:red;">\*</mark> | String | This is what will be displayed in the recommendation widget. |

{% tabs %}
{% tab title="200 " %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}

## Pin Recommendation

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations/:recommendation_identifier/pin`

#### Path Parameters

| Name                                                         | Type   | Description         |
| ------------------------------------------------------------ | ------ | ------------------- |
| identifier<mark style="color:red;">\*</mark>                 | String | Upscribe UUID       |
| recommendation\_identifier<mark style="color:red;">\*</mark> | String | Recommendation UUID |

{% tabs %}
{% tab title="200 Returns upscribe recommendation" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}

## Unpin Recommendation

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations/:recommendation_identifier/unpin`

#### Path Parameters

| Name                                                         | Type   | Description         |
| ------------------------------------------------------------ | ------ | ------------------- |
| identifier<mark style="color:red;">\*</mark>                 | String | Upscribe UUID       |
| recommendation\_identifier<mark style="color:red;">\*</mark> | String | Recommendation UUID |

{% tabs %}
{% tab title="200 Returns upscribe recommendation" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}

## Pause Recommendation

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations/:recommendation_identifier/pause`

#### Path Parameters

| Name                                                         | Type   | Description         |
| ------------------------------------------------------------ | ------ | ------------------- |
| identifier<mark style="color:red;">\*</mark>                 | String | Upscribe UUID       |
| recommendation\_identifier<mark style="color:red;">\*</mark> | String | Recommendation UUID |

{% tabs %}
{% tab title="200 Returns upscribe recommendation" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}

## Unpause Recommendation

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/upscribes/:identifier/recommendations/:recommendation_identifier/unpause`

#### Path Parameters

| Name                                                         | Type   | Description         |
| ------------------------------------------------------------ | ------ | ------------------- |
| identifier<mark style="color:red;">\*</mark>                 | String | Upscribe UUID       |
| recommendation\_identifier<mark style="color:red;">\*</mark> | String | Recommendation UUID |

{% tabs %}
{% tab title="200 Returns upscribe recommendation" %}

```json
{
  "recommendation": <Recommendation Object>
}
```

{% endtab %}
{% endtabs %}


# Partner Programs

## Get partner programs

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_programs`

Partner programs are ordered descendingly by *created\_at*, meaning the first page shows the latest publications.

{% tabs %}
{% tab title="200 Used query params ?page=1\&per\_page=2" %}

```json
{
  "partner_programs": [
    <Partner Program Object>,
    <Partner Program Object>
  ],
  "meta": {
    "per_page": 2,
    "page": 1,
    "total_pages": 12,
    "total_partner_programs": 24
  }
}
```

{% endtab %}
{% endtabs %}

## Get a partner program

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_programs/:identifier`

Returns a single partner program by UUID

#### Path Parameters

| Name                                         | Type   | Description            |
| -------------------------------------------- | ------ | ---------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Partner program's UUID |

{% tabs %}
{% tab title="200 " %}

```json
{
  "partner_program":
    <Partner Program Object>
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Partner program not found" %}

```
{
    "error": "Partner program partner_program_4bc10562d97 not found!"
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}

## Update partner program

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/partner_programs/:identifier`

#### Path Parameters

| Name                                         | Type   | Description            |
| -------------------------------------------- | ------ | ---------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Partner program's UUID |

#### Request Body

| Name                                  | Type    | Description                                                                                                                                      |
| ------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| slug                                  | String  | Unique custom signup page URL. If you set `slug` to *morning-stew*, the signup page URL is *<https://partnerprogram.page/morning-stew>*          |
| terms                                 | String  | Custom partner program conditions.                                                                                                               |
| referral\_pending\_duration\_enabled  | boolean | Set to `true` to exclude referrals that unsubscribe shortly after signup.                                                                        |
| referral\_pending\_duration           | number  | Days a subscriber must stay subscribed before the referral is confirmed. At most 30.                                                             |
| accept\_referrals\_from               | string  | Who partners can refer: `worldwide`, `us_only`, or `custom`.                                                                                     |
| blocked\_countries                    | array   | Alpha-2 country codes to block (e.g. `["GB", "CA"]`). Use with `accept_referrals_from=custom`. Cannot be sent together with `allowed_countries`. |
| allowed\_countries                    | array   | Alpha-2 country codes to allow (e.g. `["US", "CA"]`). Use with `accept_referrals_from=custom`. Cannot be sent together with `blocked_countries`. |
| restrict\_impressions\_to\_us\_states | boolean | When `accept_referrals_from` is `us_only`, limit impressions to selected US states.                                                              |
| allowed\_regions                      | array   | US state Alpha-2 codes (e.g. `["NY", "CA"]`). Used when `restrict_impressions_to_us_states` is `true`.                                           |

{% tabs %}
{% tab title="200 " %}

```json
{
  "partner_program":
    <Partner Program Object>
}
```

{% endtab %}

{% tab title="400 " %}
{% tabs %}
{% tab title="Blocked and allowed countries together" %}

```json
{
    "error": "You can either block or allow certain countries."
}
```

{% endtab %}

{% tab title="Empty body" %}

```json
{
    "error": "You have to update at least 1 field."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Partner program not found" %}

```json
{
    "error": "Partner program partner_program_34c8e39ca4 not found."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Referrals

## Get partner program referrals

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_programs/:identifier/referrals`

Partner program referrals are ordered in descending order by *referred\_at.*

#### Path Parameters

| Name                                         | Type   | Description            |
| -------------------------------------------- | ------ | ---------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Partner program's UUID |

#### Query Parameters

| Name             | Type              | Description                                                    |
| ---------------- | ----------------- | -------------------------------------------------------------- |
| referred\_after  | Date (YYYY-MM-DD) | Only return referrals with referred\_at on or after this date  |
| referred\_before | Date (YYYY-MM-DD) | Only return referrals with referred\_at on or before this date |

{% tabs %}
{% tab title="200" %}

```json
{
  "referrals": [
    <Partner Program Referral Object>,
    <Partner Program Referral Object>,
    <Partner Program Referral Object>
  ],
  "meta": {
    "per_page": 50,
    "page": 1,
    "total_pages": 12,
    "total_referrals": 598
  }
}
```

{% endtab %}
{% endtabs %}


# Partner Network

[Partner Programs](/api-v2-deprecated/endpoints/partner-network/partner-programs)

[Publications](/api-v2-deprecated/endpoints/partner-network/publications)


# Publications

## Get Publications

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_network/publications`

List, search and sort publications

#### Query Parameters

| Name  | Type   | Description                                                              |
| ----- | ------ | ------------------------------------------------------------------------ |
| query | String | Search publications by their Name, Description, Language, or Categories. |
| sort  | String | Sorting by: name, created\_at                                            |
| order | String | asc, desc                                                                |

{% tabs %}
{% tab title="Status 200" %}

```json
{
    "publications": [
        <Publication Object>,
        <Publication Object>,
        <Publication Object>
    ],
    "meta": {
        "per_page": 50,
        "page": 1,
        "total_pages": 2,
        "total_publications": 73
    }
}
```

{% endtab %}
{% endtabs %}

## Get a Publication

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_network/publications/:identifier`

Returns a single publication by UUID

{% tabs %}
{% tab title="Status 200" %}

```json
{
    "publication": <Publication Object>
}
```

{% endtab %}

{% tab title="Status 404" %}

```json
{
	"error": "Publication pub_e49df756d93da not found."
}
```

{% endtab %}
{% endtabs %}

## Recommend a Publication

<mark style="color:blue;">`POST`</mark> `https://api.sparkloop.app/v2/partner_network/publications/:identifier/recommend`

{% tabs %}
{% tab title="200 " %}

```json
{
    "partner_campaign": <Recommendation Object>
}
```

{% endtab %}

{% tab title="400" %}

```json
{"error": "This partner campaign has been archived and can't be re-activated"}
```

{% endtab %}
{% endtabs %}


# Partner Programs

## Get partner programs

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_network/partner_programs`

#### Query Parameters

| Name                      | Type    | Description                                                                                                                                                                                            |
| ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| cpa                       | integer | Limit search to partner programs with a CPA higher than a certain value                                                                                                                                |
| max\_payout               | integer | Limit search to partner programs with a Max Payout higher than a certain value                                                                                                                         |
| accepted\_countries       | string  | <p>Limit search to partner programs accepting referrals from specific countries.</p><p><br>Comma separated list of ISO3166 country codes</p><p></p><p>Example: US, CA</p>                              |
| can\_be\_recommended\_via | string  | <p>Limit search to partner programs that can be recommended via specific channels.</p><p>Comma separated list of channels.</p><p></p><p>Example: upscribe, partner\_link or magic\_link</p>            |
| sort                      | string  | <p>The criteria to sort partner programs by.</p><p></p><p>Possible values:</p><p>max\_payout - Max payout (high to low)</p><p>cpa - CPA (high to low)</p><p>max\_payout - Max payout (high to low)</p> |

{% tabs %}
{% tab title="200 Returns partner programs" %}

```json
{
  "partner_programs": [
    <Partner Program Object>,
    <Partner Program Object>,
    <Partner Program Object>,
  ],
  "meta": {
    "per_page": 2,
    "page": 1,
    "total_pages": 12,
    "total_partner_programs": 24
  }
}
```

{% endtab %}
{% endtabs %}

## Get a partner program

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/partner_network/partner_programs/:identifier`

Returns a single partner program by UUID

#### Path Parameters

| Name                                         | Type   | Description            |
| -------------------------------------------- | ------ | ---------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Partner program's UUID |

{% tabs %}
{% tab title="200 Returns partner program" %}

```json
{
  "partner_program": <Partner Program Object>
}
```

{% endtab %}
{% endtabs %}

## Join partner program

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/partner_network/partner_programs/:identifier/join`

Use this endpoint to join a partner program.

#### Path Parameters

| Name                                         | Type   | Description            |
| -------------------------------------------- | ------ | ---------------------- |
| identifier<mark style="color:red;">\*</mark> | string | Partner program's UUID |

#### Request Body

| Name                                                        | Type   | Description                                                                                        |
| ----------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------- |
| reason\_for\_recommending<mark style="color:red;">\*</mark> | string | If the partner program requires partners to apply, the reason for recommending must be passed over |

{% tabs %}
{% tab title="200 Returns partner program" %}

```
{
    "partner_campaign": <Recommendation Object>
}
```

{% endtab %}

{% tab title="400 " %}
{% tabs %}
{% tab title="Unsupported status" %}

```
{
    "error": "Some error"
}
```

{% endtab %}

{% tab title="Empty PUT body" %}

```
{
    "error": "You have to update at least 1 field."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Webhooks

## Create new webhook

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/webhooks`

#### Request Body

| Name                                  | Type    | Description                                                                         |
| ------------------------------------- | ------- | ----------------------------------------------------------------------------------- |
| url<mark style="color:red;">\*</mark> | String  | Webhook's URL                                                                       |
| new\_referral                         | boolean | If you want to receive webhook notifications for new referrals in referral programs |
| reward\_unlocked                      | boolean | If you want to receive webhook notifications when rewards are unlocked              |
| new\_partner\_referral                | String  | If you want to receive webhook notifications for new referrals from partners        |
| reward\_redeemed                      | String  | If you want to receive webhook notifications when rewards are redeemed              |

{% tabs %}
{% tab title="201: Created " %}

```javascript
{
    "webhook": {
        "uuid": "webhook_ac06a45cb5",
        "url": "https://webhook.site/6819636c-fd8d-45bd-a1bf-e154cd9f2d68",
        "events": {
            "new_referral": true,
            "reward_unlocked": false
        }
    }
}
```

{% endtab %}

{% tab title="400: Bad Request " %}

```javascript
{
    "error": "Url must start with https://"
}
```

{% endtab %}
{% endtabs %}

## Get webhooks

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/webhooks`

Webhooks are returned in descending order of creation.

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "webhooks": [
        {
            "uuid": "webhook_6f06b5421a",
            "url": "https://webhook.site/9a470b66-2d72-413e-b42d-c59c61cd4be6",
            "events": {
                "new_referral": false,
                "reward_unlocked": true
            }
        },
        {
            "uuid": "webhook_e4a23cc058",
            "url": "https://webhook.site/9a470b66-2d72-413e-b42d-c59c61cd4be6",
            "events": {
                "new_referral": true,
                "reward_unlocked": false
            }
        }
    ]
}
```

{% endtab %}

{% tab title="404: Not Found " %}

```javascript
{
    "error": "Campaign MF9a7d8983d1c not found!"
}
```

{% endtab %}
{% endtabs %}

## Get a webhook by UUID

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/webhooks/:identifier`

#### Path Parameters

| Name                                         | Type   | Description    |
| -------------------------------------------- | ------ | -------------- |
| identifier<mark style="color:red;">\*</mark> | String | Webhook's UUID |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "webhook": {
        "uuid": "webhook_ac06a45cb5",
        "url": "https://webhook.site/6819636c-fd8d-45bd-a1bf-e154cd9f2d68",
        "events": {
            "new_referral": true,
            "new_partner_referral": null,
            "reward_unlocked": null,
            "reward_redeemed": true
        }
    }
}
```

{% endtab %}

{% tab title="400: Bad Request " %}

```javascript
{
    "error": "Webhook uuid blank!"
}
```

{% endtab %}

{% tab title="404: Not Found " %}

```javascript
{
    "error": "Webhook webhook_ABC not found!"
}
```

{% endtab %}
{% endtabs %}

## Update a webhook

<mark style="color:orange;">`PUT`</mark> `https://api.sparkloop.app/v2/webhooks/:identifier`

#### Path Parameters

| Name                                         | Type   | Description    |
| -------------------------------------------- | ------ | -------------- |
| identifier<mark style="color:red;">\*</mark> | String | Webhook's UUID |

#### Request Body

| Name                   | Type    | Description                                                                  |
| ---------------------- | ------- | ---------------------------------------------------------------------------- |
| url                    | String  | Webhook's URL                                                                |
| new\_referral          | boolean | If you want to receive webhook notifications for new referrals               |
| reward\_unlocked       | boolean | If you want to receive webhook notifications when rewards are unlocked       |
| new\_partner\_referral | String  | If you want to receive webhook notifications for new referrals from partners |
| reward\_redeemed       | String  | If you want to receive webhook notifications when rewards are redeemed       |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "webhook": {
        "uuid": "webhook_ac06a45cb5",
        "url": "https://webhook.site/6819636c-fd8d-45bd-a1bf-e154cd9f2d68",
        "events": {
            "new_referral": true,
            "new_partner_referral": null,
            "reward_unlocked": null,
            "reward_redeemed": true
        }
    }
}
```

{% endtab %}

{% tab title="400: Bad Request " %}

```javascript
{
    "error": "Url must start with https://"
}
```

{% endtab %}
{% endtabs %}

## Delete a webhook

<mark style="color:red;">`DELETE`</mark> `https://api.sparkloop.app/v2/webhooks/:identifier`

#### Path Parameters

| Name                                         | Type   | Description    |
| -------------------------------------------- | ------ | -------------- |
| identifier<mark style="color:red;">\*</mark> | String | Webhook's UUID |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    "message": "Successfully deleted webhook webhook_abc"
}
```

{% endtab %}

{% tab title="400: Bad Request " %}

```javascript
{
    "error": "Webhook uuid blank!"
}
```

{% endtab %}

{% tab title="404: Not Found " %}

```javascript
{
    "error": "Webhook webhook_ABC not found!"
}
```

{% endtab %}
{% endtabs %}


# Offers

## Get offers

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/offers`

Offers are ordered descendingly by *created\_at*, meaning the first page shows the latest offer.

{% tabs %}
{% tab title="200 Used query params ?page=1\&per\_page=2" %}

```json
{
   "offers": [
      <Offer Object>,
      <Offer Object>
  ],
  "meta": {
    "per_page": 2,
    "page": 1,
    "total_pages": 12,
    "total_offers": 24
  }
}
```

{% endtab %}
{% endtabs %}

## Get an offer

<mark style="color:blue;">`GET`</mark> `https://api.sparkloop.app/v2/offers/:identifier`

Returns a single offer by UUID

#### Path Parameters

| Name                                         | Type   | Description  |
| -------------------------------------------- | ------ | ------------ |
| identifier<mark style="color:red;">\*</mark> | string | Offer's UUID |

{% tabs %}
{% tab title="200 " %}

```json
{
     "offer": <Offer Object>,
}
```

{% endtab %}

{% tab title="404 " %}
{% tabs %}
{% tab title="Offer not found" %}

```
{
    "error": "Offer offer_4bc10562d97 not found."
}
```

{% endtab %}
{% endtabs %}
{% endtab %}
{% endtabs %}


# Conversions

## Confirm a conversion from an Action offer

<mark style="color:green;">`POST`</mark> `https://api.sparkloop.app/v2/offers/:offer_uuid/submissions/:submission_uuid/conversion`

#### Path Parameters

| Name                                               | Type   | Description       |
| -------------------------------------------------- | ------ | ----------------- |
| offer\_uuid<mark style="color:red;">\*</mark>      | string | Offer's UUID      |
| submission\_uuid<mark style="color:red;">\*</mark> | string | Submission's UUID |

{% tabs %}
{% tab title="200: Ok" %}

```javascript
{
    "message": "Conversion tracked successfully"
}
```

{% endtab %}

{% tab title="422: Error" %}
When the request was sent for a different type of Offer, like Click or Lead:

<pre class="language-javascript"><code class="lang-javascript"><strong>{
</strong><strong>    "error": "Offer submission must be an action"
</strong>}
</code></pre>

When the conversion was already confirmed:

```javascript
{
    "error": "Offer submission must be unique"
}
```

{% endtab %}

{% tab title="404: Not found" %}

```javascript
{
    "error": "Offer not found"
}
```

or

```javascript
{
    "error": "Submission not found"
}{
```

{% endtab %}
{% endtabs %}


# Objects Schema


# Partner Profile Object

### Example

```json
{
  "uuid": "partner_4bc1056297",
  "name": "John Doe",
  "partner_network_status": "approved"
  "stripe_connected": true
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>uuid</td><td>string</td><td>the unique ID of the partner</td></tr><tr><td>name</td><td>string</td><td>the name of the partner</td></tr><tr><td>partner_network_status</td><td>string</td><td><p>the partner's status.</p><p>Possible values:</p><ul><li>blocked</li><li>account_incomplete</li><li>pending</li><li>rejected</li><li>approved</li></ul></td></tr><tr><td>stripe_connected</td><td>Boolean</td><td><code>true/false</code> whether the Stripe onboarding has been successful</td></tr></tbody></table>


# Partner Program Object

### Example

```json
{
  "uuid": "partner_program_45e5187ede44",
  "publication": {
    "uuid": "pub_810f4c8c08cd",
    "name": "The Pour Over",
    "logo" : "https://dash.sparkloop.app/rails/active_storage/representations/stamp.jpeg",
    "description": "The day's biggest news paired with foundational Biblical principles. Join over 250,000 delighted subscribers!",
    "url": "https://www.thepourover.org/",
    "send_frequency": "1-2x per month",
    "language": "English",
    "categories": ["religion", "politics", "lifestyle"]
  },
  "status": "active",
  "terms": null,
  "redirect_url": "https://www.thepourover.org/",
  "cpa": 2.0,
  "max_payout": 500.0,
  "referral_pending_duration": 10,
  "geo_restrictions": {
    "allow_worldwide_referrals": false,
    "allowed_countries": ["GB", "CA"],
    "blocked_countries": []
  },
  "screening_strictness": "not_enough_data",
  "expected_earning_per_referral": "$3",
  "expected_referral_confirmation_rate": 75,
  "requires_approval": true,
  "created_at": "2022-11-23T15:04:36.143Z"
}
```

| Property                               | Type      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| -------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| uuid                                   | string    | the unique ID of the partner program                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| publication                            | object    | the partner program's publication. The object includes the publication's UUID, name, description, URL, send\_frequency, language and categories                                                                                                                                                                                                                                                                                                                       |
| status                                 | string    | the status of the partner program                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| terms                                  | string    | custom terms and conditions of the publication                                                                                                                                                                                                                                                                                                                                                                                                                        |
| redirect\_url                          | string    | the URL people are redirected to when they click on the partner link                                                                                                                                                                                                                                                                                                                                                                                                  |
| cpa                                    | float     | how much this partner program pays per confirmed subscriber                                                                                                                                                                                                                                                                                                                                                                                                           |
| max\_payout                            | float     | the most a single partner can earn from this program in a month, in US dollars                                                                                                                                                                                                                                                                                                                                                                                        |
| referral\_pending\_duration            | integer   | how many days subscribers have to stay subscribed before they are confirmed.                                                                                                                                                                                                                                                                                                                                                                                          |
| geo\_restrictions                      | object    | <p>the geo-restrictions applied to this partner program. The object includes:</p><ul><li>allow\_worldwide\_referrals (boolean)</li><li>allowed\_countries (array) -> which countries are allowed (Alpha-2 ISOCODE)</li><li>blocked\_countries (array) -> which countries are blocked (Alpha-2 ISOCODE)</li><li>allowed\_regions (array) -> allowed regions (states) in the US (Alpha-2 ISOCODE) (present only when impressions are restricted to US states)</li></ul> |
| screening\_strictness                  | string    | <p>how strict is the screening of this partner program<br><br>returns exceptionally\_strict/strict/fair/very\_fair when there is enough data<br><br>otherwise returns not\_enough\_data</p>                                                                                                                                                                                                                                                                           |
| expected\_referral\_confirmation\_rate | integer   | the confirmation rate a good-fit partner should expect as a % (0-100)                                                                                                                                                                                                                                                                                                                                                                                                 |
| expected\_earning\_per\_referral       | string    | the amount of earnings expected per referral                                                                                                                                                                                                                                                                                                                                                                                                                          |
| requires\_approval                     | boolean   | <p>If the partner program requires approval.<br><br><em>Note: Only present when fetching a partner program from the Partner Network endpoints.</em></p>                                                                                                                                                                                                                                                                                                               |
| created\_at                            | timestamp | the date the partner program was created                                                                                                                                                                                                                                                                                                                                                                                                                              |


# Publication Object

### Example

```json
{
  "publication": {
    "uuid": "pub_4bc1056297",
    "name": "My first newsletter",
    "publication_url": "https://sparkloop.app/newsletter",
    "description": "Publication for my first newsletter",
    "auto_pilot": true,
    "partner_categories": ["marketing", "tech"],
    "list_id": "c34kko0",
    "status": "active",
    "created_at": "2021-03-24T11:55:02.113Z",
    "logo_url": "https://example.com/path-to-logo.png"
  }
}
```

| Property            | Type             | Description                                             |
| ------------------- | ---------------- | ------------------------------------------------------- |
| uuid                | string           | Unique ID of the publication                            |
| name                | string           | Publication's name                                      |
| publication\_url    | string           | Publication's website                                   |
| description         | string           | Publication's description                               |
| auto\_pilot         | boolean          | Whether or not this publication has auto-pilot enabled. |
| partner\_categories | array of strings | Publication's categories                                |
| status              | string           | The status of the publication (draft, active, archived) |
| logo\_url           | string           | URL to the publication's logo                           |
| list\_id            | string           | The list ID from your ESP (if your ESP supports lists)  |
| created\_at         | timestamp        | The date the publication was created                    |


# Subscriber Object

### Example

```json
{
  "uuid": "sub_4bc1056297",
  "email": "drago@sparkloop.app",
  "name": null,
  "ref_code": "c784ef4f",
  "referrer_code": null,
  "referred": true,
  "referral_status": "verified",
  "ip_address": null,
  "user_agent": null,
  "origin": "signup",
  "country_code": "US",
  "cpa": 5.0,
  "utm_source": null,
  "utm_campaign": null,
  "created_at": "2021-03-24T11:55:02.113Z"
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>uuid</td><td>string</td><td>the unique ID of the subscriber</td></tr><tr><td>email</td><td>string</td><td>the email address of the subscriber</td></tr><tr><td>name</td><td>string</td><td>the name of the subscriber</td></tr><tr><td>ref_code</td><td>string</td><td>the referral code of the subscriber</td></tr><tr><td>referrer_code</td><td>string</td><td>the referral code of the subscriber's referrer</td></tr><tr><td>referred</td><td>boolean</td><td>flag to indicate whether this subscriber was referred</td></tr><tr><td>referral_status</td><td>string</td><td>the referral status of the subscriber. Possible values: verified, pending, rejected.</td></tr><tr><td>ip_address</td><td>string</td><td>the ip address of the subscriber</td></tr><tr><td>user_agent</td><td>string</td><td>the user agent of the subscriber</td></tr><tr><td>origin</td><td>string</td><td>the origin of the subscriber. For example: "signup", "sparkloop-upscribe", etc</td></tr><tr><td>cpa</td><td>float</td><td>the cpa of the subscriber (for subscribers acquired via paid channels only)</td></tr><tr><td>utm_source</td><td>string</td><td>the acquisition source of the subscriber</td></tr><tr><td>utm_campaign</td><td>string</td><td>the acquistion campaign of the subscriber</td></tr><tr><td>created_at</td><td>timestamp</td><td>the date the subscriber was created</td></tr></tbody></table>


# Partner Program Referral Object

### Example

```json
{
  "email": "drago@sparkloop.app",
  "referral_status": "confirmed",
  "rejection_reason": null,
  "payable": true,
  "country_code": "US",
  "cpa": 3.0,
  "referred_at": "2021-03-24T11:55:02.113Z",
  "confirmed_at": "2021-04-04T11:55:02.113Z",
  "rejected_at": null,
  "recommending_publication": {
    "uuid": "pub_18b5c17562ab",
    "name": "Butter In a Lordly Dish",
    "logo": "https://logo.url"
  }
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>email</td><td>string</td><td>the email address of the referral</td></tr><tr><td>referral_status</td><td>string</td><td>the referral status of the subscriber. Possible values: confirmed, pending, rejected.</td></tr><tr><td>rejection_reason</td><td>string</td><td>the reason why the referral was rejected (only applied to rejected referrals).</td></tr><tr><td>payable</td><td>boolean</td><td>Whether or not the referral counts towards the payout.</td></tr><tr><td>country_code</td><td>string</td><td>The 2-letter country code the referral signed up from. For example: US, CA, IT</td></tr><tr><td>cpa</td><td>float</td><td>the referral's CPA (cost per acquisition)</td></tr><tr><td>referred_at</td><td>timestamp</td><td>the date the referral was created</td></tr><tr><td>confirmed_at</td><td>timestamp</td><td>the date the referral was confirmed (for confirmed referrals only)</td></tr><tr><td>rejected_at</td><td>timestamp</td><td>the date the referral was rejected (for rejected referrals only)</td></tr><tr><td>recommending_publication</td><td>object</td><td>an object with data about the partner who recommended the referral</td></tr></tbody></table>


# Campaign Object

### Example

```json
{ 
  "uuid": "MF9a78983d1c",
  "name": "My cool newsletter"
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>uuid</td><td>string</td><td>the unique ID of the  campaign</td></tr><tr><td>name</td><td>string</td><td>the name of the campaign</td></tr></tbody></table>


# Upscribe Object

### Example

```json
{
  "upscribe": {
    "uuid": "upscribe_ef8cb8124e99",
    "active": true,
    "after_submission_behaviour": "redirect",
    "redirect_url": "https://mywebsite.com/thank-you",
    "visibility_rules": [
      "https://mywebsite.com/",
      "https://mywebsite.com/blog/*",
    ],
    "after_submission_rules": [
      {
        "behaviour": "close_widget",
        "current_url": "https://mywebsite.com/",
        "redirect_url": null
      },
      {
        "behaviour": "redirect",
        "current_url": "https://mywebsite.com/blog/*",
        "redirect_url": "https://mywebsite.com/thank-you",
      }
    ]
  }
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>uuid</td><td>string</td><td>Upscribe's UUID</td></tr><tr><td>active</td><td>boolean</td><td>Whether the upscribe is active or not</td></tr><tr><td>after_submission_behaviour</td><td>string</td><td>The behaviour of Upscribe after it's submitted. Can be either "redirect" or "hide_widget"</td></tr><tr><td>redirect_url</td><td>string</td><td>The default redirect URL (only present if after_submission_behaviour is "redirect")</td></tr><tr><td>visibility_rules</td><td>array</td><td>Array of strings. Each string is a a URL where Upscribe can be shown. Empty array if Upscribe is set to be visible on all pages</td></tr><tr><td>after_submission_rules</td><td>array</td><td>Array of object. Each object represent a after_submission_rule. </td></tr></tbody></table>


# Recommendation Object

### Example

```json
{
  "uuid": "partner_campaign_23ac0811bfff",
  "type": "paid",
  "status": "active",
  "ref_code": "14ea515235",
  "publication_name": "Create&Sell",
  "publication_logo": "https://dash.sparkloop.app/rails/active_storage/representations/.../logo.png",
  "description": "Weekly, in-depth email marketing advice for creators by email genius Brennan Dunn.",
  "pinned": false,
  "position": null,
  "max_payout": 150000,
  "last_30_days_confirmation_rate": 30,
  "signup_url": "https://recs.page/create-and-sell?ref_code=14ea515235",
  "partner_program_uuid": "partner_program_83ba92h24",
  "available_budget_dollars": 100000,
  "cpa": 300,
  "referrals": {
    "pending": 541,
    "rejected": 38,
    "confirmed": 2890
  },
  "earnings": 867000,
  "net_earnings": 800000
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>uuid</td><td>string</td><td>Recommendation's UUID</td></tr><tr><td>type</td><td>string</td><td>Type of recommendation. Can be "paid" or "free".</td></tr><tr><td>status</td><td>string</td><td>Recommendation's status. Can be "active", "archived" or "paused". Referrals can only be sent to active recommendations.</td></tr><tr><td>ref_code</td><td>string</td><td>Randomly generated hex string representing the recommendation. Used for the signup URL.</td></tr><tr><td>publication_name</td><td>string</td><td>Publication's name</td></tr><tr><td>publication_logo</td><td>string</td><td>Publication's logo URL</td></tr><tr><td>description</td><td>string</td><td>Recommendation's description or reason to recommend</td></tr><tr><td>signup_url</td><td>string</td><td>Link to SparkLoop's Recommendations Hub Direct SignUp Page where the subscriber can opt into the recommended publication's newsletter. An <code>email</code> query parameter can be prepended to the URL to enable 1-click subscription.</td></tr><tr><td>cpa</td><td>integer</td><td>How much partners earn for sending confirmed referrals in cents.</td></tr><tr><td>pinned</td><td>boolean</td><td>Whether or not the recommendation is pinned. When a recommendation is pinned  it is always shown, unless it has run out of budget or the location of the subscriber is not suitable for it.</td></tr><tr><td>position</td><td>integer</td><td>The position of the recommendation</td></tr><tr><td>partner_program_uuid</td><td>string</td><td>The recommendation's partner program UUID.</td></tr><tr><td>max_payout</td><td>integer</td><td>The max amount a partner can earn per month in cents</td></tr><tr><td>available_budget</td><td>integer</td><td>Monthly budget left before hitting the max_payout, in cents. When it reaches 0, the recommendation budget will be considered exhausted and referrals won't be paid for.</td></tr><tr><td>referrals</td><td>object</td><td>Breakdown of all time pending/rejected/confirmed referrals</td></tr><tr><td>last_30_days_confirmation_rate</td><td>integer</td><td>Last 30 days referral confirmation rate</td></tr><tr><td>earnings</td><td>integer</td><td>All time gross earnings in cents</td></tr><tr><td>net_earnings</td><td>integer</td><td>All time net earnings in cents, after SparkLoop &#x26; payment processing fees</td></tr></tbody></table>


# Offer Object

### Example

```json
{
  "uuid": "offer_123abc",
  "name": "MyString",
  "headline": "MyString",
  "description": "MyString",
  "callout": "MyString",
  "redirect_url": "sparkloop.app",
  "conversion_type": "action",
  "visibility": "public",
  "status": "active",
  "created_at": "2025-11-18T09:07:48.559Z",
  "landscape_image_url": "image-url",
  "portrait_image_url": "image-url",
  "geo_restrictions": {"allow_worldwide_referrals": true, "allowed_countries": [], "blocked_countries": []},
  "monthly_budget": 2222.0,
  "cpa": 2.0,
  "conversion_event": "purchase",
  "terms": "MyString"
}
```

<table><thead><tr><th width="217">Property</th><th width="140.66666666666666">Type</th><th>Description</th></tr></thead><tbody><tr><td>uuid</td><td>string</td><td>the unique ID of the offer</td></tr><tr><td>name</td><td>string</td><td>the internal name of the offer</td></tr><tr><td>headline</td><td>string</td><td>public title of the offer</td></tr><tr><td>description</td><td>string</td><td>public description of the offer</td></tr><tr><td>callout</td><td>string</td><td>short phrase that should grab attention and pops on the offer's image</td></tr><tr><td>redirect_url</td><td>string</td><td>the URL people are redirected to when they click on the offer link</td></tr><tr><td>conversion_type</td><td>string</td><td> Click/Lead/Action</td></tr><tr><td>visibility</td><td>string</td><td>whether it's an internal offer or partner offer</td></tr><tr><td>status</td><td>string</td><td>the status of the offer</td></tr><tr><td>created_at</td><td>timestamp</td><td>the date the offer was created</td></tr><tr><td>landscape_image_url</td><td>string</td><td>URL to the landscape image</td></tr><tr><td>portrait_image_url</td><td>integer</td><td>URL to the portrait image</td></tr><tr><td>geo_restrictions</td><td>object</td><td><p>the geo-restrictions applied to this offer. The object includes:</p><ul><li>allow_worldwide_referrals (boolean)</li><li>allowed_countries (array) -> which countries are allowed (Alpha-2 ISOCODE)</li><li>blocked_countries (array) -> which countries are blocked (Alpha-2 ISOCODE)</li></ul><p></p></td></tr><tr><td>monthly_budget</td><td>float </td><td>the total monthly budget in dollars</td></tr><tr><td>cpa</td><td>float </td><td>how much this offer pays per confirmed lead in dollars</td></tr><tr><td>conversion_event</td><td>string</td><td>Defines the action a person must complete for the conversion to become payable (applies only to offers using the <strong>Action</strong> conversion type)</td></tr><tr><td>terms</td><td>string</td><td>custom terms and conditions of the brand (available only for offers of "Action" conversion_type)</td></tr></tbody></table>


