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

# Attribution API reference

> Public, portal, and internal attribution endpoints

Host: `https://dialedapi.evomarketing.co`

The [SDK repository](https://github.com/Evo-Marketing-LLC/evo-attribution-sdk) is the reference client for the public endpoints. Read it before writing your own integration.

| Namespace | Base path                     | Auth                                                |
| --------- | ----------------------------- | --------------------------------------------------- |
| Public    | `/api/public/attribution/*`   | Brand pixel key in the body (clicks need none)      |
| Webhooks  | `/api/webhooks/attribution/*` | Unguessable URL token plus the provider's signature |
| Portal    | `/api/client/attribution/*`   | Client portal session, scoped to one brand          |
| Internal  | `/api/internal/attribution-*` | Admin JWT with `user_typ: "evo"`                    |

## Public endpoints

### Record a click

```http theme={null}
POST /api/public/attribution/clicks
```

Called by the redirect application, not by brand code. It resolves a vanity-domain hit to a link, logs the click, and returns where to send the visitor.

<ParamField body="domain" type="string" required>
  The vanity host that was hit, without `www.`
</ParamField>

<ParamField body="slug" type="string">
  The path after the domain. An unknown slug falls back to the domain's root link.
</ParamField>

<ParamField body="query" type="string">
  The incoming query string. UTM values are stored on the click and forwarded to web destinations.
</ParamField>

<ParamField body="referrer" type="string">
  Referring URL. Falls back to the request's own referrer header.
</ParamField>

<ParamField body="user_agent" type="string">
  Used to detect `ios`, `android`, or `desktop`.
</ParamField>

<ParamField body="country" type="string">
  Two-letter country code from the edge, stored on the click.
</ParamField>

<ParamField body="ip" type="string">
  Visitor IP. Hashed with a secret key for app links only, and never stored raw.
</ParamField>

<ResponseField name="redirect_url" type="string">
  Where to send the visitor: the store URL for a matching mobile platform, otherwise the decorated web destination.
</ResponseField>

<ResponseField name="click_token" type="string">
  The minted `evc_…` token.
</ResponseField>

<ResponseField name="platform_hint" type="string">
  `ios`, `android`, `desktop`, or `null`.
</ResponseField>

<ResponseField name="link_type" type="string">
  `web` or `app`.
</ResponseField>

<ResponseField name="brand_name" type="string">
  Used as the heading on the smart install page.
</ResponseField>

<ResponseField name="app" type="object">
  `null` for web links. For app links: `ios_url`, `android_url`, `android_package` (parsed from the Play URL's `id` parameter), and `fallback_url` (the token-decorated web fallback).
</ResponseField>

```json theme={null}
{
  "redirect_url": "https://play.google.com/store/apps/details?id=com.example.mobile",
  "click_token": "evc_1234",
  "platform_hint": "android",
  "link_type": "app",
  "brand_name": "Example Brand",
  "app": {
    "ios_url": "https://apps.apple.com/us/app/example/id123",
    "android_url": "https://play.google.com/store/apps/details?id=com.example.mobile",
    "android_package": "com.example.mobile",
    "fallback_url": "https://example.com/download?evo_cid=evc_1234&utm_source=evo_creators&utm_medium=brand&utm_campaign=get.example.com"
  }
}
```

An unresolvable domain and slug returns `404` with `{"error":"link_not_found"}`.

### Record an install

```http theme={null}
POST /api/public/attribution/installs
```

<ParamField body="pixel_key" type="string" required>
  The brand's public pixel key.
</ParamField>

<ParamField body="install_id" type="string" required>
  Persistent device install ID. Stripped and limited to 128 characters. This is the deduplication key.
</ParamField>

<ParamField body="platform" type="string" required>
  `ios` or `android`.
</ParamField>

<ParamField body="clipboard_token" type="string">
  The `evc_…` click token read from the clipboard at first launch.
</ParamField>

<ParamField body="code" type="string">
  A creator code the new user typed in the app.
</ParamField>

<ParamField body="app_version" type="string">
  Reported app version.
</ParamField>

<ParamField body="occurred_at" type="string">
  ISO 8601 timestamp. Defaults to now.
</ParamField>

<ResponseField name="ok" type="boolean" />

<ResponseField name="duplicate" type="boolean">
  `true` when this install ID was already recorded.
</ResponseField>

<ResponseField name="install" type="object">
  `install_id`, `platform`, `attributed`, `resolution_method` (`clipboard`, `code`, `ip`, or `unattributed`), `confidence`, `link`, `code`, and `creator`. `link`, `code`, and `creator` are `null` when unattributed.
</ResponseField>

```json theme={null}
{
  "ok": true,
  "duplicate": false,
  "install": {
    "install_id": "persistent-device-install-id",
    "platform": "ios",
    "attributed": true,
    "resolution_method": "ip",
    "confidence": 0.6,
    "link": { "id": 12, "domain": "getdupe.app", "slug": "" },
    "code": null,
    "creator": { "id": "better-auth-id", "name": "Jane Doe" }
  }
}
```

| Status | When                                           |
| ------ | ---------------------------------------------- |
| `201`  | New install recorded                           |
| `200`  | Retry of a known install ID, `duplicate: true` |
| `403`  | `invalid_pixel_key`                            |
| `422`  | `install_id_required` or `invalid_platform`    |

### Record an event

```http theme={null}
POST /api/public/attribution/events
```

<ParamField body="pixel_key" type="string" required>
  The brand's public pixel key.
</ParamField>

<ParamField body="event_type" type="string" default="purchase">
  One of `purchase`, `renewal`, `refund`, `trial`, `signup`, `lead`, `custom`, `install`.
</ParamField>

<ParamField body="source" type="string" default="pixel">
  Send `sdk` from a mobile app; anything else is recorded as `pixel`.
</ParamField>

<ParamField body="click_token" type="string">
  The `evc_…` token captured on the landing page. A token minted for another brand's link never credits this brand.
</ParamField>

<ParamField body="code" type="string">
  A creator code the customer typed at checkout.
</ParamField>

<ParamField body="transaction_id" type="string">
  Deduplication key, limited to 128 characters. Always send one.
</ParamField>

<ParamField body="external_user_id" type="string">
  Your own user id, or the persistent install ID for SDK purchases — that is what lets a purchase inherit its install's attribution.
</ParamField>

<ParamField body="amount" type="number">
  Value in major units. `amount_cents` is accepted instead.
</ParamField>

<ParamField body="currency" type="string" default="USD">
  `USD`, `CAD`, `EUR`, `GBP`, or `AUD`. Anything else falls back to `USD`.
</ParamField>

<ParamField body="occurred_at" type="string">
  ISO 8601 timestamp. Defaults to now.
</ParamField>

<ResponseField name="ok" type="boolean" />

<ResponseField name="duplicate" type="boolean">
  `true` when the same `(brand, event_type, transaction_id)` already exists.
</ResponseField>

```json theme={null}
{ "ok": true, "duplicate": false }
```

`403` for an invalid pixel key, `422` with `invalid_event_type` for an unknown event type.

### Provider webhook

```http theme={null}
POST /api/webhooks/attribution/:provider/:token
```

<ParamField path="provider" type="string" required>
  `revenuecat`, `superwall`, `stripe`, `shopify`, or `apple`.
</ParamField>

<ParamField path="token" type="string" required>
  The connection's `evw_…` inbound token. This is the baseline authentication.
</ParamField>

The body is the provider's own payload, verified with that provider's signature scheme — see [Billing connectors](/attribution/billing-connectors).

<ResponseField name="ok" type="boolean" />

<ResponseField name="recorded" type="integer">
  Ledger rows written.
</ResponseField>

<ResponseField name="ignored" type="integer">
  Events that were duplicates or map to no ledger row.
</ResponseField>

`404 unknown_connection`, `401` on a signature failure, `422` on an unparseable payload. Any error sets the connection's status to `error` with `last_error`.

## Portal endpoints

Authenticated by the client portal session and always scoped to the brand it resolves. Shared domains and another brand's data are never writable.

| Method | Path                                        | Description                                                                             |
| ------ | ------------------------------------------- | --------------------------------------------------------------------------------------- |
| GET    | `/api/client/attribution/overview`          | Headline attribution numbers for a date range                                           |
| GET    | `/api/client/attribution/links`             | The brand's creator links with clicks, installs, conversions, and revenue               |
| GET    | `/api/client/attribution/codes`             | The brand's creator codes and their live totals, read-only                              |
| GET    | `/api/client/attribution/events`            | Paginated ledger feed, filterable by event type and creator                             |
| GET    | `/api/client/attribution/setup`             | The brand's pixel key, ready-to-paste pixel snippet, SDK docs URL, and install endpoint |
| GET    | `/api/client/attribution/domains`           | EVO's shared domains plus the brand's own custom domains                                |
| POST   | `/api/client/attribution/domains`           | Register a custom domain for this brand                                                 |
| POST   | `/api/client/attribution/domains/:id/check` | Re-run Vercel verification and refresh the DNS instructions                             |
| DELETE | `/api/client/attribution/domains/:id`       | Remove a custom domain; `409 domain_has_links` if links still use it                    |
| GET    | `/api/client/attribution/connections`       | Existing billing connections plus the per-provider setup cards                          |
| POST   | `/api/client/attribution/connections`       | Create a connection and mint its webhook URL; `409 already_connected`                   |
| PATCH  | `/api/client/attribution/connections/:id`   | Update the signing secret or settings such as `bundle_id`                               |
| DELETE | `/api/client/attribution/connections/:id`   | Remove a connection                                                                     |

<Note>
  `GET /api/client/attribution/setup` is the only portal endpoint that returns the brand's pixel key. The links list never exposes pixel keys or raw creator user ids — creator attribution comes back as an `{ id, name }` object.
</Note>

## Internal endpoints

EVO team only. Requires an admin JWT with `user_typ: "evo"`. Surfaced in the admin console under **Sales Attribution**.

| Method | Path                                          | Description                                                          |
| ------ | --------------------------------------------- | -------------------------------------------------------------------- |
| GET    | `/api/internal/attribution-links`             | All links, filterable by brand, with clicks and ledger totals        |
| POST   | `/api/internal/attribution-links`             | Create a web or app link for a brand and creator                     |
| PATCH  | `/api/internal/attribution-links/:id`         | Activate, deactivate, or change destination and store URLs           |
| GET    | `/api/internal/attribution-codes`             | All creator codes, filterable by brand or creator                    |
| POST   | `/api/internal/attribution-codes`             | Create a code, dictated or generated from the creator's first name   |
| PATCH  | `/api/internal/attribution-codes/:id`         | Rename the label, activate, or deactivate                            |
| DELETE | `/api/internal/attribution-codes/:id`         | Delete an unused code; `409 code_in_use` once it has credited events |
| GET    | `/api/internal/attribution-domains`           | All domains plus whether Vercel management is configured             |
| POST   | `/api/internal/attribution-domains`           | Register a shared or brand-owned domain and attach it to Vercel      |
| POST   | `/api/internal/attribution-domains/:id/check` | Re-check verification, DNS, and certificate state                    |
| DELETE | `/api/internal/attribution-domains/:id`       | Remove a domain                                                      |
| GET    | `/api/internal/attribution-connections`       | Every brand's billing connections and their health                   |
| DELETE | `/api/internal/attribution-connections/:id`   | Remove a connection                                                  |

Connections are created by the brand in its own portal, so there is no internal create endpoint.
