Skip to content
Pathbound DOCS

REST API overview

The REST API gives your own code everything your workspace holds: contacts, companies, deals, events, conversations and meeting notes. It also lets your code act: tag records, save segments, write knowledge pages, set up automations, enrich records, and send email through the tools you have connected.

Every request goes to one base URL and carries an API key:

Terminal window
curl https://api.pathbound.ai/v1/contacts?limit=5 \
-H "Authorization: Bearer $PATHBOUND_API_KEY"

Create a key under Settings → API keys in the dashboard. Authentication explains keys and scopes, and the Quickstart makes the first three calls with you.

Each page lists its endpoints in a table at the top.

AreaWhat you can doScopes
ContactsCreate, list, filter, read, update and delete people. Read a contact’s timeline, conversations, meeting transcripts and notes.contacts:read, contacts:write
CompaniesThe same for companies, with conversations rolled up across everyone at the company.companies:read, companies:write
DealsCreate, list, read, update and delete deals and their notes.deals:read, deals:write
EventsList and count tracked events: page views, clicks, form submissions and events written by integrations.events:read
TagsAdd and remove tags on contacts, companies and deals, one record or thousands at a time.the record’s write scope
SegmentsSave a set of filters under a name, reuse it on other endpoints, and run an action on everyone in it.the record’s read and write scopes
AreaWhat you can doScopes
Batch and aggregateRead up to 25 records in one request, read notes from many meetings, and group and count on the server.the record’s read scope
Schema and usageFind out which fields you can filter on, how much of your plan is used, and which integrations are connected.differs by endpoint
KnowledgeRead and write your workspace’s own pages about its business.knowledge:read, knowledge:write
AreaWhat you can doScopes
StreamingPush changes to your endpoint, or act on matching records automatically.streams:read, streams:write
FormsPost a form on your site to Pathbound, and email everyone who submits it.events:read to list forms
IdentifyTell Pathbound which of your users a visitor is, from your server.contacts:write

These endpoints send to real people or spend credits with a provider. Each one needs an opt-in scope, and most need the integration connected and a switch turned on by a workspace admin.

AreaWhat you can doScope
GmailSend, schedule and draft email from your own mailbox.actions:write
ResendManage segments, send an email, send an event, and send broadcasts in your Resend account.actions:write
Apollo sequencesCreate sequences, enroll contacts and send single emails through Apollo.actions:write
InstantlyAdd a lead to a campaign and update a lead’s interest.actions:write
Enrichment and prospectingEnrich your records and find new people and companies through Apollo or Hunter.enrichment:write

If you want an AI agent to do this work instead of your code, connect it to the MCP server. The MCP tools call these same endpoints.

  • Send JSON with Content-Type: application/json on every request that has a body.
  • All times are ISO 8601 in UTC, for example 2026-10-12T09:30:00.000Z. A date filter accepts any ISO 8601 date or date-time.
  • Every successful response is a JSON object with "status": "success".

Every record id is a 24-character hexadecimal string, for example 665f1c2ab3d4e5f601234567. An id in any other form is refused, on most endpoints with 400. An id that is well formed but belongs to no record in your workspace is answered with 404, whether or not it exists in another workspace.

Contacts, companies, deals and events name their id contact_id, company_id, deal_id and event_id. Responses also carry it as _id or id for older integrations. Use the named field in new code.

List endpoints page with page (starting at 1) and limit, and return a pagination object:

{
"pagination": { "page": 1, "limit": 10, "total_pages": 42, "total_items": 418 }
}

limit goes up to 100 on contacts, companies, deals and events. The default differs by endpoint and is given in each endpoint’s parameter table, so pass limit rather than relying on it.

A few endpoints page differently, and say so on their page: a contact’s events use limit and offset, and long texts (a meeting transcript, a knowledge page) are read in parts.

For a “how many” question, add count_only=true. The response is { "status": "success", "count": 1234 }, with no records. Counting is free and never counts against your plan.

For “how many by X”, use POST /v1/aggregate instead of paging through a list.

The fields parameter on contact, company and deal lists chooses the shape of each row:

fieldsEach row holdsCounts against your plan
idsThe record’s id and nothing else.No
summaryThe id and a handful of key properties.Yes
full (the default)The whole record.Yes
a comma-separated list (contact lists only)The id and the fields you name.Yes, unless every field you name is an id

The cheapest way to work through a large set is to filter with fields=ids, then read the records you need with POST /v1/batch/details.

List endpoints take three kinds of filter, and you can combine them. Every filter narrows the result: a record must pass all of them.

Named parameters such as lifecycle_stage, country or created_after. Each endpoint’s page lists its own.

Typed filters. The filters parameter is a JSON array of up to 10 conditions, each {"field", "op", "value"}:

Terminal window
curl -G https://api.pathbound.ai/v1/contacts \
-H "Authorization: Bearer $PATHBOUND_API_KEY" \
--data-urlencode 'filters=[{"field":"lifecyclestage","op":"in","value":["customer","opportunity"]},{"field":"emailable.score","op":"gte","value":80}]' \
--data-urlencode 'fields=ids'

field is a property of the record (lifecyclestage, jobtitle, industry) or a field of a connected source written source.field (emailable.score, hubspot.hs_lead_status, and apollo.employee_count on a company). GET /v1/schema lists the fields your workspace has, with the type of each and the operators it takes.

opValueMeaning
eq, neqa string, number or booleanEqual, not equal.
in, ninan array of 1 to 100 valuesIs one of, is none of.
gt, gte, lt, ltea number or a dateGreater than, at least, less than, at most.
containsa non-empty stringContains the text, ignoring case.
existstrue or false (default true)The field has a value, or has none.

A number or date field takes the comparison operators and not contains. A text field takes contains and not the comparison operators. A filter that names an unknown source field, or an operator its field does not take, is answered with 400 and the code invalid_filter. It is never dropped.

eq, neq, in and nin compare the whole stored value, capitals included: "United States" does not match united states. contains ignores case. The named parameters are more forgiving: country=united states matches whatever the capitals.

Tags and segments. tags_any and tags_all filter on tags. segment and segments_any scope a list to a saved segment.

An error response has a 4xx or 5xx status and "status": "error". A request that fails validation looks like this:

{
"status": "error",
"error": "Validation failed",
"details": [
{ "type": "field", "path": "limit", "location": "query", "value": "500", "msg": "Limit must be between 1 and 100" }
]
}

Many other errors carry a code:

{
"status": "error",
"code": "not_found",
"error": "Contact not found"
}

error is a sentence for a person. code is a stable word for your code to branch on, and is present when the error has one. details says more when there is more to say: the parameter that failed, the limit that was reached. Many responses also carry a timestamp.

StatusMeaning
400The request is not valid. details names the parameter.
401The API key is missing, wrong or switched off, or the user who created it has not verified their email. See Authentication.
402Your plan’s allowance is used up, or the workspace has no active plan. See Usage and metering.
403The key lacks a scope the endpoint needs. The error text names the missing scope.
404No such record in your workspace.
409The request conflicts with something that exists: a contact with that email, or a request with the same Idempotency-Key still in flight.
412Something has to be set up first: an integration is not connected, or an admin has not switched the action on. code says which.
422The request is well formed but cannot be carried out as asked. code says why.
429A rate limit or a quota was reached. Wait for the number of seconds in the Retry-After header when it is present.
5xxSomething failed on our side. A read can be repeated. For a request that sends or spends, read Idempotency first.

A request that sends something or spends something must not happen twice because a network connection dropped. Those endpoints take an Idempotency-Key header, and most of them require it:

Terminal window
curl -X POST https://api.pathbound.ai/v1/contacts/665f1c2ab3d4e5f601234567/send_email \
-H "Authorization: Bearer $PATHBOUND_API_KEY" \
-H "Idempotency-Key: 7c1f0d5e-3b0a-4a3e-9a51-2f6f6e0f8f11" \
-H "Content-Type: application/json" \
-d '{ "subject": "Next steps", "body": "Hi Ada, ..." }'
  • Choose one key per thing you intend to do, for example a UUID you store with the job. Use the same key when you retry that one thing, and a new key for the next.
  • A request with a key that already succeeded in the last 24 hours returns the first answer and does nothing again. One endpoint remembers a key for longer: a job on a segment, for 30 days.
  • A request with a key that is still being processed is answered with 409 and the code ACTION_IN_PROGRESS. Wait and ask again with the same key.
  • An endpoint that requires the header answers 400 with the code IDEMPOTENCY_KEY_REQUIRED without it.

Several sends are at most once, and their pages say so. When the provider does not answer, Pathbound cannot know whether the message went out, and it does not try again. You get the code SEND_UNCONFIRMED. Do not repeat such a request under a new key before you have checked, in the provider or in the mailbox’s Sent folder, whether it was sent.

Requests are limited per workspace, counted across all of its keys and connected AI agents, and per IP address. The limits are set as a guard against runaway loops and abuse, well above what an integration needs in normal use, so there is nothing to plan around: read with a few requests in parallel and you will not meet them.

A request over a limit is answered with 429. The workspace limit answers with the code QUOTA_EXCEEDED and a Retry-After header: wait that many seconds and send the request again. Responses carry RateLimit-* headers that show how much of the current window is left.

Some endpoints have limits of their own, given on their pages: the public form endpoint, sends through a provider, and enrichment, which is bounded by a daily credit budget.

If you plan a large backfill or expect sustained high volume, write to [email protected] first.

Plans are measured in active profiles: the customers your workspace read in the current billing cycle. A customer is a company with everyone in it, or a contact that belongs to no company. Reading the same customer again in the same cycle, through any endpoint and any number of times, counts once. Nothing is billed per request.

Free, on every plan:

  • Every write: creating, updating, importing in bulk, tagging, notes.
  • Counts and aggregates: count_only=true, total_items, POST /v1/aggregate.
  • Filtering, sorting, searching and segment scoping when the response holds ids only (fields=ids).
  • GET /v1/schema, GET /v1/usage, GET /v1/tags, and listing segments.
  • Listing events across the workspace with fields=summary.
  • A dry_run, on the endpoints that take one.

Counts as reading a customer: any response that returns something about them. That includes a single contact, company or deal, a list with fields=summary or fields=full, a timeline, conversations, and POST /v1/batch/details. A deal counts as the company or contact it belongs to.

Check before you read. Contact and company lists, a single contact and POST /v1/batch/details take dry_run=true, which tells you what the read would cost. Nothing is returned and nothing is counted:

{ "status": "success", "dry_run": true, "match_count": 25, "estimated_activations": 7 }

estimated_activations is how many customers the read would add to this cycle’s count. GET /v1/usage returns how many you have used and how many remain.

At the limit. A plan’s allowance is a hard limit and never a charge. A read that would take you over it is answered with 402 and one of these codes, and everything you have already read this cycle stays readable:

codeMeaning
BILLING_PLAN_LIMITThe plan’s active profiles for this cycle are used up.
BILLING_TRIAL_EXPIREDThe trial ended and no plan was chosen.
BILLING_SUBSCRIPTION_PAUSEDThe subscription is paused.
BILLING_SUBSCRIPTION_INACTIVEThe subscription was canceled, or there never was one.

Plans and what each includes are on the pricing page.

An AI agent connected through the MCP server calls these same endpoints, with a few deliberate differences:

  • Deleting a contact or a company is available to an API key and refused for an agent.
  • Hiding contact PII. A workspace can hide contacts’ personal details from AI agents. That setting applies to agents only: an API key always receives the full record.
  • Switches for agents. A workspace admin switches groups of tools on and off for agents under MCP Tools in the dashboard. Some of those switches also apply to an API key. Each page says which ones apply to its endpoints.