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

# Conversion API

> Send conversion events to Scope3 for attribution and campaign optimization

## Overview

Scope3's Conversion API follows the [AdCP `log_event` specification](https://docs.adcontextprotocol.org/docs/media-buy/task-reference/log_event) for server-to-server conversion measurement. This enables you to:

* Send purchase, sign-up, and other conversion events
* Attribute conversions back to ad impressions and clicks
* Optimize campaigns based on actual outcomes

<Warning>
  **Privacy-First Design**: Raw PII (emails, phone numbers) is **NOT** accepted.
  You may send pre-hashed email/phone (SHA-256) or pre-resolved identity tokens
  (e.g., LiveRamp RampIDs, UID2).
</Warning>

## Prerequisites

Before sending conversion events, you'll need two things:

### 1. Configure an Event Source

Register an event source for your advertiser using the REST sync endpoint. This gives you an `event_source_id` to include in all event requests:

<CodeGroup>
  ```bash REST theme={null}
  curl -X POST https://api.interchange.io/api/v2/buyer/advertisers/{advertiserId}/event-sources/sync \
    -H "Authorization: Bearer $SCOPE3_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "account": { "account_id": "{advertiserId}" },
      "event_sources": [
        {
          "event_source_id": "website_pixel",
          "name": "Website Pixel",
          "event_types": ["purchase", "lead", "add_to_cart"],
          "allowed_domains": ["example.com"]
        }
      ]
    }'
  ```

  ```json MCP (api_call) theme={null}
  {
    "method": "POST",
    "path": "/api/v2/buyer/advertisers/{advertiserId}/event-sources/sync",
    "body": {
      "account": { "account_id": "{advertiserId}" },
      "event_sources": [
        {
          "event_source_id": "website_pixel",
          "name": "Website Pixel",
          "event_types": ["purchase", "lead", "add_to_cart"],
          "allowed_domains": ["example.com"]
        }
      ]
    }
  }
  ```
</CodeGroup>

`account.account_id` is required and must equal the path `{advertiserId}`. Response shape:

```json theme={null}
{
  "event_sources": [
    { "event_source_id": "website_pixel", "action": "created" }
  ]
}
```

The `action` field on each result will be one of `created`, `updated`, `unchanged`, `failed`, or `deleted`. Pass `"delete_missing": true` to archive any previously-configured sources not included in the payload.

<Tip>
  Events sent to an unconfigured event source are rejected. Always sync your
  event sources before sending events for the first time.
</Tip>

### 2. Get Your API Key

1. Visit [interchange.io/user-api-keys](https://interchange.io/user-api-keys)
2. Sign up or log into your Scope3 account
3. Generate a new API key (starts with `scope3_`)

See [Authentication](/v2/authentication) for detailed setup instructions.

## Endpoints

<Note>
  **Different host on purpose.** The Conversion API ingests events at
  `ping.interchange.io`, not the main `api.interchange.io` host. CAPI runs on
  separate high-throughput ingestion infrastructure tuned for event firehose
  workloads, so the host difference is intentional — not a typo.
</Note>

```
POST https://ping.interchange.io/agentic/v1/capi
Content-Type: application/json
Authorization: Bearer scope3_<your_api_key>
```

## Request Format

### Minimal Example (Purchase with Click ID)

```json theme={null}
{
  "event_source_id": "website_pixel",
  "events": [
    {
      "event_id": "order_12345",
      "event_type": "purchase",
      "event_time": "2026-01-15T14:30:00Z",
      "action_source": "website",
      "event_source_url": "https://www.example.com/checkout/confirm",
      "user_match": {
        "click_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
        "click_id_type": "sc3clid"
      },
      "custom_data": {
        "value": 99.99,
        "currency": "USD"
      }
    }
  ]
}
```

### Full Example (with Identity Tokens and Line Items)

```json theme={null}
{
  "event_source_id": "website_pixel",
  "events": [
    {
      "event_id": "order_12345",
      "event_type": "purchase",
      "event_time": "2026-01-15T14:30:00Z",
      "action_source": "website",
      "event_source_url": "https://www.example.com/checkout/confirm",
      "user_match": {
        "click_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
        "click_id_type": "sc3clid",
        "hashed_email": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
        "uids": [
          { "type": "rampid", "value": "XY1234567890AB" },
          { "type": "id5", "value": "ID5-ABCDEF123456" }
        ]
      },
      "custom_data": {
        "value": 149.99,
        "currency": "USD",
        "order_id": "txn_12345",
        "num_items": 3,
        "contents": [
          { "id": "SKU-001", "quantity": 2, "price": 49.99 },
          { "id": "SKU-002", "quantity": 1, "price": 50.01 }
        ]
      }
    }
  ]
}
```

### Batch Example (Multiple Events)

Send up to 10,000 events in a single request:

```json theme={null}
{
  "event_source_id": "website_pixel",
  "events": [
    {
      "event_id": "evt_purchase_001",
      "event_type": "purchase",
      "event_time": "2026-01-15T10:00:00Z",
      "action_source": "website",
      "user_match": {
        "hashed_email": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
      },
      "custom_data": {
        "value": 89.99,
        "currency": "USD",
        "order_id": "order_001"
      }
    },
    {
      "event_id": "evt_lead_002",
      "event_type": "lead",
      "event_time": "2026-01-15T11:30:00Z",
      "action_source": "website",
      "user_match": {
        "click_id": "abc123def456",
        "click_id_type": "fbclid"
      }
    }
  ]
}
```

## Request Fields

### Top-Level

| Field             | Type     | Required | Description                                                                         |
| ----------------- | -------- | -------- | ----------------------------------------------------------------------------------- |
| `event_source_id` | string   | Yes      | Event source ID configured via `sync_event_sources`                                 |
| `events`          | Event\[] | Yes      | Events to log (min 1, max 10,000)                                                   |
| `test_event_code` | string   | No       | Validate events without affecting production data (see [Test Events](#test-events)) |

### Event Object

| Field               | Type       | Required | Description                                                                                                                |
| ------------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `event_id`          | string     | Yes      | Unique identifier for deduplication (scoped to `event_type` + `event_source_id`). Max 256 chars.                           |
| `event_type`        | string     | Yes      | Standard event type (see [Supported Event Types](#supported-event-types))                                                  |
| `event_time`        | string     | Yes      | ISO 8601 timestamp when the event occurred                                                                                 |
| `user_match`        | UserMatch  | No       | User identifiers for attribution matching                                                                                  |
| `custom_data`       | CustomData | No       | Event-specific data (value, currency, items)                                                                               |
| `action_source`     | string     | No       | Where the event occurred (`website`, `app`, `in_store`, `crm`, `phone_call`, `chat`, `physical_store`, `system_generated`) |
| `event_source_url`  | string     | No       | URL where the event occurred (recommended when `action_source` is `website`)                                               |
| `custom_event_name` | string     | No       | Name for custom events (used when `event_type` is `custom`)                                                                |

### UserMatch Object

| Field               | Type   | Description                                                                                     |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------- |
| `click_id`          | string | Click identifier from the landing page URL (see [Click Attribution](#click-attribution-online)) |
| `click_id_type`     | string | Type of click ID. For Scope3, use `sc3clid`. Also accepts `gclid`, `fbclid`, `ttclid`, etc.     |
| `uids`              | UID\[] | Pre-resolved universal IDs (`rampid`, `id5`, `uid2`, `euid`, `pairid`, `maid`)                  |
| `hashed_email`      | string | SHA-256 hash of lowercase, trimmed email address (64-char hex)                                  |
| `hashed_phone`      | string | SHA-256 hash of E.164-formatted phone number (64-char hex)                                      |
| `client_ip`         | string | Client IP address for probabilistic matching                                                    |
| `client_user_agent` | string | Client user agent string for probabilistic matching                                             |

Provide the strongest identifiers available. Sending multiple types increases match rates.

### CustomData Object

| Field          | Type       | Description                                       |
| -------------- | ---------- | ------------------------------------------------- |
| `value`        | number     | Monetary value of the event                       |
| `currency`     | string     | ISO 4217 currency code (e.g. `USD`, `EUR`, `GBP`) |
| `order_id`     | string     | Unique order or transaction identifier            |
| `num_items`    | integer    | Number of items in the event                      |
| `content_ids`  | string\[]  | Product or content identifiers (e.g. SKUs, GTINs) |
| `content_type` | string     | Category of content (e.g. `product`, `service`)   |
| `contents`     | Content\[] | Per-item details: `id`, `quantity`, `price`       |

## Supported Event Types

<AccordionGroup>
  <Accordion title="Commerce Events">
    | Event Type          | Description                                               |
    | ------------------- | --------------------------------------------------------- |
    | `add_to_cart`       | User added an item to cart                                |
    | `remove_from_cart`  | User removed an item from cart                            |
    | `viewed_cart`       | User viewed their shopping cart                           |
    | `add_to_wishlist`   | User added an item to a wishlist                          |
    | `initiate_checkout` | User started checkout process                             |
    | `add_payment_info`  | User added payment information                            |
    | `purchase`          | User completed a purchase                                 |
    | `refund`            | A purchase was fully or partially refunded (adjusts ROAS) |
  </Accordion>

  <Accordion title="Lead & Registration Events">
    \| Event Type | Description | |------------|-------------| | `lead` | User
    expressed interest (form submission, signup, etc.) | | `qualify_lead` | Lead
    qualified by sales or scoring criteria | | `close_convert_lead` | Lead
    converted to a customer or closed deal | | `disqualify_lead` | Lead
    disqualified or marked as not viable | | `complete_registration` | User
    completed account registration | | `subscribe` | User subscribed to a service
    or newsletter | | `start_trial` | User started a free trial | |
    `submit_application` | User submitted an application (loan, job, etc.) |
  </Accordion>

  <Accordion title="Engagement Events">
    \| Event Type | Description | |------------|-------------| | `page_view` | User
    viewed a page | | `view_content` | User viewed specific content (product,
    article, etc.) where the view is mostly context for a lower-funnel goal | |
    `content_view` | User meaningfully consumed a content unit (a watch, listen,
    stream, episode play, or read) where the consumption itself is the optimized
    event | | `watch_milestone` | User reached a content progress threshold (25%,
    50%, 75%, 100%, or a seconds-viewed milestone) | | `follow` |
    User made a free, durable opt-in to an account, channel, feed, list, podcast,
    or similar owned property | | `select_content` | User selected or clicked on
    content | | `select_item` | User selected a specific product or item from a
    list | | `search` | User performed a search | | `share` | User shared content
    via social or messaging |
  </Accordion>

  <Accordion title="App & Other Events">
    | Event Type    | Description                                        |
    | ------------- | -------------------------------------------------- |
    | `app_install` | User installed an application                      |
    | `app_launch`  | User launched an application                       |
    | `contact`     | User initiated contact (call, message, etc.)       |
    | `schedule`    | User scheduled an appointment or event             |
    | `donate`      | User made a donation                               |
    | `custom`      | Custom event type (specify in `custom_event_name`) |
  </Accordion>
</AccordionGroup>

See the [AdCP conversion tracking spec](https://docs.adcontextprotocol.org/docs/media-buy/conversion-tracking#event-types) for the complete list of supported event types.

## Attribution Methods

Scope3 supports two attribution methods depending on your use case:

| Method                  | Use Case                                 | How It Works                                    |
| ----------------------- | ---------------------------------------- | ----------------------------------------------- |
| **Click Attribution**   | Online conversions (same session/device) | Pass the `sc3clid` from the landing page URL    |
| **Offline Attribution** | Cross-device or offline conversions      | Pass pre-resolved identity tokens or hashed PII |

### Click Attribution (Online)

When a user clicks an ad served through Scope3, we append a unique `sc3clid` parameter to the landing page URL:

```
https://your-site.com/landing?sc3clid=01ARZ3NDEKTSV4RRFFQ69G5FAV&axem=...
```

**To enable click attribution:**

1. Capture the `sc3clid` from the URL when the user lands
2. Store it (cookie, session, or database)
3. Include it as `user_match.click_id` with `click_id_type: "sc3clid"` when sending conversion events

```json theme={null}
{
  "event_source_id": "website_pixel",
  "events": [
    {
      "event_id": "order_12345",
      "event_type": "purchase",
      "event_time": "2026-01-15T14:30:00Z",
      "action_source": "website",
      "user_match": {
        "click_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
        "click_id_type": "sc3clid"
      },
      "custom_data": {
        "value": 99.99,
        "currency": "USD"
      }
    }
  ]
}
```

<Tip>
  Click attribution is the simplest and most accurate method when the user
  converts in the same browser session.
</Tip>

### Offline Attribution (Cross-Device / Offline)

For conversions that happen on a different device or offline, use pre-resolved identity tokens or hashed identifiers. These allow Scope3 to match the conversion back to ad exposure even without a click ID.

**Use cases:**

* User sees ad on mobile, purchases on desktop
* User sees ad online, purchases in physical store
* CRM/offline sales data upload

```json theme={null}
{
  "event_source_id": "crm_import",
  "events": [
    {
      "event_id": "store_txn_20260115_001",
      "event_type": "purchase",
      "event_time": "2026-01-15T16:45:00Z",
      "action_source": "in_store",
      "user_match": {
        "hashed_email": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
        "uids": [{ "type": "rampid", "value": "XY1234567890AB" }]
      },
      "custom_data": {
        "value": 250.0,
        "currency": "USD",
        "order_id": "POS-2026-0115-001",
        "contents": [
          { "id": "SKU-JACKET-L", "quantity": 1, "price": 189.0 },
          { "id": "SKU-SCARF-01", "quantity": 1, "price": 61.0 }
        ]
      }
    }
  ]
}
```

### Supported Identity Types

| Type     | Description                            |
| -------- | -------------------------------------- |
| `rampid` | LiveRamp RampID (person-based)         |
| `id5`    | ID5 ID (device-based)                  |
| `uid2`   | Unified ID 2.0 (person-based)          |
| `euid`   | European Unified ID (person-based)     |
| `pairid` | Publisher Advertiser ID (device-based) |
| `maid`   | Mobile Advertising ID (device-based)   |

**Hashing requirements** for `hashed_email` and `hashed_phone`:

* Must be SHA-256 hex strings (64 characters, lowercase)
* Emails: normalize to lowercase with whitespace trimmed before hashing
* Phone numbers: normalize to E.164 format (e.g. `+12065551234`) before hashing

<Warning>
  Identity tokens must be pre-resolved via your identity partner. Raw PII
  (plain-text emails, phone numbers, addresses) is **NOT** accepted.
</Warning>

## Test Events

Use `test_event_code` to validate your integration without affecting production attribution or reporting:

```json theme={null}
{
  "event_source_id": "website_pixel",
  "test_event_code": "TEST_12345",
  "events": [
    {
      "event_id": "test_evt_001",
      "event_type": "purchase",
      "event_time": "2026-01-15T14:30:00Z",
      "action_source": "website",
      "event_source_url": "https://www.example.com/checkout",
      "user_match": {
        "click_id": "test_click_abc",
        "click_id_type": "sc3clid"
      },
      "custom_data": {
        "value": 99.99,
        "currency": "USD"
      }
    }
  ]
}
```

Test events appear in the test events UI but do not affect production campaigns.

## Response Format

### Success (200 OK)

```json theme={null}
{
  "events_received": 2,
  "events_processed": 2,
  "match_quality": 0.87,
  "warnings": [],
  "partial_failures": []
}
```

| Field              | Type    | Description                                                           |
| ------------------ | ------- | --------------------------------------------------------------------- |
| `events_received`  | integer | Number of events received                                             |
| `events_processed` | integer | Number of events successfully queued                                  |
| `match_quality`    | number  | Overall match quality score (0.0–1.0)                                 |
| `warnings`         | array   | Non-fatal issues (e.g. low match quality, missing recommended fields) |
| `partial_failures` | array   | Per-event validation failures (see below)                             |

### Partial Failures

Events within a batch are processed independently. Failed events are reported in `partial_failures` without rejecting the entire batch:

```json theme={null}
{
  "events_received": 3,
  "events_processed": 2,
  "partial_failures": [
    {
      "event_id": "evt_bad_001",
      "code": "MISSING_USER_MATCH",
      "message": "No user identifiers provided"
    }
  ]
}
```

### Error Responses

Operation-level errors (auth failure, invalid event source) return an `errors` array instead of the success fields:

```json theme={null}
{
  "errors": [
    {
      "code": "EVENT_SOURCE_NOT_FOUND",
      "message": "Event source 'website_pixel' is not configured"
    }
  ]
}
```

| Error Code               | Description                           | Resolution                                                                                                        |
| ------------------------ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `EVENT_SOURCE_NOT_FOUND` | Event source not configured           | Run `sync_event_sources` first                                                                                    |
| `INVALID_EVENT_TYPE`     | Unrecognized or disallowed event type | Check event source's `event_types` configuration                                                                  |
| `INVALID_EVENT_TIME`     | Event time too far in the past/future | Use timestamps within the seller's attribution window                                                             |
| `MISSING_USER_MATCH`     | No user identifiers provided          | Include at least one of: `uids`, `hashed_email`, `hashed_phone`, `click_id`, or `client_ip` + `client_user_agent` |
| `BATCH_TOO_LARGE`        | More than 10,000 events               | Split into smaller batches                                                                                        |
| `RATE_LIMITED`           | Too many requests                     | Wait and retry with exponential backoff                                                                           |

## Code Examples

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://ping.interchange.io/agentic/v1/capi \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer scope3_<your_api_key>" \
      -d '{
        "event_source_id": "website_pixel",
        "events": [
          {
            "event_id": "order_12345",
            "event_type": "purchase",
            "event_time": "2026-01-15T14:30:00Z",
            "action_source": "website",
            "user_match": {
              "click_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
              "click_id_type": "sc3clid"
            },
            "custom_data": {
              "value": 99.99,
              "currency": "USD"
            }
          }
        ]
      }'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests
    from datetime import datetime, timezone

    response = requests.post(
        "https://ping.interchange.io/agentic/v1/capi",
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer scope3_<your_api_key>"
        },
        json={
            "event_source_id": "website_pixel",
            "events": [
                {
                    "event_id": "order_12345",
                    "event_type": "purchase",
                    "event_time": datetime.now(timezone.utc).isoformat(),
                    "action_source": "website",
                    "user_match": {
                        "click_id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
                        "click_id_type": "sc3clid"
                    },
                    "custom_data": {
                        "value": 99.99,
                        "currency": "USD"
                    }
                }
            ]
        }
    )

    result = response.json()
    if "errors" in result:
        raise Exception(f"Operation failed: {result['errors']}")

    print(f"Processed: {result['events_processed']}/{result['events_received']}")
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    const response = await fetch('https://ping.interchange.io/agentic/v1/capi', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer scope3_<your_api_key>'
      },
      body: JSON.stringify({
        event_source_id: 'website_pixel',
        events: [
          {
            event_id: 'order_12345',
            event_type: 'purchase',
            event_time: new Date().toISOString(),
            action_source: 'website',
            user_match: {
              click_id: '01ARZ3NDEKTSV4RRFFQ69G5FAV',
              click_id_type: 'sc3clid'
            },
            custom_data: {
              value: 99.99,
              currency: 'USD'
            }
          }
        ]
      })
    });

    const result = await response.json();
    if (result.errors) {
      throw new Error(`Operation failed: ${JSON.stringify(result.errors)}`);
    }

    console.log(`Processed: ${result.events_processed}/${result.events_received}`);
    if (result.partial_failures?.length) {
      for (const f of result.partial_failures) {
        console.warn(`Failed: ${f.event_id} — ${f.message}`);
      }
    }
    ```
  </Tab>
</Tabs>

## Best Practices

<AccordionGroup>
  <Accordion title="Configure Event Sources First">
    Always run `sync_event_sources` before sending events. Events sent to
    unconfigured sources are rejected with `EVENT_SOURCE_NOT_FOUND`.
  </Accordion>

  <Accordion title="Always Capture Click ID">
    Store the `sc3clid` parameter immediately when users land from ads. Use
    cookies, session storage, or your database to persist it until conversion.
  </Accordion>

  <Accordion title="Send Events Server-Side">
    Never expose your API key in client-side code. Always send conversion events
    from your backend server.
  </Accordion>

  <Accordion title="Use Stable, Deterministic Event IDs">
    Use order numbers, transaction IDs, or composite keys (e.g.
    `"purchase_user123_20260115"`) as `event_id` rather than random UUIDs. Events
    are deduplicated by `event_id` + `event_type` + `event_source_id`, so stable
    IDs ensure safe retries without duplicate counting.
  </Accordion>

  <Accordion title="Send Multiple Identity Types">
    Provide as many user identifiers as available in `user_match` (click ID,
    hashed email, UIDs). More identifiers increases match rates across devices and
    channels.
  </Accordion>

  <Accordion title="Include Value and Currency">
    For purchase events, always include `custom_data.value` and
    `custom_data.currency` to enable ROAS reporting and optimization.
  </Accordion>

  <Accordion title="Use Accurate Event Times">
    Set `event_time` to when the event actually occurred, not when you're sending
    the request. Events outside the attribution window may not be matched.
  </Accordion>

  <Accordion title="Batch When Possible">
    Send up to 10,000 events per request to reduce API calls. Events within a
    batch are processed independently — a failure in one won't affect others.
  </Accordion>

  <Accordion title="Test with test_event_code">
    Set `test_event_code` during integration to validate events without
    affecting production data.
  </Accordion>
</AccordionGroup>

## Event Deduplication

Events are deduplicated by the combination of `event_id` + `event_type` + `event_source_id`. Sending the same event multiple times is safe — duplicates are silently ignored.

## Next Steps

<CardGroup cols={2}>
  <Card title="Measurement & Incrementality" href="/v2/guides/measurement-incrementality" icon="flask">
    Use conversion data to power incrementality tests and lift measurement
  </Card>

  <Card title="Reporting" href="/v2/guides/reporting-overview" icon="chart-bar">
    Analyze conversion performance across campaigns
  </Card>

  <Card title="Authentication" href="/v2/authentication" icon="key">
    Learn more about API key management
  </Card>

  <Card title="Campaign object guide" href="/v2/object-guides/campaign" icon="bullseye-arrow">
    Set up campaigns that optimize toward your conversion events
  </Card>
</CardGroup>
