Atribu
API Reference

Customers & Journey

Paginated customer list with conversion data, and full event timeline per customer.

Customers & Journey

PII scope required

Both endpoints on this page return personally identifiable information (names, emails). Your API key must have the customers:read scope explicitly granted. Keys without this scope will receive a 403 Forbidden response.


Customer List

Endpoint
GET /api/v1/customers

Returns a paginated list of customers who achieved a specific conversion goal within the date range. Each row includes the customer's identity, the conversion details, channel attribution, and lifetime metrics.

Scope: customers:read

Parameters

ParameterTypeRequiredDescription
date_fromstringYesStart date in YYYY-MM-DD format.
date_tostringYesEnd date in YYYY-MM-DD format.
goalstringYesConversion type: payment_received, lead_created, appointment_booked, closed_won, etc.
searchstringNoSearch by customer name or email.
limitnumberNoResults per page. Default 10, max 100.
cursor_timestringNoPagination cursor timestamp (from previous response).
cursor_idstringNoPagination cursor ID (from previous response).

Request

cURL
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&limit=5"
JavaScript
const res = await fetch(
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&limit=5",
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data, pagination } = await res.json();
Python
import requests

res = requests.get(
    "https://www.atribu.app/api/v1/customers",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "goal": "payment_received",
        "limit": 5,
    },
)
body = res.json()
data = body["data"]
has_next = body["pagination"]["has_next"]

Response

Success response (200 OK)
{
  "data": [
    {
      "conversion_id": "uuid",
      "customer_profile_id": "uuid",
      "name": "Jane Smith",
      "email": "[email protected]",
      "country": "US",
      "device": "mobile",
      "channel": "Paid Social",
      "source": "ig",
      "revenue": 299.00,
      "revenue_type": "cash",
      "conversion_time": "2026-03-22T14:30:00Z",
      "time_to_complete_seconds": 172800,
      "touch_count": 3,
      "touch_channels": ["Paid Social", "Direct"],
      "conversion_count": 2,
      "total_revenue": 598.00
    }
  ],
  "pagination": {
    "has_next": true,
    "cursor": "2026-03-22T14:30:00Z|uuid"
  },
  "meta": {
    "date_from": "2026-03-01",
    "date_to": "2026-03-25",
    "profile_id": "uuid"
  }
}

Response fields

FieldTypeDescription
conversion_idstringUnique ID of this conversion event.
customer_profile_idstringCustomer's profile UUID. Use this for the Journey endpoint.
namestringCustomer's full name (may be null for anonymous visitors).
emailstringCustomer's email address (may be null).
countrystringISO country code from the converting session.
devicestringDevice type: desktop, mobile, tablet.
channelstringMarketing channel of the last touch before conversion.
sourcestringTraffic source (e.g., ig, fb, google).
revenuenumberRevenue attributed to this specific conversion.
revenue_typestringcash, pipeline, or gross.
conversion_timestringISO 8601 timestamp of the conversion.
time_to_complete_secondsnumberSeconds between first touch and conversion.
touch_countnumberNumber of marketing touchpoints before conversion.
touch_channelsstring[]Distinct channels across all touchpoints.
conversion_countnumberTotal conversions by this customer (lifetime).
total_revenuenumberTotal revenue from this customer (lifetime).

Pagination

This endpoint uses cursor-based pagination. The pagination.cursor field in the response contains the values needed to fetch the next page.

Fetch the first page with your desired limit.

Check pagination.has_next. If true, split pagination.cursor by | to get cursor_time and cursor_id.

Pass both values in the next request.

cURL — fetch page 2
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers?date_from=2026-03-01&date_to=2026-03-25&goal=payment_received&cursor_time=2026-03-22T14:30:00Z&cursor_id=uuid"
JavaScript — paginate through all results
let allCustomers = [];
let cursorTime = undefined;
let cursorId = undefined;

while (true) {
  const url = new URL("https://www.atribu.app/api/v1/customers");
  url.searchParams.set("date_from", "2026-03-01");
  url.searchParams.set("date_to", "2026-03-25");
  url.searchParams.set("goal", "payment_received");
  url.searchParams.set("limit", "50");
  if (cursorTime) url.searchParams.set("cursor_time", cursorTime);
  if (cursorId) url.searchParams.set("cursor_id", cursorId);

  const res = await fetch(url, {
    headers: { Authorization: "Bearer atb_live_YOUR_KEY" },
  });
  const body = await res.json();
  allCustomers.push(...body.data);

  if (!body.pagination.has_next) break;
  [cursorTime, cursorId] = body.pagination.cursor.split("|");
}
Python — paginate through all results
import requests

all_customers = []
cursor_time = None
cursor_id = None

while True:
    params = {
        "date_from": "2026-03-01",
        "date_to": "2026-03-25",
        "goal": "payment_received",
        "limit": 50,
    }
    if cursor_time:
        params["cursor_time"] = cursor_time
        params["cursor_id"] = cursor_id

    body = requests.get(
        "https://www.atribu.app/api/v1/customers",
        headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
        params=params,
    ).json()

    all_customers.extend(body["data"])

    if not body["pagination"]["has_next"]:
        break
    cursor_time, cursor_id = body["pagination"]["cursor"].split("|")

Customer Journey

Endpoint
GET /api/v1/customers/{id}/journey

Returns the full event timeline for a single customer -- every page view, form submission, booking, payment, and marketing touchpoint in chronological order.

Scope: customers:read

Parameters

ParameterTypeRequiredDescription
idstringYesCustomer profile UUID (path parameter).
offsetnumberNoSkip the first N events. Default 0.
limitnumberNoMaximum events to return. Default 50, max 100.

BOLA protection

This endpoint enforces object-level authorization. If the customer ID does not belong to your profile, it returns empty data (not an error). This prevents information disclosure about whether specific customer IDs exist in the system.

Request

cURL
curl -H "Authorization: Bearer atb_live_YOUR_KEY" \
  "https://www.atribu.app/api/v1/customers/customer-uuid/journey?limit=20"
JavaScript
const customerId = "customer-uuid";
const res = await fetch(
  `https://www.atribu.app/api/v1/customers/${customerId}/journey?limit=20`,
  { headers: { Authorization: "Bearer atb_live_YOUR_KEY" } }
);
const { data } = await res.json();
console.log(`${data.total_count} events in journey`);
Python
import requests

customer_id = "customer-uuid"
res = requests.get(
    f"https://www.atribu.app/api/v1/customers/{customer_id}/journey",
    headers={"Authorization": "Bearer atb_live_YOUR_KEY"},
    params={"limit": 20},
)
events = res.json()["data"]["events"]

Response

Success response (200 OK)
{
  "data": {
    "events": [
      {
        "event_type": "page_view",
        "event_name": "page_view",
        "event_time": "2026-03-18T10:15:00Z",
        "url": "https://example.com/pricing",
        "path": "/pricing",
        "channel": "Paid Social",
        "source": "ig",
        "medium": "paid",
        "campaign": "Spring Sale",
        "value_amount": null,
        "currency": null,
        "device": "mobile",
        "browser": "Safari",
        "os": "iOS",
        "country": "US",
        "city": "New York",
        "is_synthetic": false
      },
      {
        "event_type": "payment_received",
        "event_name": "payment_received",
        "event_time": "2026-03-20T16:45:00Z",
        "url": null,
        "path": null,
        "channel": null,
        "source": "stripe",
        "medium": null,
        "campaign": null,
        "value_amount": 299.00,
        "currency": "USD",
        "device": null,
        "browser": null,
        "os": null,
        "country": null,
        "city": null,
        "is_synthetic": false
      }
    ],
    "total_count": 12
  },
  "meta": {
    "profile_id": "uuid"
  }
}

Response fields

FieldTypeDescription
event_typestringEvent category (page_view, lead_created, payment_received, etc.).
event_namestringSpecific event name.
event_timestringISO 8601 timestamp.
urlstringFull page URL (web events only).
pathstringURL path component.
channelstringClassified marketing channel.
sourcestringTraffic source or payment provider.
mediumstringMarketing medium (paid, organic, referral, etc.).
campaignstringCampaign name (resolved from platform ID).
value_amountnumberMonetary value (for payment and deal events).
currencystringISO 4217 currency code.
devicestringDevice type.
browserstringBrowser name.
osstringOperating system.
countrystringISO country code.
citystringCity name.
is_syntheticbooleantrue for off-site conversions that had no website visit.
total_countnumberTotal events in the journey (use with offset/limit for paging).

Synthetic touchpoints

Events with is_synthetic: true represent off-site conversions (e.g., Meta lead forms submitted via Instagram that flow into GoHighLevel without a website visit). Atribu creates synthetic touchpoints for these so they can still be attributed to the originating ad campaign. See Synthetic Touches for details.

On this page