---
title: "Poll tracking events after a cursor"
method: GET
path: "/events"
tags: ["Tracking & events"]
---

# Poll tracking events after a cursor

`GET /events`

## Request

- Base URL: `https://gtmswag.com/api/v1`
- URL: `https://gtmswag.com/api/v1/events`
- Auth: HTTP bearer

An append-only feed of gift events, oldest first: `send.link_opened` (a recipient opened their gift link), `send.auto_approved` (a playbook's auto-approve rule approved and paid for a gift), status, link, order, tracking and outcome changes. Start without `cursor`, store `nextCursor` after every poll (it is always set) and keep the same filters; `hasMore` says whether to poll again now. Test and live events share the feed: read `mode`. `playbook.person_waiting` is a webhook-only event.

## Query parameters

- `campaignId` string
- `externalId` string
- `status` 'awaiting_address' | 'queued' | 'in_production' | 'shipped' | 'delivered' | 'cancelled'
- `sendId` string
- `limit` integer
- `cursor` string
- `type` 'send.created' | 'send.gift_link_changed' | 'send.order_changed' | 'send.address_confirmed' | 'send.link_opened' | 'send.status_changed' | 'send.outcome_reported' | 'send.tracking_changed' | 'send.auto_approved'

## Response `200`

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

- TrackingEventsPageResponse: The success body: `{"data": TrackingEventsPage}`.
  - `data` TrackingEventsPage, required: Events after the cursor, oldest first. Store `nextCursor` (always set) and poll again now when `hasMore`.
    - `items` GiftEvent[], required
      - `id` string, required: Event ID (32 hex); the X-GTM-Swag-Event-Id header. Deduplicate on it.
      - `cursor` string, required: This event's position in GET /events.
      - `type` 'send.created' | 'send.gift_link_changed' | 'send.order_changed' | 'send.address_confirmed' | 'send.link_opened' | 'send.status_changed' | 'send.outcome_reported' | 'send.tracking_changed' | 'send.auto_approved', required
      - `timestamp` string, required: When the event happened (ISO 8601, UTC).
      - `sendId` string, required: The gift (send_).
      - `campaignId` string, nullable, required
      - `attribution` object: The gift's reporting references (externalId, source, contactId, accountId, opportunityId, metadata).
      - `mode` 'test' | 'live', required
      - `status` 'awaiting_address' | 'queued' | 'in_production' | 'shipped' | 'delivered' | 'cancelled', required
      - `outcome` 'none' | 'replied' | 'meeting_booked' | 'meeting_held', required
      - `orderState` string, nullable
      - `carrier` string, nullable
      - `trackingNumber` string, nullable
      - `trackingUrl` string, nullable
      - `giftLink` object: send.gift_link_changed: the link's state, generation and expiry (never its URL).
        - `state` string
        - `generation` integer
        - `expiresAt` string
        - `redeemedAt` string
      - `generation` integer: send.link_opened: the link generation that was opened.
      - `source` string: send.outcome_reported: the reporting source.
      - `externalEventId` string: send.outcome_reported: the source's event ID.
      - `occurredAt` string: send.outcome_reported: when the outcome happened.
    - `nextCursor` string, required: Always set: store it and send it as cursor next time.
    - `hasMore` boolean, required

## Other responses

- `400`: Bad request: `VALIDATION_ERROR`, `INVALID_REQUEST`. 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.
- `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)
