Skip to content New SourceLoop MCP: chat with your attribution data in Claude, ChatGPT & Cursor
SourceLoop
API reference

Contacts

The lead ledger: list contacts with their first and last touch, read one, and pull the full visitor journey behind it.

Endpoint Method Path Permission
List contacts Your contacts, one row per conversion GET /v1/contacts conversions:read
Get contact One conversion, with its deals and value history GET /v1/contacts/{id} conversions:read
Update contact Write an outcome back PATCH /v1/contacts/{id} conversions:write
Get contact journey Every session behind one conversion, in order GET /v1/contacts/{id}/journey conversions:read

Base URL https://app.sourceloop.ai/api/v1

List contacts

Your contacts, one row per conversion

GET /v1/contacts

Requires conversions:read

One row per CONVERSION, not per person: someone who converts twice appears twice. `identity_id` is on every row for callers who need to group by person.

Returns your leads with their contact details. New keys include the `pii:read` scope by default, so email and phone come back in full.

If a key is created WITHOUT that scope, email, phone and name are masked and the email domain is preserved, so the row still identifies the company. Use that for keys given to contractors, reporting tools, or AI assistants.

Paginate with `cursor`, taking `next_cursor` from the previous response.

curl -s -X GET "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&period=last%2030%20days&type=Web%20Form" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&period=last%2030%20days&type=Web%20Form",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/contacts?website=acme.com&period=last%2030%20days&type=Web%20Form",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()

Query parameters

websitestring

Website domain, e.g. acme.com. Required only when the key covers more than one.

periodstring

Plain-language period: "last 30 days", "yesterday", "July 2026", or "2026-07-01..2026-07-31". Ignored when from/to are given.

Example last 30 days

typestring

Example Web Form

event_namestring

statusstring

emailstring

Requires the pii:read scope: searching by email is reading it.

include_spamboolean

Default false

cursorstring

limitinteger

Default 50

total_countboolean

Include meta.total_count, the exact number of matching rows. Off by default because counting scans every match while the page itself reads one page, so on a large workspace the count costs far more than the rows. Paginate with has_more and next_cursor unless you are rendering "70 of 366".

Default false

Responses

  • 200 Conversions
  • 403 Something was wrong with the request or the credential.
  • 429 Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

Get contact

One conversion, with its deals and value history

GET /v1/contacts/{id}

Requires conversions:read

Adds the CRM deals this person is attached to and the full history of value changes, each with the reason and the system that made it.

curl -s -X GET "https://app.sourceloop.ai/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/contacts/{id}",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/contacts/{id}",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()

Path parameters

idstringpathrequired

Responses

  • 200 Conversion detail
  • 404 Something was wrong with the request or the credential.
  • 429 Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

Update contact

Write an outcome back

PATCH /v1/contacts/{id}

Requires conversions:write

How your CRM tells Sourceloop that a lead became worth 14,000, which turns cost-per-lead reporting into cost-per-revenue reporting.

Send an `Idempotency-Key` header. A retried request returns the original result rather than writing twice, and a request that changes nothing writes nothing at all. Both matter because a conversion write propagates to your connected CRM and to ad-platform conversion upload.

Attribution fields are computed by Sourceloop and cannot be set.

curl -s -X PATCH "https://app.sourceloop.ai/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "won",
    "sales_value": 14000,
    "currency": "USD"
  }'
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/contacts/{id}",
  {
    method: "PATCH",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "status": "won",
      "sales_value": 14000,
      "currency": "USD"
    }),
  },
);

const data = await res.json();
import os, requests

res = requests.patch(
    "https://app.sourceloop.ai/api/v1/contacts/{id}",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
    json={
      "status": "won",
      "sales_value": 14000,
      "currency": "USD"
    },
)
data = res.json()

Path parameters

idstringpathrequired

Headers

Idempotency-Keystringheader

A unique token per distinct request. Strongly recommended.

Request body

statusstring

sales_valuenumber

quote_valuenumber

currencystring

notesstring

lead_status_rawstring

lifecycle_stage_rawstring

is_spamboolean

Responses

  • 200 Updated, or unchanged
  • 404 Something was wrong with the request or the credential.
  • 429 Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

Get contact journey

Every session behind one conversion, in order

GET /v1/contacts/{id}/journey

Requires conversions:read

The evidence the attribution numbers rest on: how this person arrived each time, and what they did once there.

Devices are merged via the identity graph first, so someone who browsed on a phone and converted on a laptop is one timeline rather than two half-journeys.

When no raw events exist (a CRM import, or a server-side conversion) the timeline is reconstructed from stored first-touch and last-touch attribution and flagged synthetic:true. Do not present that as a complete history.

curl -s -X GET "https://app.sourceloop.ai/api/v1/contacts/{id}/journey?days=365&limit=2000" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/contacts/{id}/journey?days=365&limit=2000",
  {
    method: "GET",
    headers: {
      Authorization: `Bearer ${process.env.SOURCELOOP_API_KEY}`,
    },
  },
);

const data = await res.json();
import os, requests

res = requests.get(
    "https://app.sourceloop.ai/api/v1/contacts/{id}/journey?days=365&limit=2000",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()

Path parameters

idstringpathrequired

Query parameters

daysinteger

Default 365

limitinteger

Default 2000

Responses

  • 200 Journey
  • 404 Something was wrong with the request or the credential.
  • 429 Too many requests for this workspace in the current minute. Wait Retry-After seconds and repeat the request unchanged.

Track every conversion to its true source

Capture and send full attribution data from every signup, lead, booking, and sale to your CRM and ad platforms, so you know exactly what's driving revenue.

Without SourceLoop

Untagged

Kayden Floyd

kayden@abc.com

  • SourceUnknown
  • MediumUnknown
  • CampaignUnknown
  • Landing pageUnknown
Journey
No touchpoints captured

With SourceLoop

Auto-tagged

Kayden Floyd

kayden@abc.com · Acme Co.

  • Channel Paid Social
  • CampaignFree_demo
  • Landing page/pricing
Journey
Synced to HubSpot Google Ads Meta