---
title: "Read private gift links for matching"
method: GET
path: "/campaigns/{id}/distribution"
tags: ["Gift links"]
---

# Read private gift links for matching

`GET /campaigns/{id}/distribution`

## Request

- Base URL: `https://gtmswag.com/api/v1`
- URL: `https://gtmswag.com/api/v1/campaigns/{id}/distribution`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://gtmswag.com/oauth/authorize`, token URL `https://gtmswag.com/oauth/token`, scopes: `gifts:links:read`)

One page of a campaign's people with their private claim URL (`gift_link`, only when `availability` is `ready`), expiry and stable source IDs for matching into another system. Others carry `availability_reason`. Page with `limit` and `nextCursor` (null on the last page) and keep the same filters; `counts` cover the whole filtered set. A cursor from before a campaign change returns 409 CURSOR_STALE: start again. Reading never pays, sends, renews or rotates a link. Accepts an MCP OAuth token with `gifts:links:read`.

## Path parameters

- `id` string, required

## Query parameters

- `recipientIds` string[]
- `giftIds` string[]
- `search` string
- `availability` 'ready' | 'awaiting_approval' | 'preparing_links' | 'unavailable'
- `cursor` string
- `limit` integer

## Response `200`

Success: `{"data": DistributionRecordsPage}`.

- DistributionRecordsPageResponse: The success body: `{"data": DistributionRecordsPage}`.
  - `data` DistributionRecordsPage, required: One page of a campaign's people with their private gift links; `nextCursor` is null on the last page.
    - `version` 1, required
    - `records` object[], required
      - `recipient_id` string, required
      - `source_system` string, required
      - `external_id` string, required
      - `name` string, required
      - `company` string, required
      - `email` string, required
      - `profile_url` string, required
      - `campaign_id` string, required
      - `gift_id` string, required
      - `product_id` string, required
      - `import_id` string, required
      - `import_row_id` string, required
      - `review_url` string, required
      - `gift_link` string, required
      - `gift_status` string, required
      - `availability_reason` string, required
      - `gift_expires_at` string, required
      - `availability` 'awaiting_approval' | 'preparing_links' | 'ready' | 'unavailable', required
      - `link_generation` integer, required
      - `mode` 'test' | 'live', required
    - `nextCursor` string, nullable, required
    - `counts` DistributionCounts, required
      - `total` integer, required
      - `ready` integer, required
      - `unavailable` integer, required
    - `sourceImports` SourceImport[], required
      - `importId` string, required
      - `headers` string[], required
      - `retainedColumnIndices` integer[], required
      - `totalRows` integer, required
      - `complete` boolean, required

## Other responses

- `400`: Bad request: `VALIDATION_ERROR`. Each example says when.
- `401`: Not authenticated: `AUTH_REQUIRED`. no credentials, or an OAuth token on a route that only takes workspace keys; the key is malformed, unknown or revoked.
- `403`: Forbidden: `INSUFFICIENT_SCOPE`. Each example says when.
- `404`: Not found: `NOT_FOUND`. Each example says when.
- `409`: Conflict: `CURSOR_STALE`. Each example says when.
- `429`: Too many requests: `RATE_LIMIT`. more than 240 requests in the current minute for this workspace (REST and MCP share the limit); wait `retryAfterMs`.
- `default`: Any other refusal or failure, such as 403 CSRF (a cross-origin `Origin` header), 403 HTTPS_REQUIRED or 500 INTERNAL_ERROR, with what to do next.

---

[API](https://skmtc.dev/gtmswag/apis/gtm-swag-api.md) · [All operations](https://skmtc.dev/gtmswag/apis/gtm-swag-api/llms.txt) · [OpenAPI document](https://skmtc.dev/gtmswag/apis/gtm-swag-api/revisions/9ff86fa99afe?raw)
