> For the complete documentation index, see [llms.txt](https://docs.humblytics.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.humblytics.com/ads-attribution-api.md).

# Ads Attribution API

The Ads Attribution API merges ad-spend data from your connected ad platforms (Meta, Google) with on-site session data and revenue from connected payment providers (Stripe, Foxy) to produce a per-campaign full-funnel report. Use it to answer "which campaigns are paying back?" with revenue-level granularity.

> **Base URL**: `https://app.humblytics.com/api/v1`
>
> Note: this endpoint sits under `/api/v1`, **not** `/api/external/v1` like the [External Analytics API](/external-analytics-api.md). Both bases accept the same property-scoped Bearer API key.

## Authentication

Same property-scoped API key as the External Analytics API. Generate one in the dashboard at **Utilities → API**.

```http
Authorization: Bearer <your_api_key>
```

The `propertyId` in the path must match the property linked to the key.

## Prerequisites

For `revenue`, `revenue_conversions`, and ROAS to populate, the property must have:

1. **At least one ad connector** connected (Meta Ads or Google Ads) via **Connectors** in the dashboard. Without a connector, `spend`, `impressions`, `clicks`, and `roas` come back as `0` / `null`.
2. **A revenue source** connected (Stripe is the most common). Without it, `revenue` and `roas` will be empty even if spend is flowing.
3. **Clean UTM hygiene** on outbound ad creative. Campaigns without `utm_campaign` parameters land in `unmatched_ad_campaigns` (spend visible but no session/revenue join) or `unmatched_utm_campaigns` (sessions visible but no spend join).

***

## Get Ads Attribution

```
GET /properties/{propertyId}/ads-attribution
```

Returns the full per-campaign funnel — impressions → clicks → sessions → revenue — joined across ad platforms and payment providers, plus totals and any unmatched rows.

### Query Parameters

| Name        | Type   | Required | Notes                                     |
| ----------- | ------ | -------- | ----------------------------------------- |
| `startDate` | string | Yes      | ISO date (e.g., `2026-04-01`). Inclusive. |
| `endDate`   | string | Yes      | ISO date (e.g., `2026-04-30`). Inclusive. |

### Sample Request

```bash
curl \
  -H "Authorization: Bearer $HUMBLYTICS_API_KEY" \
  "https://app.humblytics.com/api/v1/properties/PROPERTY_ID/ads-attribution?startDate=2026-04-01&endDate=2026-04-30"
```

### Sample Response

```json
{
  "campaigns": [
    {
      "campaign": "GADS_CORE_PainPoints_2026Q1",
      "utm_campaign": "core_painpoints",
      "platforms": ["google"],
      "target_urls": [
        "https://humblytics.com/solutions/ab-testing?utm_source=google&utm_medium=cpc&utm_campaign=core_painpoints"
      ],
      "top_landing_page": "/solutions/ab-testing",
      "spend": 1240.55,
      "impressions": 84210,
      "clicks": 1832,
      "ad_conversions": 41,
      "sessions": 1654,
      "revenue_conversions": 12,
      "revenue": 4380.00,
      "trial_count": 9,
      "roas": 3.53,
      "true_cpa": 103.38,
      "cost_per_trial": 137.84,
      "click_to_session_rate": 0.903,
      "avg_hours_to_convert": 38.4
    }
  ],
  "totals": {
    "spend": 1240.55,
    "impressions": 84210,
    "clicks": 1832,
    "sessions": 1654,
    "revenue": 4380.00,
    "revenue_conversions": 12,
    "trial_count": 9,
    "roas": 3.53,
    "avg_hours_to_convert": 38.4
  },
  "unmatched_ad_campaigns": [
    {
      "campaign": "META_RETARGETING_Lookalike_v3",
      "platforms": ["meta"],
      "target_urls": ["https://humblytics.com/pricing"],
      "spend": 412.30,
      "impressions": 18204,
      "clicks": 312,
      "note": "Spend visible but no matching utm_campaign found in session data — check UTM hygiene on this campaign."
    }
  ],
  "unmatched_utm_campaigns": [
    {
      "utm_campaign": "newsletter_april",
      "top_landing_page": "/blog/cro-tips",
      "sessions": 84,
      "revenue_conversions": 2,
      "revenue": "180.00",
      "trial_count": 1,
      "note": "Sessions and revenue tracked, but no matching ad spend — likely an organic/email campaign."
    }
  ],
  "breakdowns": {
    "GADS_CORE_PainPoints_2026Q1": {
      "devices": [{ "name": "desktop", "sessions": 1180 }, { "name": "mobile", "sessions": 472 }],
      "countries": [{ "name": "United States", "sessions": 1102 }],
      "browsers": [{ "name": "Chrome", "sessions": 1310 }],
      "os": [{ "name": "macOS", "sessions": 612 }],
      "pages": [{ "name": "/solutions/ab-testing", "sessions": 980 }],
      "contents": [{ "name": "ab-testing", "sessions": 980 }]
    }
  },
  "date_range": { "startDate": "2026-04-01", "endDate": "2026-04-30" },
  "connections": { "google": 1, "meta": 1 }
}
```

### Field Reference

#### `campaigns[]` — fully matched campaigns

These joined successfully across spend, sessions, and revenue data.

| Field                   | Type           | Notes                                                                               |
| ----------------------- | -------------- | ----------------------------------------------------------------------------------- |
| `campaign`              | string         | Platform-side campaign name (e.g., the Google Ads campaign name).                   |
| `utm_campaign`          | string         | The `utm_campaign` value found on session data.                                     |
| `platforms`             | string\[]      | One or more of `"google"`, `"meta"`. Multiple if the same campaign ran across both. |
| `target_urls`           | string\[]      | Distinct landing URLs tagged for this campaign.                                     |
| `top_landing_page`      | string \| null | The single most-visited landing page path.                                          |
| `spend`                 | number         | Total ad spend for the period (in account currency).                                |
| `impressions`           | number         | Total impressions delivered.                                                        |
| `clicks`                | number         | Total clicks reported by the ad platform.                                           |
| `ad_conversions`        | number         | Conversions reported by the ad platform's pixel/tag.                                |
| `sessions`              | number         | Sessions Humblytics attributed to this campaign via UTM match.                      |
| `revenue_conversions`   | number         | Number of revenue events (purchases, paid signups).                                 |
| `revenue`               | number         | Total attributed revenue.                                                           |
| `trial_count`           | number         | Trial starts attributed to this campaign.                                           |
| `roas`                  | number \| null | Revenue ÷ spend. `null` when spend is `0`.                                          |
| `true_cpa`              | number \| null | Spend ÷ revenue\_conversions. The "real" cost per acquired paying customer.         |
| `cost_per_trial`        | number \| null | Spend ÷ trial\_count.                                                               |
| `click_to_session_rate` | number \| null | Sessions ÷ clicks. Useful for spotting tracking gaps (rate ≪ 1 means lost clicks).  |
| `avg_hours_to_convert`  | number \| null | Average time from first session to revenue conversion.                              |

#### `totals`

Roll-up across all matched campaigns. Same fields as `campaigns[]` minus the campaign-specific identifiers.

#### `unmatched_ad_campaigns[]`

Spend was found on the ad platform but no matching `utm_campaign` was seen in session data. This usually means a UTM hygiene issue — the ad creative isn't tagged correctly, so traffic from it can't be attributed to revenue.

#### `unmatched_utm_campaigns[]`

The reverse: sessions and revenue were tagged with a `utm_campaign` value that has no matching ad spend. Often this is organic/email/affiliate traffic that piggybacks on UTM tagging — not a problem.

#### `breakdowns`

Keyed by `campaign`. For each fully matched campaign, returns session counts split by `devices`, `countries`, `browsers`, `os`, `pages`, and `contents` (the `utm_content` value).

#### `connections`

Counts of currently connected ad-platform connections for the property. `{ google: 0, meta: 0 }` is a strong signal the response will be empty — the user needs to connect at least one ad source.

### Notes

* All currency values are in the property's account currency (set when the connector was authorized).
* The endpoint accepts dates only (no time component); timezone is the property's default.
* Empty `campaigns[]` with non-empty `unmatched_*` arrays is the most common signal that UTM hygiene needs work — the data is flowing, it just isn't joining.
* For programmatic access patterns, see [Understanding Attribution](/understanding-your-data/understanding-attribution.md).

## Related Endpoints

* [Ads Connections API](/ads-connections-api.md) — list and inspect Meta and Google Ads connections, pull campaign-level metadata directly from the ad platforms.
* [External Analytics API → Traffic Breakdown](/external-analytics-api.md) — UTM/source breakdown for organic and unattributed traffic.
