CitationBenchTalk to Sales
API referenceLink Building

Link building CRM API — accounts, contacts, relationships, and event log

CRUD endpoints for the CRM beneath every link-building activity. Shared accounts (partner domains), contacts, per-workspace relationships, and an event log — all queryable and writable via REST and MCP.

The CRM layer beneath every link-building activity. Accounts (the partner domains), contacts (the people), relationships (per-workspace state), events (the log). All queryable, all read/write.

For the conceptual model — shared accounts, per-workspace relationships, event-driven status — see Link Building.

Endpoints

Accounts (shared, system-wide)

MethodPathPurpose
GET/v1/link-building/crm/accountList accounts (in your workspace's view)
GET/v1/link-building/crm/account/{id}Get one
PATCH/v1/link-building/crm/account/{id}Update fields where allowed

Contacts (tied to accounts)

MethodPathPurpose
GET/v1/link-building/crm/contactList contacts
GET/v1/link-building/crm/contact/{id}Get one
POST/v1/link-building/crm/contactCreate manually
PATCH/v1/link-building/crm/contact/{id}Update
DELETE/v1/link-building/crm/contact/{id}Delete
POST/v1/link-building/crm/contact/discoverDiscover contacts via Apollo

Relationships (per-workspace pipeline)

MethodPathPurpose
GET/v1/link-building/crm/relationshipList your workspace's relationships
GET/v1/link-building/crm/relationship/{id}Get one
POST/v1/link-building/crm/relationshipCreate manually
PATCH/v1/link-building/crm/relationship/{id}Update status / priority / notes
DELETE/v1/link-building/crm/relationship/{id}Archive

Events (the log)

MethodPathPurpose
GET/v1/link-building/crm/eventList events
GET/v1/link-building/crm/event/{id}Get one
POST/v1/link-building/crm/eventAdd a note / status-change event manually

curl -G .../v1/link-building/crm/account \
  --data-urlencode "minDR=40" \
  --data-urlencode "status=NEVER_CONTACTED,IN_DISCUSSION" \
  --data-urlencode "limit=50"
{
  "data": [
    {
      "id": "acct_***",
      "domain": "engineering-blog.com",
      "name": "Engineering Blog Weekly",
      "source": "SEARCH",
      "systemContactStatus": "RATE_ESTABLISHED",
      "priceLinkInsertion": 350,
      "priceGuestPost": 900,
      "domainRating": 62,
      "organicTraffic": 48500,
      "relationshipInThisWorkspace": {
        "id": "rel_***",
        "status": "ACTIVELY_ENGAGED",
        "lastContactedByUsAt": "2026-05-22T..."
      }
    }
  ],
  "total": 124
}
ParamNotes
domain / domainContains
minDR / maxDR
statusLinkBuildingAccount.systemContactStatus values
hasRelationshipboolean — filter to accounts with/without a relationship in this workspace

Returns full account record + the workspace's relationship + contacts + the most recent 50 events.


You can update some fields (your perspective): rawNotes, priceLinkInsertion, priceGuestPost, priceContext. The systemContactStatus can only be updated by the system or via an explicit DO_NOT_CONTACT action.

curl -X PATCH .../v1/link-building/crm/account/acct_*** -d '{
  "priceLinkInsertion": 450,
  "priceContext":       "Asking $450 for editorial insertions in 2026"
}'

POST /v1/link-building/crm/contact/discover

The Apollo contact-finder. Returns contacts found at the target domains.

curl -X POST .../v1/link-building/crm/contact/discover -d '{
  "accountIds": ["acct_***A", "acct_***B"],
  "roles":      ["Editor", "Head of Content", "Marketing Manager"],
  "seniority":  ["manager", "director", "vp"]
}'

Response

{
  "invocationId": "inv_***",
  "agentId": "agt_***",
  "skill": "link_building.crm.contact.discover",
  "status": "RUNNING",
  "estimatedCost": { "credits": 0.8, "durationSeconds": 30 }
}

Final result

{
  "invocationId": "inv_***",
  "status": "SUCCEEDED",
  "creditsUsed": 0.8,
  "result": {
    "perAccount": [
      {
        "accountId": "acct_***A",
        "contactsFound": [
          {
            "id": "ct_***",
            "firstName": "Marina",
            "lastName": "Olafsson",
            "position": "Senior Editor",
            "email": "marina@engineering-blog.com"
          }
        ]
      },
      {
        "accountId": "acct_***B",
        "contactsFound": [],
        "reason": "Apollo returned no contacts matching the role filter"
      }
    ]
  }
}

curl -G .../v1/link-building/crm/relationship \
  --data-urlencode "status=ACTIVELY_ENGAGED,LINK_PLACED" \
  --data-urlencode "since=2026-05-01T00:00:00Z"
{
  "data": [
    {
      "id": "rel_***",
      "accountId": "acct_***",
      "accountDomain": "engineering-blog.com",
      "status": "ACTIVELY_ENGAGED",
      "priority": "HIGH",
      "targetBlogPost": "https://engineering-blog.com/best-pm-tools",
      "ourContent": "https://acme.com/blog/engineering-team-capacity-tracking",
      "lastEventAt": "2026-05-22T..."
    }
  ]
}

curl -X PATCH .../v1/link-building/crm/relationship/rel_*** -d '{
  "status":   "LINK_PLACED",
  "priority": "MEDIUM",
  "rawNotes": "Live on /best-pm-tools — confirmed 2026-05-22"
}'

Status transitions are logged as STATUS_CHANGED events automatically.


curl -G .../v1/link-building/crm/event \
  --data-urlencode "relationshipId=rel_***" \
  --data-urlencode "type=EMAIL_SENT,EMAIL_RECEIVED" \
  --data-urlencode "limit=50"
{
  "data": [
    {
      "id": "evt_***",
      "relationshipId": "rel_***",
      "contactId": "ct_***",
      "type": "EMAIL_SENT",
      "subject": "Section 7 of your PM tools roundup",
      "content": "Hi Marina, ...",
      "metadata": { "instantlyMessageId": "im_***" },
      "occurredAt": "2026-05-08T09:14:23Z"
    },
    {
      "id": "evt_***",
      "relationshipId": "rel_***",
      "type": "EMAIL_RECEIVED",
      "subject": "RE: Section 7 of your PM tools roundup",
      "content": "Sure, send the methodology ...",
      "occurredAt": "2026-05-09T14:22:00Z"
    }
  ]
}

POST /v1/link-building/crm/event

Add a manual note or out-of-band event.

curl -X POST .../v1/link-building/crm/event -d '{
  "relationshipId": "rel_***",
  "type":           "NOTE_ADDED",
  "subject":        "Off-platform reply",
  "content":        "Marina replied on LinkedIn DM — asked to schedule a call"
}'

MCP

> Who are my actively-engaged link-building partners?

Claude calls link_building.crm.relationship.list with status=ACTIVELY_ENGAGED.

> Find contacts at engineering-blog.com and devleadweekly.com.

Claude calls link_building.crm.contact.discover.

> Add a note to my relationship with engineering-blog.com that Marina prefers Mondays.

Claude calls link_building.crm.relationship.list to find the relationship, then link_building.crm.event.create.


Errors

StatusCodeCause
404account_not_found / relationship_not_found / contact_not_found / event_not_found
403system_field_protectedTried to PATCH a systemContactStatus field directly
422apollo_quota_exceededWorkspace's Apollo quota hit; try again next cycle

Cost

ActionCredits
All CRUD reads / writesfree
Manual send-email2 (covered by campaign.send_email)
contact.discover (per account)0.4

Use cases (string things together)

A. Daily engaged-partners digest

curl -G .../v1/link-building/crm/relationship \
  --data-urlencode "status=ACTIVELY_ENGAGED" \
  | jq '.data[] | { domain: .accountDomain, lastEventAt }'

Pipe to Slack as a morning standup post.

B. Track placement rate per source

curl -G .../v1/link-building/crm/relationship \
  --data-urlencode "status=LINK_PLACED" \
  --data-urlencode "since=2026-05-01" \
  | jq 'group_by(.source) | map({ source: .[0].source, count: length })'

C. Manual override of agent decisions

The agent moved a relationship to NOT_INTERESTED based on a polite-decline reply. You disagree. PATCH it back to PROSPECTING.

curl -X PATCH .../v1/link-building/crm/relationship/rel_*** -d '{
  "status":   "PROSPECTING",
  "rawNotes": "Re-evaluated; their decline was about timing, not content"
}'

D. Cross-portfolio account intelligence

Because accounts are shared system-wide, agencies see other agencies' negotiated rates and DO_NOT_CONTACT flags. Powerful for portfolio benchmarking.

On this page