Segments
A segment is a named group of contacts or companies in your workspace. You create one from a list of filters, and from then on you refer to it by its id or its handle wherever an endpoint takes a segment parameter: to list its members, count them, aggregate over them, tag them, or read their meetings.
A segment can also be the target of an action that runs over every member as a background job: enrich them with Apollo, add them to a Resend segment, or enroll them in an Apollo sequence. You start the job, get a job id back, and poll it.
The MCP tools create_segment, list_segments, get_segment, enrich_segment, broadcast_to_segment, apollo_add_segment_to_sequence and get_job call the same endpoints. See the MCP tools reference.
| Endpoint | Scope | What it does |
|---|---|---|
POST /v1/segments | contacts:write or companies:write, by what the segment holds | Create a dynamic segment from filters. |
GET /v1/segments | contacts:read or companies:read, either one | List the workspace’s segments. |
GET /v1/segments/:ref | contacts:read or companies:read, by what the segment holds | One segment with its current member count. |
POST /v1/segments/:ref/actions | enrichment:write or actions:write, by action | Start a background job over every member. |
GET /v1/jobs/:id | any valid key | Status, progress and result of a job. |
Creating, listing and counting a segment never counts against your plan. Reading its members counts like any other read of contacts or companies. See Usage and metering.
Static and Dynamic Segments
Section titled “Static and Dynamic Segments”Every segment has a type.
dynamic: a saved list of filters over contacts or over companies. It stores no member list. Its members are whichever records match the filters at the moment you use it, so a contact that starts to match is in the segment on the next request.static: a fixed list of contacts that someone picked by hand on the Segments page of the dashboard. It holds contacts only.
POST /v1/segments creates dynamic segments. Static segments are created in the dashboard. Both kinds are listed, read and used through the API in the same way.
A segment is addressed by a ref: its id, or its handle if you gave it one. A handle is a short name you choose (us-customers), which is easier to keep in a script than an id.
The API has no endpoint that changes or deletes a segment. Its name, description and color are edited, and the segment is deleted, on the Segments page of the dashboard. The filters of a dynamic segment cannot be changed: create a new segment instead.
Create a Segment
Section titled “Create a Segment”POST /v1/segmentsRequired scope: contacts:write for a contacts segment, companies:write for a companies segment.
Creates a dynamic segment. The filters are checked before anything is stored: a filter that is not valid is refused with 400 invalid_filter, and no segment is created.
Request Body
Section titled “Request Body”| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Up to 100 characters. Unique in the workspace. |
entity | string | yes | contacts or companies. |
filters | array | yes | 1 to 10 filters, each { "field", "op", "value" }. A record is a member when it matches all of them. See Filters. |
handle | string | no | A short name to address the segment by: 1 to 64 lowercase letters, digits or hyphens, starting with a letter or a digit. Unique in the workspace. |
description | string | no | Up to 500 characters. |
color | string | no | A color for the segment in the dashboard. Up to 32 characters. |
Example
Section titled “Example”curl -X POST https://api.pathbound.ai/v1/segments \ -H "Authorization: Bearer $PATHBOUND_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "US customers", "handle": "us-customers", "entity": "contacts", "filters": [ { "field": "lifecyclestage", "op": "eq", "value": "customer" }, { "field": "country", "op": "eq", "value": "United States" } ] }'Response — 201 Created
Section titled “Response — 201 Created”{ "status": "success", "segment": { "id": "670b8f2e4c1a9d3e5f607182", "name": "US customers", "handle": "us-customers", "type": "dynamic", "entity": "contact", "filters": [ { "field": "lifecyclestage", "op": "eq", "value": "customer" }, { "field": "country", "op": "eq", "value": "United States" } ], "description": null, "color": null, "contact_count": 0, "created_at": "2026-10-13T09:02:11.000Z" }}| Field | Description |
|---|---|
id | The segment’s id. |
handle | The handle, or null. |
type | dynamic or static. |
entity | contact or company. The request takes the plural (contacts), the response answers in the singular. A static segment is always contact. |
filters | The stored filters. Only on a dynamic segment. |
contact_count | For a static segment, the number of contacts on its list. A dynamic segment keeps no count here: read count from Get a Segment. |
A name or a handle that is already taken is answered with 409 and code conflict.
Filters
Section titled “Filters”A segment’s filters are the API’s typed filters: each is { "field": "...", "op": "...", "value": ... }, with the fields, operators and values described under Filtering. They are the same as the filters parameter of GET /v1/contacts and GET /v1/companies, so you can try a set of filters on a list first and save it as a segment once it returns what you want.
GET /v1/schema lists the fields your workspace can filter on, with the type of each. Take field names from there: a source.field that is not on that list is either refused with 400 invalid_filter or matches nothing.
Which operators a field takes depends on its type. A property with no known type takes all of them.
| Type | Operators |
|---|---|
| Text | eq, neq, in, nin, contains, exists |
| Number, date | eq, neq, in, nin, gt, gte, lt, lte, exists |
| Boolean | eq, neq, exists |
| List of text | contains, in, exists |
A segment’s filters are stored as you send them and applied on every later read, so three details are worth getting right:
- Case.
eq,neq,inandnincompare the whole stored value, including case:"United States"does not matchunited states.containsignores case. - Values. A value is converted to the field’s type, so
"50"is accepted for a number and"2026-10-01"for a date. A value that cannot be converted is refused with400 invalid_filter, anddetailsnames the field. exists. Withfalseit matches the records where the field is missing or null.
List Segments
Section titled “List Segments”GET /v1/segmentsRequired scope: contacts:read or companies:read. Either one is enough.
Returns the workspace’s segments, static and dynamic, sorted by name. The list holds at most 200 segments and is not paged.
Response — 200 OK
Section titled “Response — 200 OK”{ "status": "success", "segments": [ { "id": "670b8f2e4c1a9d3e5f607182", "name": "US customers", "handle": "us-customers", "type": "dynamic", "entity": "contact", "filters": [ { "field": "lifecyclestage", "op": "eq", "value": "customer" }, { "field": "country", "op": "eq", "value": "United States" } ], "description": null, "color": null, "contact_count": 0, "created_at": "2026-10-13T09:02:11.000Z" }, { "id": "66f41d7a2b9c8e0f1a2b3c4d", "name": "Webinar attendees", "handle": null, "type": "static", "entity": "contact", "description": "Picked after the October webinar", "color": "#4f46e5", "contact_count": 38, "created_at": "2026-10-02T14:20:45.000Z" } ]}The fields are those of Create a Segment.
Get a Segment
Section titled “Get a Segment”GET /v1/segments/:refRequired scope: companies:read for a companies segment, contacts:read for any other.
Returns one segment and how many records are in it right now. The members themselves are not in the response: list them with GET /v1/contacts?segment=.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
ref | string | The segment’s id or its handle. |
Example
Section titled “Example”curl https://api.pathbound.ai/v1/segments/us-customers \ -H "Authorization: Bearer $PATHBOUND_API_KEY"Response — 200 OK
Section titled “Response — 200 OK”{ "status": "success", "segment": { "id": "670b8f2e4c1a9d3e5f607182", "name": "US customers", "handle": "us-customers", "type": "dynamic", "entity": "contact", "filters": [ { "field": "lifecyclestage", "op": "eq", "value": "customer" }, { "field": "country", "op": "eq", "value": "United States" } ], "description": null, "color": null, "contact_count": 0, "created_at": "2026-10-13T09:02:11.000Z" }, "count": 412}count is counted when you ask. For a dynamic segment it is the number of records that match the filters now.
The filters of a dynamic segment are checked again every time the segment is used, here and on every endpoint that takes a segment. If one of them names a source field that no longer exists, the request is answered with 400 invalid_filter.
Use a Segment on Other Endpoints
Section titled “Use a Segment on Other Endpoints”| Endpoint | Parameter | What it does |
|---|---|---|
GET /v1/contacts, GET /v1/companies | segment (query) | Only the members of one segment. Combines with every other filter, with count_only and with paging. |
| The same two | segments_any (query) | Records that are in at least one of several segments: a comma-separated list of ids or handles. The first 50 are read. |
POST /v1/aggregate | segment (body), filters.segments_any | Group and count inside a segment. For entity contacts or companies only. |
POST /v1/contacts/tags/bulk, POST /v1/companies/tags/bulk | segment (body) | Add or remove tags on every member, in place of a list of ids. When both are sent, segment is used. |
POST /v1/meetings/query | segment (body) | Meetings with the segment’s contacts. A companies segment matches anyone at its companies, and needs companies:read as well. |
curl "https://api.pathbound.ai/v1/contacts?segment=us-customers&limit=50" \ -H "Authorization: Bearer $PATHBOUND_API_KEY"Three rules hold on all of them:
- The segment has to be for the records you ask for. A dynamic contacts segment on
GET /v1/companies, or a companies segment onGET /v1/contacts, is refused with400 segment_entity_mismatch. A static segment is not checked this way: it holds contacts, and on an endpoint that reads companies it is answered200and matches nothing. Use it where contacts are read. - An unknown
segmentis an error. It is answered with404 segment_not_found, never with every record in the workspace. - An unknown entry in
segments_anyadds no members. If none of the entries names a segment, nothing matches.
Run an Action on a Segment
Section titled “Run an Action on a Segment”POST /v1/segments/:ref/actionsRequired scope: enrichment:write for apollo_enrich, actions:write for resend_add_to_segment and apollo_add_to_sequence. Both scopes are opt-in: a key has them only if they were selected when it was created. See Scopes.
Starts a background job that works through every member of the segment. The request returns at once with a job. Poll it with Get a Job.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
ref | string | The segment’s id or its handle. |
Headers
Section titled “Headers”| Header | Required | Description |
|---|---|---|
Idempotency-Key | yes | Any non-empty string that is unique to this job. See Idempotency. |
Request Body
Section titled “Request Body”action is required and selects one of three jobs. The other fields belong to one action each.
Each action also needs its integration connected and one switch turned on by a workspace admin under MCP Tools in the dashboard. For these jobs the switches apply to API keys as well as to AI agents.
| Field | Type | Required | Description |
|---|---|---|---|
action | string | yes | apollo_enrich, resend_add_to_segment or apollo_add_to_sequence. |
apollo_enrich
Section titled “apollo_enrich”Enriches every member with Apollo: contacts for a contacts segment, companies for a companies segment. Needs enrichment:write.
| Field | Type | Required | Description |
|---|---|---|---|
force | boolean | no | true enriches members that were enriched before, and pays for them again. Default false: they are skipped. |
A member that Apollo finds costs about one Apollo credit. A member that was enriched before, or a company without a domain, is not sent to Apollo and costs nothing. The other skips can cost: a member that Apollo found and that could not be saved is counted in skipped, and its credit is in credits_consumed. The job draws on the workspace’s daily credit budget for enrichment, and stops when the budget is reached.
The switch is Enrich contacts & companies, for Apollo. This action always runs on Apollo, whichever data provider serves enrichment of single records.
resend_add_to_segment
Section titled “resend_add_to_segment”Adds every contact of the segment that has an email address to a segment in your Resend account. A contact that is not in Resend yet is created there. This sends no email: it fills the audience you then send a broadcast to. Needs actions:write and a contacts segment.
| Field | Type | Required | Description |
|---|---|---|---|
resend_segment_id | string | yes | The id of the Resend segment to add to. GET /v1/integrations/resend/segments lists them. |
The switch is Manage segments, for Resend.
apollo_add_to_sequence
Section titled “apollo_add_to_sequence”Enrolls every contact of the segment in an existing Apollo sequence. Apollo then emails them on the sequence’s schedule. Needs actions:write and a contacts segment.
| Field | Type | Required | Description |
|---|---|---|---|
sequence_id | string | yes | The Apollo sequence id: 24 hexadecimal characters. |
sender_account_id | string | yes | The id of the Apollo mailbox the sequence sends from: 24 hexadecimal characters. There is no default. GET /v1/integrations/apollo/senders lists the mailboxes you may use. |
start_at | string | no | An ISO 8601 time in the future. Contacts are enrolled paused and start then. |
tags | string[] | no | Up to 20 Pathbound tags, each up to 64 characters, added to every contact that was enrolled. |
Contacts without an email address, contacts already in the sequence, and contacts that Apollo’s own safeguards refuse are skipped and counted by reason. Enrollments are capped per workspace per day, and a job that reaches the cap stops there.
The switch is Create and run sequences, for Apollo. This action is checked before a job is created: if Apollo is not connected, the switch is off, or the mailbox may not be used, the request is refused with the error codes described on the Apollo page, and nothing starts.
Example
Section titled “Example”curl -X POST https://api.pathbound.ai/v1/segments/us-customers/actions \ -H "Authorization: Bearer $PATHBOUND_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: enrich-us-customers-2026-10-13" \ -d '{ "action": "apollo_enrich" }'Response — 202 Accepted
Section titled “Response — 202 Accepted”{ "status": "success", "job_id": "670b91a74c1a9d3e5f6071a3", "action": "apollo_enrich", "job_status": "pending", "entity": "contact", "segment_ref": "us-customers", "progress": { "processed": 0, "total": 412 }, "result": null, "error": null, "created_at": "2026-10-13T09:14:02.000Z", "updated_at": "2026-10-13T09:14:02.000Z", "match_count": 412, "estimated_credits": 412}| Field | Description |
|---|---|
job_id | The id to poll. |
match_count | How many members the segment has now. |
estimated_credits | Only for apollo_enrich. An estimate of one credit per member, so it equals match_count. The job reports what was really spent in result.credits_consumed. |
The other fields are those of Get a Job.
For apollo_enrich and resend_add_to_segment, whether the integration is connected and its switch is on is checked when the job runs, not when you start it. A job that cannot run ends with job_status: "failed" and the reason in error.
The job reads the segment’s members when it runs. For a dynamic segment that can be a slightly different set from the one match_count counted.
Idempotency
Section titled “Idempotency”The Idempotency-Key identifies the job. A second request with the same key starts nothing: it is answered with 200 OK, "replay": true and the job as it stands now, without match_count or estimated_credits.
This endpoint differs from the convention across the API in three ways:
- Only the key is compared. A request that reuses a key with a different segment or a different body still returns the first job. Use a new key for every job you mean to run.
- A key is remembered for 30 days, as long as the job itself is kept, and not for 24 hours. To run the same action on the same segment again within that time, send a new key.
- A replay returns the job in whatever state it is in, one that is still running or one that failed included. There is no
409for a job in progress. To try a failed job again, send a new key.
A new key always starts a new job, and the job spends again.
Get a Job
Section titled “Get a Job”GET /v1/jobs/:idRequired scope: none. Any valid API key of the workspace can read the workspace’s jobs.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
id | string | The job_id returned by Run an Action on a Segment. |
Example
Section titled “Example”curl https://api.pathbound.ai/v1/jobs/670b91a74c1a9d3e5f6071a3 \ -H "Authorization: Bearer $PATHBOUND_API_KEY"Response — 200 OK
Section titled “Response — 200 OK”{ "status": "success", "job_id": "670b91a74c1a9d3e5f6071a3", "action": "apollo_enrich", "job_status": "running", "entity": "contact", "segment_ref": "us-customers", "progress": { "processed": 150, "total": 412 }, "result": { "requested": 150, "enriched": 96, "not_found": 21, "skipped": 33, "failed": 0, "credits_consumed": 96, "failures": [] }, "error": null, "created_at": "2026-10-13T09:14:02.000Z", "updated_at": "2026-10-13T09:15:40.000Z"}status says that this request succeeded. The state of the job is job_status.
| Field | Description |
|---|---|
action | The action the job runs. |
job_status | See Job statuses. |
entity | contact or company: what the segment holds. |
segment_ref | The id or handle the job was started with. |
progress | processed members out of total. |
result | Counters for the action, or null before the first batch. It is updated while the job runs. |
error | Why the job failed, or null. |
Job statuses
Section titled “Job statuses”job_status | Meaning |
|---|---|
pending | Accepted and waiting to start. |
running | Working through the segment. |
succeeded | Finished, and no member failed. |
partial | Finished or stopped early with some members not done: a batch failed, a daily cap was reached, or Apollo refused the rest. result says how many and why. |
failed | Could not run, or stopped on an error. error says why. result keeps what was done before that. |
Poll until job_status is succeeded, partial or failed. A job has no fixed duration: it works in batches (25 members at a time for the two Apollo actions, 100 for Resend), paced to the provider’s rate limits, and progress moves once per batch. A job that fails is not retried.
A job can be read for 30 days after it was created. After that GET /v1/jobs/:id answers 404.
Result fields
Section titled “Result fields”apollo_enrich
| Field | Description |
|---|---|
requested | Members in the batches that have completed so far. |
enriched | Members Apollo found and enriched. |
not_found | Members Apollo has no match for. |
skipped | Members not enriched for another reason: enriched before (unless force is true), a company without a domain, or a request to Apollo that failed. |
failed | Members in a batch that failed as a whole. |
credits_consumed | Apollo credits spent. |
failures | Up to 50 failed batches, each { "ids": [...], "error": "..." }. |
stopped_reason | apollo_daily_credit_cap_reached when the job stopped at the daily cap. The job is then partial. |
resend_add_to_segment
| Field | Description |
|---|---|
added | Contacts added to the Resend segment. |
failed | Contacts Resend refused. |
no_email | Contacts without an email address, which were left out. |
skipped | Always 0 for this action. |
failures | Always empty for this action. |
apollo_add_to_sequence
| Field | Description |
|---|---|
requested | Contacts in the batches that have completed so far. |
enrolled | Contacts now in the sequence. |
skipped | Contacts not enrolled, with the count per reason in skip_reasons (for example no_email, already_enrolled, or the reason Apollo gave). |
failed | Contacts in a batch that failed. |
sequence_id, sequence_name | The sequence. |
sender_email | The mailbox the sequence sends from. |
failures | Up to 50 failed batches, each { "ids": [...], "error": "...", "code": "..." }. |
stopped_reason, error | Set when the job stopped early: apollo_daily_enroll_cap_reached, or the code of the Apollo error that would have repeated for every batch. The job is then partial. |
Errors
Section titled “Errors”Errors use the common error format.
| Status | code | Meaning |
|---|---|---|
400 | none | A field failed validation. details lists each one. |
400 | invalid_filter | A filter names a source field that does not exist, uses an operator the field’s type does not take, or carries a value of the wrong type. details names the field. |
400 | IDEMPOTENCY_KEY_REQUIRED | An action was sent without the Idempotency-Key header. |
400 | validation_error | The action does not fit: resend_add_to_segment or apollo_add_to_sequence on a companies segment, a missing resend_segment_id, sequence_id or sender_account_id, or a start_at that is not in the future. |
400 | segment_entity_mismatch | A dynamic contacts segment was used where companies are read, or the reverse. A static segment used where companies are read is not an error: it matches nothing. |
403 | auth_error | The key lacks the scope the request needs. The message names it. |
404 | not_found | No segment has that id or handle, or no job has that id. |
404 | segment_not_found | The segment parameter of another endpoint names no segment. |
409 | conflict | A segment with that name or that handle already exists. |
412 | field_redacted | Only for calls made through the MCP server in a workspace that masks personal data for AI agents: a filter names a masked contact field. A direct call with an API key never gets it. |
412 | integration_missing, invalid_credentials, needs_reconsent, tool_disabled | apollo_add_to_sequence only: Apollo is not connected, its connection has to be renewed, or Create and run sequences is off. No job was created. |
400, 403 | sender_inactive, sender_not_allowed | apollo_add_to_sequence only: the mailbox is inactive in Apollo, or is not one this workspace may send from. No job was created. The Apollo page lists the other errors Apollo can answer with. |
422 | segment_invalid | The segment’s stored filters no longer resolve to any condition, so it is refused instead of matching every record. |
429 | rate_limited | The workspace has too many jobs waiting. Nothing was started: retry with the same Idempotency-Key after retry_after seconds. |
500 | none | The request could not be completed. |