> ## 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.

# Post a conversion another system saw

> Records a fact the site cannot see itself — a reservation paid on a booking site, a lead that became a customer — as an event with `source: external`, and delivers it to the ad platforms the Conversion reports to (Google Ads, Google Analytics 4, Meta, TikTok server APIs). Google Analytics only accepts it within 72 hours of when it happened.

**Joining.** The CMS joins the fact to a visitor’s journey with `match`, strongest first (visitor ID, the `fk_at` token, `_gcl_au`, a click ID, hashed email / phone), and copies that visitor’s attribution onto the event. `join.by` in the answer says which matched. The producer’s own click IDs win over the visitor record’s; its browser IDs only fill gaps.

**Consent.** The producer’s `consent` flags govern what is sent about the fact (omitted flags are granted). A denial trims what is sent — no contact details or browser data, Google receives `CONSENT_DENIED` — but the conversion is always stored and delivered.

**Browser context.** Send the customer’s `client` (IP, user agent) and `page.url` as captured when they booked; with them the fact reaches Meta as a `website` event (de-duplicated against the pixel), without them as `system_generated`.

**Idempotent on `eventId`.** Posting the same fact again returns the stored event with `created: false`; the same ID with different content is a 409. Retry freely.

**Access:** an API key with `conversions:write` — or its group master `ads:all`, or `admin:all` — or a signed-in user holding the `conversion-events:write` permission. Anonymous callers get 404.

**Signed:** every API key must sign this request (`X-Fetchski-Timestamp` + `X-Fetchski-Signature`, see the headers below), whatever its own signing mode. Signed-in users in the CMS need no signature. [Signing requests](/signing) shows how, from the API and both SDKs.

**Rate limit:** the key’s own writes-per-minute limit, set when the key is created (default 120); over it, 429 with `Retry-After`.



## OpenAPI

````yaml /api-reference/openapi.yaml post /conversion-event/external
openapi: 3.1.0
info:
  title: Fetchski Attract CMS API
  description: >-
    Auto-generated OpenAPI spec for this Fetchski site instance. Authenticate
    machine-to-machine requests with an API key by sending the header
    `Authorization: ApiKey <token>`. Keys are issued in the CMS (API Keys) and
    carry scopes that limit them to specific actions. Each key also has its own
    security policy, chosen when it is created: a signing mode (token only,
    signed writes, or every request signed with the key’s signing secret via
    `X-Fetchski-Timestamp` + `X-Fetchski-Signature`), an optional network
    allowlist, and reads/writes-per-minute limits (429 with `Retry-After` over
    them). Some routes require a signature from every key — see each route.
  version: 1.0.0
servers:
  - url: https://{host}/api/v1
    description: 'Your site: set `host` to its host name.'
    variables:
      host:
        default: acme.example
security: []
tags:
  - name: Authentication
    description: >-
      Sign in as a person to get a bearer token or a session cookie.
      Integrations use an API key instead: send `Authorization: ApiKey <token>`.
  - name: SDK Packages
    description: >-
      The site’s own npm registry and Composer repository for the Attract SDKs,
      so an app installs them with one of the site’s API keys. Point npm, pnpm,
      Bun or Composer at them; the SDKs never call them.
  - name: Pages
    description: 'The page tree: each page’s content, place and URL.'
  - name: Global Content
    description: Site-wide content and settings every page shares.
  - name: Redirects
    description: Old URLs and where they now send visitors.
  - name: Internationalization
    description: The site’s locales.
  - name: Languages
    description: >-
      The site’s languages: the main language’s name and every further
      language’s name, code and Public switch. A save applies at once: a new
      language gets its home page and its own addresses (/fr/…), and its pages
      are then translated with each type’s localize routes.
  - name: Page Templates
    description: >-
      The site’s whole-page starting arrangements. They are not pages of the
      site (the Pages list leaves them out for API keys): read one, then make a
      page from it with Pages → create and `_copyingId` (`copyingId` in the
      SDKs).
  - name: Blog Posts
    description: The blog’s posts, and importing them from Markdown or MDX.
  - name: Blog Categories
    description: The categories posts are filed under.
  - name: Authors
    description: The people posts are credited to.
  - name: Team Members
    description: The people the Team section shows.
  - name: Testimonials
    description: Quotes from customers.
  - name: FAQ Items
    description: Questions and their answers.
  - name: FAQ Categories
    description: The groups questions are sorted into.
  - name: Google Reviews
    description: Reviews from the business’s Google profile, synced on a schedule.
  - name: Instagram Posts
    description: Posts from the connected Instagram account, synced on a schedule.
  - name: Service Areas
    description: The places the business serves, for the service map.
  - name: Cookies
    description: The cookies the consent banner declares.
  - name: Media
    description: The Images and Files libraries, and their tags.
  - name: Videos
    description: The Videos library.
  - name: Image Folders
    description: The folders that organize the Images library.
  - name: File Folders
    description: The folders that organize the Files library.
  - name: Video Folders
    description: The folders that organize the Videos library.
  - name: Photo Albums
    description: Hand-picked, ordered sets of photos for galleries.
  - name: Photo Feeds
    description: >-
      Feeds that collect photos from a connected photo source into the Images
      library on a schedule.
  - name: Attachments
    description: Uploading files and cropping images.
  - name: Fetchski Media
    description: >-
      Read-only discovery over the Images and Videos libraries: find what to
      reference in an MDX blog import, or which file to use, with its URLs.
  - name: Forms
    description: The site’s forms and their fields.
  - name: Form Submissions
    description: >-
      What visitors sent through the site’s forms. Submissions hold personal
      details: give a key `forms:read` only where it is needed.
  - name: Conversion Events
    description: >-
      The conversion event log — one record per conversion, with its attribution
      and consent snapshot and its delivery to each ad platform. Read it
      incrementally, post facts from other systems (a reservation purchase, a
      lead outcome), and adjust them (refunds, cancellations). One event ID
      travels everywhere as the platforms’ de-duplication key.
  - name: Visitor Attribution
    description: >-
      Per-visitor attribution on this site: first touch, last ad or campaign
      touch, and the ad platforms’ browser IDs. The join other systems use to
      attach a purchase to the marketing-site journey that led to it.
  - name: Ads Metrics
    description: >-
      Aggregates for dashboards: ad-click landings and conversions, by day,
      platform, campaign and more.
  - name: Conversions
    description: >-
      Conversion definitions — the visitor actions (a lead, a call click, a
      booking, a purchase) this site reports to the ad platforms, each with a
      row per platform. Managed by administrators in the CMS.
  - name: Webhooks
    description: >-
      Push notifications of form submissions and conversion-event changes to the
      site’s Webhook Destinations — signed with each destination’s own secret,
      retried for a day, at-least-once. Set destinations up in the CMS (Webhook
      Destinations, for people granted “Manage webhook destinations”): URL,
      events, optional filters and custom headers, and the signing secret shown
      once.
  - name: Request Log
    description: >-
      The page views the site captured (Connections → Tracking → Request log),
      and the query parameters on them: campaign tags and ad click IDs.
paths:
  /conversion-event/external:
    post:
      tags:
        - Conversion Events
      summary: Post a conversion another system saw
      description: >-
        Records a fact the site cannot see itself — a reservation paid on a
        booking site, a lead that became a customer — as an event with `source:
        external`, and delivers it to the ad platforms the Conversion reports to
        (Google Ads, Google Analytics 4, Meta, TikTok server APIs). Google
        Analytics only accepts it within 72 hours of when it happened.


        **Joining.** The CMS joins the fact to a visitor’s journey with `match`,
        strongest first (visitor ID, the `fk_at` token, `_gcl_au`, a click ID,
        hashed email / phone), and copies that visitor’s attribution onto the
        event. `join.by` in the answer says which matched. The producer’s own
        click IDs win over the visitor record’s; its browser IDs only fill gaps.


        **Consent.** The producer’s `consent` flags govern what is sent about
        the fact (omitted flags are granted). A denial trims what is sent — no
        contact details or browser data, Google receives `CONSENT_DENIED` — but
        the conversion is always stored and delivered.


        **Browser context.** Send the customer’s `client` (IP, user agent) and
        `page.url` as captured when they booked; with them the fact reaches Meta
        as a `website` event (de-duplicated against the pixel), without them as
        `system_generated`.


        **Idempotent on `eventId`.** Posting the same fact again returns the
        stored event with `created: false`; the same ID with different content
        is a 409. Retry freely.


        **Access:** an API key with `conversions:write` — or its group master
        `ads:all`, or `admin:all` — or a signed-in user holding the
        `conversion-events:write` permission. Anonymous callers get 404.


        **Signed:** every API key must sign this request (`X-Fetchski-Timestamp`
        + `X-Fetchski-Signature`, see the headers below), whatever its own
        signing mode. Signed-in users in the CMS need no signature. [Signing
        requests](/signing) shows how, from the API and both SDKs.


        **Rate limit:** the key’s own writes-per-minute limit, set when the key
        is created (default 120); over it, 429 with `Retry-After`.
      operationId: createExternalConversionEvent
      parameters:
        - $ref: '#/components/parameters/SignatureTimestamp'
        - $ref: '#/components/parameters/Signature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExternalConversionFact'
            examples:
              purchase:
                summary: A reservation purchase with browser context
                value:
                  eventId: tenant-42:order:1234:purchase
                  conversion: purchase
                  occurredAt: '2026-09-21T18:30:00Z'
                  value: 3200
                  currency: USD
                  match:
                    token: 1.q9Zr…
                    gclAu: 1.1.1234567890.1790000000
                    gclid: Cj0KCQjw
                    emailSha256: >-
                      b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514
                    phoneSha256: >-
                      b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514
                  consent:
                    adUserData: true
                    adPersonalization: true
                    limitedDataUse: false
                  client:
                    ip: 203.0.113.9
                    userAgent: Mozilla/5.0 (Macintosh; …)
                    capturedAt: '2026-09-21T18:29:40Z'
                  page:
                    url: https://shop.example.com/orders/1234
                  producer:
                    system: reservation
                    ref: booking-1234
              leadOutcome:
                summary: A lead outcome with only a hashed email
                value:
                  eventId: switchboard:lead:8841:booked
                  conversion: converted_lead
                  value: 0
                  match:
                    emailSha256: >-
                      b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514
                  producer:
                    system: switchboard
                    ref: lead-8841
      responses:
        '200':
          description: >-
            The stored event. `created` is false when this event ID was already
            stored with the same content.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateExternalConversionEventResponse'
              example:
                created: true
                event:
                  eventId: tenant-42:orders:1234:purchase
                  conversion:
                    id: ckq1w2e3r4t5y6u7i8o9p0
                    key: booking
                    title: Booking
                  trigger:
                    type: external
                    ref: booking-1234
                  source: external
                  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: 3200
                  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: []
                  producer:
                    system: reservation
                    ref: booking-1234
                  join:
                    by: token
        '400':
          $ref: '#/components/responses/DataApiInvalid'
        '401':
          $ref: '#/components/responses/DataApiUnauthorized'
        '403':
          $ref: '#/components/responses/DataApiForbidden'
        '404':
          $ref: '#/components/responses/DataApiNotFound'
        '409':
          $ref: '#/components/responses/DataApiConflict'
        '429':
          $ref: '#/components/responses/DataApiRateLimited'
      security:
        - ApiKeyAuth:
            - conversions:write
      x-codeSamples:
        - lang: typescript
          label: TypeScript
          source: >-
            import { createSiteClient } from '@fetchski/attract-cms-sdk';


            const site = createSiteClient();

            const createExternalConversionEvent = await
            site.conversionEvents().createExternalConversionEvent({ eventId:
            'tenant-42:order:1234:purchase', conversion: 'booking', producer: {
            system: 'reservation' } });
        - lang: php
          label: Laravel
          source: >-
            use Fetchski\AttractCmsLaravel\Facades\Attract;


            $createExternalConversionEvent =
            Attract::conversionEvents()->createExternalConversionEvent(eventId:
            'tenant-42:order:1234:purchase', conversion: 'booking', producer:
            ['system' => 'reservation']);
components:
  parameters:
    SignatureTimestamp:
      name: X-Fetchski-Timestamp
      in: header
      required: false
      description: >-
        Required for API keys. The Unix time in seconds when the request was
        signed; it must be within 5 minutes of the server clock.
      schema:
        type: string
        pattern: ^[0-9]{9,11}$
      example: '1790000000'
    Signature:
      name: X-Fetchski-Signature
      in: header
      required: false
      description: >-
        Required for API keys. `v1=` + the lower-case hex HMAC-SHA256, keyed
        with the API key’s signing secret (`fss_…`, shown once when the key is
        created or rotated), of `<timestamp>.<METHOD>.<path including
        query>.<raw request body>` — e.g.
        `1790000000.POST./api/v1/conversion-event/external.{"eventId":…}`.
        Several comma-separated `v1=` values are accepted.
      schema:
        type: string
        pattern: ^v1=[0-9a-f]{64}(,\s*v1=[0-9a-f]{64})*$
      example: v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  schemas:
    ExternalConversionFact:
      type: object
      required:
        - eventId
        - conversion
        - producer
      properties:
        eventId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
          maxLength: 128
          description: >-
            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.
          example: tenant-42:order:1234:purchase
        conversion:
          type: string
          maxLength: 128
          description: >-
            A Conversion’s id or key (`GET /conversion/definitions`); it must
            have a “Reported by another system” trigger.
          example: booking
        occurredAt:
          type: string
          format: date-time
          description: When it happened (not in the future). Defaults to now.
        value:
          type: number
          minimum: 0
          description: Rounded to cents. Defaults to the Conversion’s value.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          example: USD
          description: ISO 4217 code of `value`, e.g. `USD`.
        match:
          $ref: '#/components/schemas/FactMatch'
        consent:
          $ref: '#/components/schemas/FactConsent'
        client:
          $ref: '#/components/schemas/FactClient'
        page:
          type: object
          description: Where the customer was when it happened.
          properties:
            url:
              type: string
              format: uri
              maxLength: 2048
              description: The page the purchase happened on (http/https).
        producer:
          type: object
          required:
            - system
          description: 'Your system: who is posting the fact.'
          properties:
            system:
              type: string
              pattern: ^[a-z0-9][a-z0-9._-]*$
              maxLength: 64
              example: reservation
              description: >-
                Your system’s name (lowercase, `a-z0-9._-`), the same on every
                post.
            ref:
              type: string
              maxLength: 256
              example: booking-1234
              description: Your own reference for the fact (an order or booking number).
      example:
        eventId: tenant-42:order:1234:purchase
        conversion: purchase
        occurredAt: '2026-09-21T18:30:00Z'
        value: 3200
        currency: USD
        match:
          token: 1.q9Zr…
          gclAu: 1.1.1234567890.1790000000
          gclid: Cj0KCQjw
          emailSha256: b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514
          phoneSha256: b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514
        consent:
          adUserData: true
          adPersonalization: true
          limitedDataUse: false
        client:
          ip: 203.0.113.9
          userAgent: Mozilla/5.0 (Macintosh; …)
          capturedAt: '2026-09-21T18:29:40Z'
        page:
          url: https://shop.example.com/orders/1234
        producer:
          system: reservation
          ref: booking-1234
    CreateExternalConversionEventResponse:
      type: object
      required:
        - created
        - event
      properties:
        created:
          type: boolean
        event:
          $ref: '#/components/schemas/ConversionEvent'
    FactMatch:
      type: object
      description: >-
        What the CMS may join the fact to a visitor with, tried strongest first:
        `visitorId`, `token` (fk_at), `gclAu`, a click ID, then `emailSha256` /
        `phoneSha256`. Hashes also travel with the conversion to the platforms
        (see `UserDataHashes` for the normalization).
      properties:
        visitorId:
          type: string
          maxLength: 128
          description: >-
            The visitor id the site gave the customer’s browser (its
            `fetchski_vid` cookie), when you have it.
        token:
          type: string
          maxLength: 512
          description: The `fk_at` attribution token the visitor arrived with.
        gclAu:
          type: string
          description: The `_gcl_au` cookie value.
          example: 1.1.1234567890.1790000000
        gclid:
          type: string
          description: The Google Ads click id (`gclid`) the visit came with.
        gbraid:
          type: string
          description: The Google Ads app click id (`gbraid`, iOS).
        wbraid:
          type: string
          description: The Google Ads web-to-app click id (`wbraid`, iOS).
        fbclid:
          type: string
          description: The Meta click id (`fbclid`) the visit came with.
        ttclid:
          type: string
          description: The TikTok click id (`ttclid`) the visit came with.
        fbp:
          type: string
          description: The `_fbp` cookie value.
        fbc:
          type: string
          description: The `_fbc` cookie value.
        ttp:
          type: string
          description: The `_ttp` cookie value.
        emailSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
        emailGoogleSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
        phoneSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
        phoneDigitsSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
    FactConsent:
      type: object
      description: >-
        The customer’s ad-data consent as the producer knows it. Each flag
        defaults to the US-friendly value when omitted. A denial trims what is
        sent (no contact details or browser data; Google receives
        CONSENT_DENIED); the conversion itself is always stored and delivered.
      properties:
        adUserData:
          type: boolean
          default: true
          description: The customer allows their data to be sent to ad platforms.
        adPersonalization:
          type: boolean
          default: true
          description: The customer allows ads personalized for them.
        limitedDataUse:
          type: boolean
          default: false
          description: Meta LDU / TikTok limited data use (e.g. a US opt-out of sale).
    FactClient:
      type: object
      description: >-
        The customer’s browser as captured WHEN THEY BOOKED (not the server
        confirming later). With a user agent and a page URL the fact reaches
        Meta as a `website` event; without, as `system_generated`. Kept only
        until delivery is finished, never returned.
      properties:
        ip:
          type: string
          description: IPv4 or IPv6.
          example: 203.0.113.9
        userAgent:
          type: string
          maxLength: 512
          description: The browser’s `User-Agent` header.
        capturedAt:
          type: string
          format: date-time
          description: When the browser data was captured.
    ConversionEvent:
      type: object
      description: >-
        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.
      required:
        - eventId
        - conversion
        - source
        - occurredAt
        - updatedAt
        - delivery
        - status
        - adjustments
      properties:
        eventId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
          maxLength: 128
          description: >-
            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.
          example: tenant-42:order:1234:purchase
        conversion:
          type: object
          required:
            - id
            - key
          description: The Conversion it counts for.
          properties:
            id:
              type: string
              description: The Conversion’s id.
            key:
              type: string
              description: The Conversion’s key when the event was recorded.
            title:
              type: string
              description: The Conversion’s name in the CMS.
        trigger:
          type: object
          description: What fired it.
          properties:
            type:
              type: string
              enum:
                - form
                - pageView
                - linkClick
                - chat
                - external
              description: >-
                A form submitted, a page viewed, a link clicked, a chat event,
                or an `external` fact.
            ref:
              type: string
              maxLength: 256
              description: >-
                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`).
        source:
          type: string
          enum:
            - browser
            - server
            - external
          description: >-
            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).
        occurredAt:
          type: string
          format: date-time
          description: When it happened.
        recordedAt:
          type: string
          format: date-time
          description: When the site stored it.
        updatedAt:
          type: string
          format: date-time
          description: Last change (delivery outcome, adjustment) — the sync order.
        page:
          $ref: '#/components/schemas/ConversionEventPageInfo'
        visitorId:
          type: string
          description: Analytics or Marketing only.
        attribution:
          $ref: '#/components/schemas/EventAttribution'
        consent:
          $ref: '#/components/schemas/ConsentSnapshot'
        value:
          type: number
          minimum: 0
          description: Its value, when the Conversion has one.
        adjustedValue:
          type: number
          minimum: 0
          description: The latest restated value.
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 code of `value`, e.g. `USD`.
        userData:
          $ref: '#/components/schemas/UserDataHashes'
        submissionId:
          type: string
          description: The form submission a form conversion came from.
        producer:
          $ref: '#/components/schemas/Producer'
        join:
          type: object
          description: >-
            How an external fact was joined to a visitor (`by: null` — no
            match).
          properties:
            by:
              description: The identifier that matched, or null for none.
              type:
                - string
                - 'null'
              enum:
                - visitorId
                - token
                - gclAu
                - gclid
                - gbraid
                - wbraid
                - fbclid
                - ttclid
                - emailSha256
                - phoneSha256
                - null
        delivery:
          type: object
          description: Per destination the Conversion reports to.
          propertyNames:
            enum:
              - google-ads
              - ga4
              - gtm
              - meta
              - tiktok
          additionalProperties:
            $ref: '#/components/schemas/DeliveryState'
        status:
          type: string
          enum:
            - active
            - retracted
            - adjusted
          description: >-
            `active`; `retracted` (withdrawn by an adjustment) or `adjusted`
            (its value restated).
        adjustments:
          type: array
          items:
            $ref: '#/components/schemas/Adjustment'
          description: Every adjustment, oldest first (empty when none).
      example:
        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: []
    DataApiError:
      type: object
      description: >-
        Every error. `name` → HTTP status: `invalid` 400,
        `invalid`/`unauthorized` 401, `forbidden` 403, `notfound` 404,
        `conflict` 409, `tooManyRequests` 429.
      required:
        - name
        - message
      properties:
        name:
          type: string
          enum:
            - invalid
            - unauthorized
            - forbidden
            - notfound
            - conflict
            - tooManyRequests
            - error
        message:
          type: string
        data:
          type: object
          properties:
            errors:
              type: array
              items:
                $ref: '#/components/schemas/DataApiFieldError'
              description: Present on 400.
            retryAfter:
              type: integer
              description: Present on 429.
            eventId:
              type: string
              description: Present on 409.
    ConversionEventPageInfo:
      type: object
      description: The page it happened on.
      properties:
        url:
          type: string
          format: uri
          description: Analytics or Marketing only.
        path:
          type: string
          description: Its path.
        referrer:
          type: string
          description: Origin only.
    EventAttribution:
      type: object
      description: >-
        The attribution snapshot the event copied when it happened — independent
        of the visitor record’s later changes or expiry.
      required:
        - adPlatforms
        - campaign
      properties:
        firstTouch:
          oneOf:
            - $ref: '#/components/schemas/AttributionTouch'
            - type: 'null'
          description: The visitor’s first visit on record; null when unknown.
        lastTouch:
          oneOf:
            - $ref: '#/components/schemas/AttributionTouch'
            - type: 'null'
          description: >-
            Their last visit with an ad click or campaign parameters; null when
            none.
        adPlatforms:
          type: array
          items:
            type: string
          description: >-
            The ad platforms whose click ids the visits carried (empty when
            none).
        campaign:
          type: object
          additionalProperties:
            type: string
          description: The campaign parameters in force (`utm_*`, …); empty when none.
        clickIds:
          type: object
          additionalProperties:
            type: string
          description: Marketing only.
        browserIds:
          $ref: '#/components/schemas/BrowserIds'
        gaSessions:
          type: object
          additionalProperties:
            type: string
            pattern: ^\d{6,20}$
          description: >-
            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.
          example:
            AB12CD34EF: '1790000000'
        provenance:
          type: object
          additionalProperties:
            type: string
            enum:
              - ourRecord
              - url
              - platformCookie
              - producer
          description: >-
            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.
    ConsentSnapshot:
      type: object
      description: >-
        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).
      required:
        - categories
        - mode
        - gpc
      properties:
        categories:
          type: object
          description: What the visitor allowed.
          properties:
            analytics:
              type: boolean
              description: Analytics cookies and measurement.
            marketing:
              type: boolean
              description: 'Marketing: ad platforms and their identifiers.'
        mode:
          type: string
          enum:
            - lenient
            - strict
          description: >-
            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).
        country:
          type:
            - string
            - 'null'
          pattern: ^[A-Z]{2}$
          description: The visitor’s two-letter country, when known; null otherwise.
        gpc:
          type: boolean
          description: Global Privacy Control, or the producer’s limited-data-use flag.
        policyVersion:
          type:
            - integer
            - 'null'
          description: The consent policy version the visitor answered; null when none.
        adUserData:
          type: boolean
          description: >-
            An external fact: the producer allowed sending user data to ad
            platforms.
        adPersonalization:
          type: boolean
          description: 'An external fact: the producer allowed ad personalization.'
        origin:
          type: string
          const: producer
          description: Present (`producer`) when the consent came with an external fact.
    UserDataHashes:
      type: object
      description: >-
        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).
      properties:
        emailSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
        emailGoogleSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
        phoneSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
        phoneDigitsSha256:
          type: string
          pattern: ^[0-9a-f]{64}$
          description: Lower-case hex SHA-256 digest.
    Producer:
      type: object
      description: The system that posted an external fact.
      required:
        - system
        - contentHash
        - recordedBy
      properties:
        system:
          type: string
          example: reservation
          description: >-
            The system’s name, as it posted it (lowercase, `a-z0-9._-`, at most
            64).
        ref:
          type: string
          example: booking-1234
          description: >-
            Its own reference for the fact (at most 256 characters), when it
            gave one.
        contentHash:
          type: string
          description: Fingerprint of the posted fact (idempotency).
        recordedBy:
          $ref: '#/components/schemas/RecordedBy'
    DeliveryState:
      type: object
      properties:
        browser:
          type: string
          enum:
            - sent
            - not-sent
            - n/a
          description: >-
            Whether the page fired it at this platform’s tag; `n/a` when the
            Conversion sends to this platform from the server only.
        server:
          $ref: '#/components/schemas/ServerDeliveryState'
        adjustment:
          $ref: '#/components/schemas/AdjustmentDeliveryState'
    Adjustment:
      type: object
      required:
        - adjustmentId
        - action
        - recordedAt
      properties:
        adjustmentId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
          maxLength: 128
          description: >-
            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.
          example: tenant-42:order:1234:purchase
        action:
          type: string
          enum:
            - retract
            - restate
          description: '`retract` (withdraw the conversion) or `restate` (a new value).'
        value:
          type: number
          minimum: 0
          description: The restated value (restate only).
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 code of `value`.
        reason:
          type: string
          maxLength: 500
          description: Why, as the writer gave it.
        occurredAt:
          type: string
          format: date-time
          description: When the change happened (as the writer said).
        recordedAt:
          type: string
          format: date-time
          description: When the site stored it.
        recordedBy:
          $ref: '#/components/schemas/RecordedBy'
    DataApiFieldError:
      type: object
      required:
        - name
        - message
      properties:
        name:
          type: string
          example: invalid
        code:
          type: integer
          example: 400
        path:
          type: string
          description: The query parameter or body field (dotted) the problem is in.
          example: match.emailSha256
        message:
          type: string
        data:
          type: object
    AttributionTouch:
      type: object
      description: >-
        One touch of a visitor’s journey on this site. Identifier values
        (`clickIds`, `linker`) appear only when the visitor allowed Marketing.
      properties:
        at:
          type: string
          format: date-time
          description: When the page view happened.
        path:
          type: string
          example: /pricing
          description: The page’s path.
        landingUrl:
          type: string
          format: uri
          description: >-
            The full landing URL. Without Marketing consent its identifier
            parameters are removed.
        referrerOrigin:
          type:
            - string
            - 'null'
          description: Where the visitor came from; null for a direct visit.
          example: https://www.google.com
        adPlatforms:
          type: array
          items:
            type: string
            enum:
              - google
              - meta
              - tiktok
              - other
          description: >-
            Platforms whose ad click ID the URL carried — kept for every
            visitor, consent or not.
        campaign:
          type: object
          additionalProperties:
            type: string
          description: >-
            UTM and campaign parameters (`utm_*`, `gad_source`,
            `gad_campaignid`, `srsltid`).
          example:
            utm_source: google
            utm_campaign: spring-sale
        clickIds:
          type: object
          additionalProperties:
            type: string
          description: >-
            Ad click IDs (`gclid`, `gbraid`, `wbraid`, `fbclid`, `ttclid`).
            Marketing only.
          example:
            gclid: Cj0KCQjw…
        linker:
          type: object
          additionalProperties:
            type: string
          description: Google’s cross-domain linker value (`_gl`). Marketing only.
    BrowserIds:
      type: object
      additionalProperties:
        type: string
      description: >-
        Ad-platform browser IDs (`_gcl_au` — the cross-site key — `_gcl_aw`,
        `_gcl_gb`, `_gcl_gs`, `_gcl_dc`, `_ga`, `_fbp`, `_fbc`, `_ttp`).
        Marketing only.
      example:
        _gcl_au: 1.1.1234567890.1790000000
        _fbp: fb.1.1790000000000.123456789
    RecordedBy:
      type: object
      description: >-
        Who wrote it: an API key (its public key ID and label) or an
        administrator.
      required:
        - type
        - label
      properties:
        type:
          type: string
          enum:
            - apiKey
            - user
          description: An API key or a signed-in person.
        keyId:
          type: string
          description: 'An API key: its public key id.'
        id:
          type: string
          description: 'A person: their user `_id`.'
        label:
          type: string
          description: The key’s label, or the person’s name.
    ServerDeliveryState:
      type: object
      description: >-
        Server-side delivery to this platform’s API (Google Data Manager, GA4
        Measurement Protocol, Meta Conversions API, TikTok Events API).
      required:
        - status
        - attempts
      properties:
        status:
          type: string
          enum:
            - queued
            - retry
            - sent
            - failed
            - not-configured
            - expired
            - not-sent
            - no-consent
            - n/a
          description: >-
            `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.
        attempts:
          type: integer
          minimum: 0
          description: Sends tried so far.
        sentAt:
          type: string
          format: date-time
          description: When the platform took it; absent until then.
        lastAttemptAt:
          type: string
          format: date-time
          description: The last try; absent before the first.
        nextAttemptAt:
          type:
            - string
            - 'null'
          format: date-time
          description: Set while `status` is `retry`; null otherwise.
        lastError:
          type: string
          maxLength: 500
          description: Why the last try failed, as the platform said.
        detail:
          type: string
          maxLength: 500
          description: >-
            Why it wasn’t sent, for a status it ended in without trying (e.g.
            “The event was retracted.”).
        test:
          type: boolean
          const: true
          description: Present (true) only when it went to the platform’s test mode.
    AdjustmentDeliveryState:
      type: object
      description: >-
        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`).
      required:
        - adjustmentId
        - action
        - status
        - attempts
      properties:
        adjustmentId:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
          maxLength: 128
          description: >-
            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.
          example: tenant-42:order:1234:purchase
        action:
          type: string
          enum:
            - retract
            - restate
          description: '`retract` (withdraw the conversion) or `restate` (a new value).'
        value:
          type: number
          minimum: 0
          description: The value the platform holds once it is sent.
        refund:
          type: number
          minimum: 0
          description: 'Google Analytics: the amount refunded.'
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 code, e.g. `USD`.
        status:
          type: string
          enum:
            - queued
            - retry
            - sent
            - failed
            - not-configured
            - unsupported
            - not-needed
          description: >-
            `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.
        attempts:
          type: integer
          minimum: 0
          description: Sends tried so far.
        sentAt:
          type: string
          format: date-time
          description: When the platform took it; absent until then.
        lastAttemptAt:
          type: string
          format: date-time
          description: The last try; absent before the first.
        nextAttemptAt:
          type:
            - string
            - 'null'
          format: date-time
          description: Set while `status` is `retry`; null otherwise.
        lastError:
          type: string
          maxLength: 500
          description: Why the last try failed, as the platform said.
        detail:
          type: string
          maxLength: 500
          description: >-
            Why it wasn’t sent, for a status it ended in without trying (e.g.
            “The event was retracted.”).
        test:
          type: boolean
          const: true
          description: Present (true) only when it went to the platform’s test mode.
  responses:
    DataApiInvalid:
      description: >-
        The request is not valid. `data.errors` lists each problem with the
        `path` of the parameter or body field it concerns.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataApiError'
          example:
            name: invalid
            message: The query is not valid.
            data:
              errors:
                - name: invalid
                  code: 400
                  path: limit
                  message: A whole number from 1 to 500.
                  data: {}
    DataApiUnauthorized:
      description: >-
        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`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataApiError'
          example:
            name: unauthorized
            message: X-Fetchski-Signature does not match this request.
            data: {}
    DataApiForbidden:
      description: >-
        The API key lacks the scope this route needs (the message names it), or
        the key is used from an address outside its allowlist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataApiError'
          example:
            name: forbidden
            message: 'This API key lacks the required scope: conversions:read.'
            data: {}
    DataApiNotFound:
      description: >-
        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.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataApiError'
          example:
            name: notfound
            message: No event with this ID.
            data: {}
    DataApiConflict:
      description: >-
        The identifier was already used with different content (an event ID
        re-posted with a changed fact, an adjustment ID re-used), or the event
        is retracted.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataApiError'
          example:
            name: conflict
            message: An event with this ID is already stored with different content.
            data:
              eventId: tenant-42:order:1234:purchase
    DataApiRateLimited:
      description: >-
        The key used up its reads- or writes-per-minute limit (set per key;
        defaults 600 / 120). Wait `Retry-After` seconds.
      headers:
        Retry-After:
          description: Seconds until the budget resets.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/DataApiError'
          example:
            name: tooManyRequests
            message: This API key’s rate limit is used up for this minute.
            data:
              retryAfter: 12
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        Paste your API key as the CMS showed it: `fsk_<site>_<key id>_<secret>`
        (issued under API Keys). `ApiKey ` in front of it works too. Leave it
        empty to read as the public.

````

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