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

Deals

CRM deals and the pipelines they move through, with their values, stages and the marketing behind each one. Endpoints, parameters, and schemas.

Endpoint Method Path Permission
List deals Your pipeline, with the attribution we computed for it GET /v1/deals deals:read
List pipelines Pipelines and their stages, in order GET /v1/pipelines deals:read
Get deal One deal, with its people and its history GET /v1/deals/{id} deals:read
Get deal journey The marketing behind one deal GET /v1/deals/{id}/journey deals:read

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

List deals

Your pipeline, with the attribution we computed for it

GET /v1/deals

Requires deals:read

Deals mirrored from your CRM, each carrying the first-touch and last-touch attribution Sourceloop derived by walking the touchpoints of everyone linked to the deal. Your CRM does not hold those fields, which is the reason to read deals here rather than from the CRM API.

Every row includes `stage.normalized_bucket` (open, won, lost, other). Stage names are chosen by the customer, so a report that trusts the label breaks the day someone renames a stage.

Returns a connection-required error, not an empty list, when no CRM is connected. "No deals" and "no CRM" are different answers.

curl -s -X GET "https://app.sourceloop.ai/api/v1/deals?website=acme.com" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals?website=acme.com",
  {
    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/deals?website=acme.com",
    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.

statusstring

Allowed values 3

  • open
  • won
  • lost

pipeline_idstring

stage_idstring

account_idstring

owner_emailstring

min_amountnumber

closed_afterstring

closed_beforestring

channelstring

First-touch channel that introduced the company.

sourcestring

First-touch source.

campaignstring

First-touch campaign.

latest_channelstring

sortstring

Allowed values 2

  • amount
  • updated

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 Deals with attribution
  • 400 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.

List pipelines

Pipelines and their stages, in order

GET /v1/pipelines

Requires deals:read

Needed to interpret a deal's stage: which stages exist, what order they run in, their win probability, and which of the customer's stage names actually mean won.

curl -s -X GET "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/pipelines?website=acme.com",
  {
    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/pipelines?website=acme.com",
    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.

Responses

  • 200 Pipelines with stages
  • 400 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 deal

One deal, with its people and its history

GET /v1/deals/{id}

Requires deals:read

Adds the contacts on the deal, the append-only stage history (which survives a stage being renamed later, so time-in-stage stays computable) and the pipeline it belongs to.

curl -s -X GET "https://app.sourceloop.ai/api/v1/deals/{id}" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals/{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/deals/{id}",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()

Path parameters

idstringpathrequired

Responses

  • 200 Deal 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.

Get deal journey

The marketing behind one deal

GET /v1/deals/{id}/journey

Requires deals:read

Every session by every contact attached to the deal. The closed loop stated in full: not "paid search influenced 60,000 of pipeline", but the sessions that claim actually rests on.

curl -s -X GET "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365" \
  -H "Authorization: Bearer $SOURCELOOP_API_KEY"
const res = await fetch(
  "https://app.sourceloop.ai/api/v1/deals/{id}/journey?days=365",
  {
    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/deals/{id}/journey?days=365",
    headers={"Authorization": f"Bearer {os.environ['SOURCELOOP_API_KEY']}"},
)
data = res.json()

Path parameters

idstringpathrequired

Query parameters

daysinteger

Default 365

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