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

# List conversion events (incremental sync)

> Events in the order they last CHANGED (updatedAt, then eventId), oldest first — built for incremental sync.

```ts TypeScript wrap theme={"dark"}
import { createSiteClient } from '@fetchski/attract-cms-sdk';

const site = createSiteClient();
const conversionEventPage = await site.conversionEvents().listConversionEvents();
```

```json 200 wrap theme={"dark"}
{
  "events": [
    {
      "eventId": "b8f2c1de-4f5a-4c1e-9a53-0d6f2b7c1e9a",
      "conversion": {
        "id": "ckq1w2e3r4t5y6u7i8o9p0",
        "key": "quote-request",
        "title": "Quote request"
      },
      "trigger": {
        "type": "form",
        "ref": "ckformid123"
      },
      "source": "browser",
      "occurredAt": "2026-09-21T15:04:05.000Z",
      "recordedAt": "2026-09-21T15:04:05.412Z",
      "updatedAt": "2026-09-21T15:04:07.020Z",
      "page": {
        "url": "https://example.com/contact?gclid=Cj0KCQjw",
        "path": "/contact",
        "referrer": "https://www.google.com"
      },
      "visitorId": "0b8e6a4c-8d2f-4b6e-9a1c-3f5d7e9b1a2c",
      "attribution": {
        "firstTouch": {
          "at": "2026-09-20T09:12:00.000Z",
          "path": "/",
          "landingUrl": "https://example.com/?gclid=Cj0KCQjw",
          "referrerOrigin": "https://www.google.com",
          "adPlatforms": [
            "google"
          ],
          "campaign": {
            "utm_source": "google",
            "utm_campaign": "spring-sale"
          },
          "clickIds": {
            "gclid": "Cj0KCQjw"
          }
        },
        "adPlatforms": [
          "google"
        ],
        "campaign": {
          "utm_source": "google",
          "utm_campaign": "spring-sale"
        },
        "clickIds": {
          "gclid": "Cj0KCQjw"
        },
        "browserIds": {
          "_gcl_au": "1.1.1234567890.1790000000"
        },
        "provenance": {
          "gclid": "ourRecord",
          "_gcl_au": "platformCookie"
        }
      },
      "consent": {
        "categories": {
          "analytics": true,
          "marketing": true
        },
        "mode": "lenient",
        "country": "US",
        "gpc": false,
        "policyVersion": 7
      },
      "value": 50,
      "currency": "USD",
      "userData": {
        "emailSha256": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514",
        "phoneSha256": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514",
        "phoneDigitsSha256": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514"
      },
      "submissionId": "ck9submission1",
      "delivery": {
        "google-ads": {
          "browser": "sent",
          "server": {
            "status": "sent",
            "attempts": 1,
            "sentAt": "2026-09-21T15:04:07.001Z",
            "lastAttemptAt": "2026-09-21T15:04:07.001Z"
          }
        },
        "ga4": {
          "browser": "sent",
          "server": {
            "status": "n/a",
            "attempts": 0
          }
        }
      },
      "status": "active",
      "adjustments": []
    }
  ],
  "nextCursor": "eyJ1IjoiMjAyNi0wOS0yMVQxNTowNDowNy4wMjBaIiwiZSI6ImI4ZjJjMWRlLTRmNWEtNGMxZS05YTUzLTBkNmYyYjdjMWU5YSJ9",
  "hasMore": false
}
```

## In your app

A function makes the call on the client it is given, so your code passes the real one and a test passes a fake.

**1. Add the function**

```ts src/attract/list-conversion-events.ts theme={"dark"}
import type { SiteClient } from '@fetchski/attract-cms-sdk';

export async function listConversionEvents(site: SiteClient) {
  return site.conversionEvents().listConversionEvents();
}
```

**2. Call it**

A script makes the client once and calls the function:

```ts scripts/list-conversion-events.ts theme={"dark"}
import { createSiteClient } from '@fetchski/attract-cms-sdk';
import { listConversionEvents } from '../src/attract/list-conversion-events.ts';

const site = createSiteClient();
console.log(await listConversionEvents(site));
```

```bash Terminal theme={"dark"}
node --env-file=.env scripts/list-conversion-events.ts
```

A route handler, a job or a queue worker calls it the same way.

Keep the response’s `nextCursor` and send it as `since` next time: you receive every new event and every later change to one (a delivery outcome, an adjustment) exactly once more. While `hasMore` is true, fetch again at once.

Filters narrow the page; `from` / `to` filter on when the conversion happened (`occurredAt`), `since` on when it last changed.

Fields the visitor’s consent did not allow are absent. The visitor’s IP address and user agent are never returned.

> [accordion] Optional parameters
>
> One object, e.g. `listConversionEvents({ since })`; leave out the optional fields.
>
> **since** `string | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. The previous page’s `nextCursor`, or an ISO 8601 time (changes strictly after it). Omit to start from the beginning.
>
> **limit** `number | null | undefined`
>
> **Optional.** Leave it out and the site uses `100`. Events per page: 1 to 500, 100 when left out (a value outside is refused, 400).
>
> **conversion** `string | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. A Conversion’s id or key. At most 256 characters.
>
> **platform** `ListConversionEventsPlatform | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. Events that report to this platform. One of `google-ads`, `ga4`, `gtm`, `meta`, `tiktok`.
>
> **visitorId** `string | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. One visitor’s events. At most 128 characters.
>
> **source** `ConversionEventSource | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. Where the event was born: the page (`browser`), this server, or another system (`external`). One of `browser`, `server`, `external`.
>
> **status** `Status | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. `active`, `retracted` or `adjusted` (restated). One of `active`, `retracted`, `adjusted`.
>
> **utmCampaign** `string | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. The `utm_campaign` of the event’s attribution. At most 256 characters.
>
> **from** `Date | string | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. Happened at or after (ISO 8601 date or time). Format: an ISO 8601 date and time.
>
> **to** `Date | string | null | undefined`
>
> **Optional.** Leave it out (or pass `null`) and it isn’t sent: the site’s default applies. Happened before (ISO 8601 date or time). Format: an ISO 8601 date and time.

> [accordion] Returns
>
> A `ConversionEventPage`. A page of events. Read its properties (`conversionEventPage.events`): each says whether the site always sends it.
>
> **events** `readonly ConversionEvent[]`
>
> **Always sent.** A list of `ConversionEvent`.
>
> **ConversionEvent**
>
> One conversion occurrence. Fields the visitor’s consent did not allow are ABSENT, not empty. Never returned: the visitor’s IP address and user agent, outbox lease and retry bookkeeping.
>
> **eventId** `string`
>
> **Always sent.** Opaque, unique per site, stable. The CMS mints a UUID for events it originates; another system mints its own (e.g. `tenant-42:order:1234:purchase`). Forwarded unchanged to every ad platform as its de-duplication key. Matches `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`. At most 128 characters.
>
> **conversion** `ConversionEventConversion`
>
> **Always sent.** A `ConversionEventConversion`. The Conversion it counts for.
>
> **ConversionEventConversion**
>
> **id** `string`
>
> **Always sent.** The Conversion’s id.
>
> **key** `string`
>
> **Always sent.** The Conversion’s key when the event was recorded.
>
> **title** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The Conversion’s name in the CMS.
>
> **source** `ConversionEventSource`
>
> **Always sent.** How it was recorded: `browser` (the site’s tag), `external` (posted through the Conversion Data API), or `server` (the site’s own server; nothing records one yet). One of `browser`, `server`, `external`; a newer site may send others, so keep a fallback.
>
> **occurredAt** `Date`
>
> **Always sent.** When it happened. Format: an ISO 8601 date and time.
>
> **updatedAt** `Date`
>
> **Always sent.** Last change (delivery outcome, adjustment) — the sync order. Format: an ISO 8601 date and time.
>
> **delivery** `Readonly\<Record\<string, DeliveryState\>\>`
>
> **Always sent.** A map of names to `DeliveryState`. Per destination the Conversion reports to.
>
> **DeliveryState**
>
> **browser** `Browser`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Whether the page fired it at this platform’s tag; `n/a` when the Conversion sends to this platform from the server only. One of `sent`, `not-sent`, `n/a`; a newer site may send others, so keep a fallback.
>
> **server** `ServerDeliveryState`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `ServerDeliveryState`. Server-side delivery to this platform’s API (Google Data Manager, GA4 Measurement Protocol, Meta Conversions API, TikTok Events API).
>
> **ServerDeliveryState**
>
> **status** `ServerDeliveryStateStatus`
>
> **Always sent.** `queued`/`retry` — owed; `sent` — delivered (`test: true` in a platform test mode); `failed` — attempts used up or a permanent error; `not-configured` — credentials, pixel, Google tag or conversion action missing; `expired` — older than the platform accepts (Google Ads 90 days, Meta and TikTok 7, Google Analytics 3); `not-sent` — retracted first, or nothing the platform can match; `no-consent` — the visitor did not allow Marketing; `n/a` — the platform has no server API here. One of `queued`, `retry`, `sent`, `failed`, `not-configured`, `expired`, `not-sent`, `no-consent`, `n/a`; a newer site may send others, so keep a fallback.
>
> **attempts** `number`
>
> **Always sent.** Sends tried so far. At least 0.
>
> **sentAt** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. When the platform took it; absent until then. Format: an ISO 8601 date and time.
>
> **lastAttemptAt** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The last try; absent before the first. Format: an ISO 8601 date and time.
>
> **nextAttemptAt** `Date | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. Set while `status` is `retry`; null otherwise. Format: an ISO 8601 date and time.
>
> **lastError** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Why the last try failed, as the platform said. At most 500 characters.
>
> **detail** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Why it wasn’t sent, for a status it ended in without trying (e.g. “The event was retracted.”). At most 500 characters.
>
> **test** `boolean`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Present (true) only when it went to the platform’s test mode. Always `true`.
>
> **adjustment** `AdjustmentDeliveryState`
>
> **Sometimes sent:** `undefined` when the site leaves it out. An `AdjustmentDeliveryState`. Telling this platform about the latest adjustment, once it had already received the event. Google Ads: the event re-sent with its new value (a retraction is sent as 0 — Google still counts the conversion). Google Analytics: a `refund`, for purchases only and only to lower the value. Meta, TikTok, Google Tag Manager and browser-only rows cannot be told (`unsupported`, with what to do instead in `detail`).
>
> **AdjustmentDeliveryState**
>
> **adjustmentId** `string`
>
> **Always sent.** Opaque, unique per site, stable. The CMS mints a UUID for events it originates; another system mints its own (e.g. `tenant-42:order:1234:purchase`). Forwarded unchanged to every ad platform as its de-duplication key. Matches `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`. At most 128 characters.
>
> **action** `Action`
>
> **Always sent.** `retract` (withdraw the conversion) or `restate` (a new value). One of `retract`, `restate`; a newer site may send others, so keep a fallback.
>
> **status** `AdjustmentDeliveryStateStatus`
>
> **Always sent.** `queued`/`retry` — owed; `sent` — delivered (`test: true` in a platform test mode); `failed` — attempts used up or a permanent error; `not-configured` — credentials or the live Google tag missing; `unsupported` — the platform cannot be told (`detail` says why and what to do instead); `not-needed` — the platform already holds this value. One of `queued`, `retry`, `sent`, `failed`, `not-configured`, `unsupported`, `not-needed`; a newer site may send others, so keep a fallback.
>
> **attempts** `number`
>
> **Always sent.** Sends tried so far. At least 0.
>
> **value** `number`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The value the platform holds once it is sent. At least 0.
>
> **refund** `number`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Google Analytics: the amount refunded. At least 0.
>
> **currency** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. ISO 4217 code, e.g. `USD`. Matches `^[A-Z]{3}$`.
>
> **sentAt** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. When the platform took it; absent until then. Format: an ISO 8601 date and time.
>
> **lastAttemptAt** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The last try; absent before the first. Format: an ISO 8601 date and time.
>
> **nextAttemptAt** `Date | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. Set while `status` is `retry`; null otherwise. Format: an ISO 8601 date and time.
>
> **lastError** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Why the last try failed, as the platform said. At most 500 characters.
>
> **detail** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Why it wasn’t sent, for a status it ended in without trying (e.g. “The event was retracted.”). At most 500 characters.
>
> **test** `boolean`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Present (true) only when it went to the platform’s test mode. Always `true`.
>
> **status** `Status`
>
> **Always sent.** `active`; `retracted` (withdrawn by an adjustment) or `adjusted` (its value restated). One of `active`, `retracted`, `adjusted`; a newer site may send others, so keep a fallback.
>
> **adjustments** `readonly Adjustment[]`
>
> **Always sent.** A list of `Adjustment`. Every adjustment, oldest first (empty when none).
>
> **Adjustment**
>
> **adjustmentId** `string`
>
> **Always sent.** Opaque, unique per site, stable. The CMS mints a UUID for events it originates; another system mints its own (e.g. `tenant-42:order:1234:purchase`). Forwarded unchanged to every ad platform as its de-duplication key. Matches `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`. At most 128 characters.
>
> **action** `Action`
>
> **Always sent.** `retract` (withdraw the conversion) or `restate` (a new value). One of `retract`, `restate`; a newer site may send others, so keep a fallback.
>
> **recordedAt** `Date`
>
> **Always sent.** When the site stored it. Format: an ISO 8601 date and time.
>
> **value** `number`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The restated value (restate only). At least 0.
>
> **currency** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. ISO 4217 code of `value`. Matches `^[A-Z]{3}$`.
>
> **reason** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Why, as the writer gave it. At most 500 characters.
>
> **occurredAt** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. When the change happened (as the writer said). Format: an ISO 8601 date and time.
>
> **recordedBy** `RecordedBy`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `RecordedBy`. Who wrote it: an API key (its public key ID and label) or an administrator.
>
> **RecordedBy**
>
> **type** `RecordedByType`
>
> **Always sent.** An API key or a signed-in person. One of `apiKey`, `user`; a newer site may send others, so keep a fallback.
>
> **label** `string`
>
> **Always sent.** The key’s label, or the person’s name.
>
> **keyId** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. An API key: its public key id.
>
> **id** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A person: their user `_id`.
>
> **trigger** `ConversionEventTrigger`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `ConversionEventTrigger`. What fired it.
>
> **ConversionEventTrigger**
>
> **type** `ConversionEventTriggerType`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A form submitted, a page viewed, a link clicked, a chat event, or an `external` fact. One of `form`, `pageView`, `linkClick`, `chat`, `external`; a newer site may send others, so keep a fallback.
>
> **ref** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. What exactly: the form’s `aposDocId` (`form`), the page path (`pageView`), the link rule that matched or `outbound:<host>` / `download:<type>` (`linkClick`), the chat event (`chat`), or the producer’s reference (`external`). At most 256 characters.
>
> **recordedAt** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. When the site stored it. Format: an ISO 8601 date and time.
>
> **page** `ConversionEventPageInfo`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `ConversionEventPageInfo`. The page it happened on.
>
> **ConversionEventPageInfo**
>
> **url** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Analytics or Marketing only. Format: an absolute URL.
>
> **path** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Its path.
>
> **referrer** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Origin only.
>
> **visitorId** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Analytics or Marketing only.
>
> **attribution** `EventAttribution`
>
> **Sometimes sent:** `undefined` when the site leaves it out. An `EventAttribution`. The attribution snapshot the event copied when it happened — independent of the visitor record’s later changes or expiry.
>
> **EventAttribution**
>
> **adPlatforms** `readonly string[]`
>
> **Always sent.** The ad platforms whose click ids the visits carried (empty when none).
>
> **campaign** `Readonly\<Record\<string, string\>\>`
>
> **Always sent.** The campaign parameters in force (`utm_*`, …); empty when none.
>
> **firstTouch** `AttributionTouch | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. An `AttributionTouch`. The visitor’s first visit on record; null when unknown.
>
> **AttributionTouch**
>
> **at** `Date`
>
> **Sometimes sent:** `undefined` when the site leaves it out. When the page view happened. Format: an ISO 8601 date and time.
>
> **path** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The page’s path.
>
> **landingUrl** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The full landing URL. Without Marketing consent its identifier parameters are removed. Format: an absolute URL.
>
> **referrerOrigin** `string | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. Where the visitor came from; null for a direct visit.
>
> **adPlatforms** `readonly AdPlatforms[]`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Platforms whose ad click ID the URL carried — kept for every visitor, consent or not. Each one of `google`, `meta`, `tiktok`, `other`.
>
> **campaign** `Readonly\<Record\<string, string\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. UTM and campaign parameters (`utm_*`, `gad_source`, `gad_campaignid`, `srsltid`).
>
> **clickIds** `Readonly\<Record\<string, string\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Ad click IDs (`gclid`, `gbraid`, `wbraid`, `fbclid`, `ttclid`). Marketing only.
>
> **linker** `Readonly\<Record\<string, string\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Google’s cross-domain linker value (`_gl`). Marketing only.
>
> **lastTouch** `AttributionTouch | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. An `AttributionTouch`. Their last visit with an ad click or campaign parameters; null when none. Its fields are listed under the first `AttributionTouch` above.
>
> **clickIds** `Readonly\<Record\<string, string\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Marketing only.
>
> **browserIds** `Readonly\<Record\<string, string\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Ad-platform browser IDs (`_gcl_au` — the cross-site key — `_gcl_aw`, `_gcl_gb`, `_gcl_gs`, `_gcl_dc`, `_ga`, `_fbp`, `_fbc`, `_ttp`). Marketing only.
>
> **gaSessions** `Readonly\<Record\<string, string\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The visitor’s Google Analytics session per data stream when it happened (from the `_ga_<container>` cookies), keyed by the measurement ID without its `G-`. The GA4 server sender joins the event to that session. Marketing only.
>
> **provenance** `Readonly\<Record\<string, Provenance\>\>`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Where each identifier came from: the visitor’s attribution record, the page URL, the platform’s own cookie, or the system that posted an external fact.
>
> **consent** `ConsentSnapshot`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `ConsentSnapshot`. The consent in force when the event happened. For an external fact (`origin: producer`) the posting system’s flags are recorded as `adUserData` / `adPersonalization` / `gpc` (limited data use).
>
> **ConsentSnapshot**
>
> **categories** `ConsentSnapshotCategories`
>
> **Always sent.** A `ConsentSnapshotCategories`. What the visitor allowed.
>
> **ConsentSnapshotCategories**
>
> **analytics** `boolean`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Analytics cookies and measurement.
>
> **marketing** `boolean`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Marketing: ad platforms and their identifiers.
>
> **mode** `ConsentSnapshotMode`
>
> **Always sent.** The rule that applied to the visitor: `lenient` (a notice-only region: non-essential ran unless they declined) or `strict` (an opt-in region: nothing non-essential until they agreed). One of `lenient`, `strict`; a newer site may send others, so keep a fallback.
>
> **gpc** `boolean`
>
> **Always sent.** Global Privacy Control, or the producer’s limited-data-use flag.
>
> **country** `string | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. The visitor’s two-letter country, when known; null otherwise. Matches `^[A-Z]{2}$`.
>
> **policyVersion** `number | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. The consent policy version the visitor answered; null when none.
>
> **adUserData** `boolean`
>
> **Sometimes sent:** `undefined` when the site leaves it out. An external fact: the producer allowed sending user data to ad platforms.
>
> **adPersonalization** `boolean`
>
> **Sometimes sent:** `undefined` when the site leaves it out. An external fact: the producer allowed ad personalization.
>
> **origin** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Present (`producer`) when the consent came with an external fact. Always `producer`.
>
> **value** `number`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Its value, when the Conversion has one. At least 0.
>
> **adjustedValue** `number`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The latest restated value. At least 0.
>
> **currency** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. ISO 4217 code of `value`, e.g. `USD`. Matches `^[A-Z]{3}$`.
>
> **userData** `UserDataHashes`
>
> **Sometimes sent:** `undefined` when the site leaves it out. An `UserDataHashes`. Hashed contact details. Email: trimmed + lower-cased; `emailGoogleSha256` only for a Gmail address with dots (Google drops them). Phone: E.164 — `phoneSha256` with the "+", `phoneDigitsSha256` without (Meta’s form).
>
> **UserDataHashes**
>
> **emailSha256** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Lower-case hex SHA-256 digest. Matches `^[0-9a-f]{64}$`.
>
> **emailGoogleSha256** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Lower-case hex SHA-256 digest. Matches `^[0-9a-f]{64}$`.
>
> **phoneSha256** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Lower-case hex SHA-256 digest. Matches `^[0-9a-f]{64}$`.
>
> **phoneDigitsSha256** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Lower-case hex SHA-256 digest. Matches `^[0-9a-f]{64}$`.
>
> **submissionId** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. The form submission a form conversion came from.
>
> **producer** `Producer`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `Producer`. The system that posted an external fact.
>
> **Producer**
>
> **system** `string`
>
> **Always sent.** The system’s name, as it posted it (lowercase, `a-z0-9._-`, at most 64).
>
> **contentHash** `string`
>
> **Always sent.** Fingerprint of the posted fact (idempotency).
>
> **recordedBy** `RecordedBy`
>
> **Always sent.** A `RecordedBy`. Who wrote it: an API key (its public key ID and label) or an administrator. Its fields are listed under the first `RecordedBy` above.
>
> **ref** `string`
>
> **Sometimes sent:** `undefined` when the site leaves it out. Its own reference for the fact (at most 256 characters), when it gave one.
>
> **join** `ConversionEventJoin`
>
> **Sometimes sent:** `undefined` when the site leaves it out. A `ConversionEventJoin`. How an external fact was joined to a visitor (`by: null` — no match).
>
> **ConversionEventJoin**
>
> **by** `By | null`
>
> **Sometimes sent:** `undefined` when the site leaves it out, and it can be `null`. The identifier that matched, or null for none. One of `visitorId`, `token`, `gclAu`, `gclid`, `gbraid`, `wbraid`, `fbclid`, `ttclid`, `emailSha256`, `phoneSha256`; a newer site may send others, so keep a fallback.
>
> **nextCursor** `string | null`
>
> **Always sent, can be `null`.** Pass as `since` to continue. When the page is empty it echoes the `since` you sent — keep polling with it.
>
> **hasMore** `boolean`
>
> **Always sent.** More changes are waiting right now; fetch again at once.

> [accordion] Errors
>
> Nothing to catch: the SDK retries what is safe, and throws the rest as the classes below; catch only what you want to react to (see [Errors and testing](https://docs.fetchski-attract.com/typescript/errors.md)).
>
> **ValidationFailed** `400`
>
> The request is not valid. `data.errors` lists each problem with the `path` of the parameter or body field it concerns.
>
> **Retried first:** No. **Read:** `errors` (field → messages), `siteMessage`.
>
> **AuthenticationFailed** `401`
>
> The API key is missing, malformed, unknown, disabled, expired or archived (`invalid`), or a request that must be signed — by the route, or by the key’s own signing mode — has a missing, stale or wrong signature (an upload signs its SHA-256, declared in `X-Fetchski-Content-SHA256`, in the body’s place) (`unauthorized`).
>
> **Retried first:** No. **Read:** `siteMessage`, `errorName`, `data`.
>
> **Forbidden** `403`
>
> The API key lacks the scope this route needs (the message names it), or the key is used from an address outside its allowlist.
>
> **Retried first:** No. **Read:** `siteMessage`, `errorName`, `data`.
>
> **NotFound** `404`
>
> Nothing with this identifier — or the caller may not use the Data API at all (anonymous visitors and non-admin sessions get 404 so the API is not advertised). A write while conversion tracking is switched off also answers 404.
>
> **Retried first:** No. **Read:** `siteMessage`, `errorName`, `data`.
>
> **RateLimited** `429`
>
> The key used up its reads- or writes-per-minute limit (set per key; defaults 600 / 120). Still, after the client’s own waits and retries.
>
> **Retried first:** Yes: the client waits `Retry-After` (1 s when absent; over 60 s it gives up at once) and retries, up to `retries` times (2 by default). **Read:** `retryAfter` (seconds, or null), `siteMessage`.
>
> **SiteError** `5xx`
>
> The site failed (or something in front of it did). Its logs say why.
>
> **Retried first:** A 502, 503 or 504: yes, after 0.5 s then 1 s. A 500: no. **Read:** `siteMessage`, `errorName`, `data`.
>
> **SiteUnreachable** `no answer`
>
> No answer: DNS, a refused connection, TLS, or the timeout (15 s per attempt by default).
>
> **Retried first:** Yes, after 0.5 s then 1 s. **Read:** `reason`, `neverConnected`.
>
> **RequestNotSent** `not sent`
>
> The client refused to send it (e.g. a path segment of `..`); the site never saw it.
>
> **Retried first:** No. **Read:** `reason` (what to change).
>
> **SiteRequestFailed** `any other`
>
> Any other status, or a success whose JSON doesn’t match this SDK version’s types (update the SDK).
>
> **Retried first:** No. **Read:** `reason`, `status`.
>
> **UnknownSite** `setup`
>
> `sites.forSite('acme')` named a slug your sites (`createSiteClients()`) don’t have.
>
> **Retried first:** No: nothing was sent. **Read:** `message`, `slug`.
>
> **SiteConfigurationError** `setup`
>
> The client’s settings are wrong: no `FETCHSKI_ATTRACT_URL` (or `baseUrl`), or a malformed API key.
>
> **Retried first:** No: nothing was sent. **Read:** `message` (what to fix).

## Behavior

* **Access:** an API key with `conversions:read` — or its group master `ads:all`, or `admin:all` — or a signed-in user holding the `conversion-events:view` permission. Anonymous callers get 404.
* **Rate limit:** the key’s own reads-per-minute limit, set when the key is created (default 600); over it, 429 with `Retry-After`. The client waits and retries for you; you see `RateLimited` only if that runs out.
* **Retries:** a read is safe to repeat: the client retries it on no answer, a 502/503/504 and a 429.
* **Timeout:** 15 s per attempt by default; raise `timeout` (milliseconds) on the client for longer.
* **Paging:** one page per call. To walk them all, use `listConversionEventsAll()` (see More examples): it asks for the next page as you iterate, and stops when `hasMore` is false.

## More examples

`listConversionEventsAll()` takes `listConversionEvents()`’s arguments except `since`.

```ts Every page wrap theme={"dark"}
import { createSiteClient } from '@fetchski/attract-cms-sdk';

const site = createSiteClient();
for await (const conversionEvent of site.conversionEvents().listConversionEventsAll()) {
  console.log(conversionEvent); // the next page is fetched as you iterate
}
```

## Test it

It calls the function above with a fake client: the fake answers every call with a valid example, so nothing leaves your machine and no settings or key are needed. Run it with `node --test`.

```ts tests/conversionEvents-listConversionEvents.test.ts theme={"dark"}
import { test } from 'node:test';
import { fakeSite } from '@fetchski/attract-cms-sdk/testing';
import { listConversionEvents } from '../src/attract/list-conversion-events.ts';

test('conversionEvents.listConversionEvents', async () => {
  const site = fakeSite();
  await listConversionEvents(site);
  site.assertCalled('conversionEvents.listConversionEvents');
});
```

**Answer with your own values:** fake `conversionEvents.listConversionEvents` with a `sequence()` of pages, each `factories.ConversionEventPage({...})`, and walk `listConversionEventsAll()`.

```ts tests/conversionEvents-listConversionEvents-answer.test.ts theme={"dark"}
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { factories, fakeSite, sequence } from '@fetchski/attract-cms-sdk/testing';

test('conversionEvents.listConversionEvents answers your values', async () => {
  const site = fakeSite({
    'conversionEvents.listConversionEvents': sequence(
      factories.ConversionEventPage({ hasMore: true, nextCursor: 'page-2' }),
      factories.ConversionEventPage({ hasMore: false, nextCursor: null })
    )
  });
  const walked: unknown[] = [];
  for await (const conversionEvent of site.conversionEvents().listConversionEventsAll()) {
    walked.push(conversionEvent);
  }
  assert.equal(walked.length, 2);
});
```

To fake `listConversionEventsAll()`, answer `conversionEvents.listConversionEvents` (the walk calls it page by page): a `sequence()` of answers for several pages.

## Related

> [card] [Get one conversion event](https://docs.fetchski-attract.com/typescript/reference/conversion-events/get-conversion-event.md)
>
> `site.conversionEvents().getConversionEvent()`

> [card] [Post a conversion another system saw](https://docs.fetchski-attract.com/typescript/reference/conversion-events/create-external-conversion-event.md)
>
> `site.conversionEvents().createExternalConversionEvent()`

> [card] [Retract or restate a conversion event](https://docs.fetchski-attract.com/typescript/reference/conversion-events/adjust-conversion-event.md)
>
> `site.conversionEvents().adjustConversionEvent()`

> [card] [GET /conversion-event](https://docs.fetchski-attract.com/api-reference/conversion-events/list-conversion-events-incremental-sync.md)
>
> The endpoint this calls, in the API reference: the HTTP request and answer, and every rule the site checks.


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