API Reference for integrating with Draftboard platform | Version 0.9.17
All API requests require authentication using a Bearer token. Include your API token in the Authorization header of every request.
Generate and manage your API tokens in the app under Settings → API Keys.
If you have any questions, contact [email protected]
Authorization: Bearer YOUR_API_TOKEN_HERE
Note: Keep your API token secure. Do not share it publicly or commit it to version control.
https://intros.draftboard.com
Changes are grouped by release, then by type — New (additions), Changed (behavior changes to existing endpoints), and Removed (deletions). The most recent release is at the top.
Filtering targets by a tag you do not have is refused instead of ignored.
ChangedtagNames or tagIds entry that is not one of your tags is refused with 400 naming it (Unknown tagName(s): … / Unknown tagId(s): …). Before, an unknown name was ignored and every target came back; an unknown id returned nothing. tagNames now match case-insensitively, as on GET/connectors.See how many of your connections carry each label before you filter. One new read endpoint, and clearer refusals.
New0 included), how many you rated at each rating 5..1 or left unrated, and the total, under the same filters as GET/supporters. Read-only: no credit balance needed. Refused with 412 when your own LinkedIn is not connected.412 and a sentence saying so, instead of a 500. See Error Handling.error[0] are written for you to act on: they no longer name internal statuses or services, and an unexpected failure says only that it failed. Machine-readable codes such as INSUFFICIENT_CREDITS are unchanged.name are named after what they search (kind, companies, titles) instead of a date.Importing supporters no longer rates them for you. The request and response are unchanged.
Changed2..5 is what makes someone a supporter (the supporter label).Find people by label on more endpoints, see the labels on every connection, and look up the company to label. Additive except one: an account id is no longer accepted where a company id is expected. Requests without the new filters return the same results as before.
Newquery) and / or by label (labels). Each row's id is the id PUT/companies/{company_id}/labels takes, and connectorsCount is how many people a label on it reaches.labels, the connector's labels as you see them (the same tokens as on GET/supporters).connectorLabels, connectorLabelsMatch and excludeConnectorLabels, the label filters GET/targets/{target_id}/connections already takes, e.g. connectorLabels=investor = "my strongest paths through our investors".labels, labelsMatch and excludeLabels: only connectors carrying these labels, alone or together with the tag filters.company_id is an id from GET/companies. An account id from GET/accounts is not a company id and is a 400.Ask for paths only through the people you would actually ask. Additive only: a new optional filter on two endpoints and your own star rating on each connection. Requests without the filter return the same results as before.
Newrating filter: only paths through connectors you rated with one of these stars, e.g. rating=4&rating=5 (or filters[rating][]=4&filters[rating][]=5, as on GET/supporters). Each connection now carries rating, your own star rating of the connector; absent when you have not rated them.rating filter, 2..5. The connector on each row already carried your rating.GET/me reports your credit balance — the number your writes depend on. Two fields that described limits no longer in force are removed.
Newcredits: your organization's credit balance, shared by your team. Writes are refused with 402 INSUFFICIENT_CREDITS once it reaches 0, so you can see that coming instead of finding out on a failed write.subscription is absent unless you have a subscription in force, instead of an empty or expired plan.tokens. It described a quota that no longer limits anything; read credits instead.subscription.apiAccessMode. It never restricted any request; what decides whether a write goes through is credits.List your own LinkedIn connections, or a teammate's, and see who on your team knows each person. Additive only: a new optional filter and a new owners field on supporters. Requests without the filter return the same list as before.
filters[ownerIds][]: only the people the given team members are connected to on LinkedIn. Pass your own customerProfileId from GET/me for your own network, or teamMembers[].id for a teammate's.owners: the members of your team who know them, strongest first.nextPage is 0 returns each person once.customerProfileId is always your own member id, the one to pass as filters[ownerIds][] for your own network.Labels: say who a person is to you, then work only through that group — for example "paths to this target through our investors". Additive only: two new endpoints, new optional filters, and a new labels field on supporters. Existing requests and response fields are unchanged.
investor, customer, advisor, partner, do_not_contact) are shared with your team; personal ones (close_friend, family, mentor) are yours only. Writing requires a funded credit balance — see Error Handling.investor, customer, partner, do_not_contact): everyone in your network who currently works there carries the label. Writing requires a funded credit balance.labels (so does the connector on GET /connectors, GET /connectors/{connector_id}/intros and GET /paths/strongest); new filters filters[labels][], filters[labelsMatch] (all / any) and filters[excludeLabels][].connectorLabels, connectorLabelsMatch and excludeConnectorLabels.A supporter search can score everyone it finds against your ideal customer. Additive only: the new request inputs (icp, minIcpFit) are optional, and icpFits is a new response field on pool prospects. Existing requests and response fields are unchanged.
icp: enabled plus at least one of industry,
companySize, location, description. An enabled icp
cannot be combined with companies (400).icpFits, one entry per search that scored it
(campaignId, percent 0–100 or absent when it could not be assessed,
reason). New filter minIcpFit (0–100, requires campaignId).A supporter search now leaves every person it finds in the pool for you to review.
ChangedremainingCapacity is for information only.Documentation only — no endpoint, field or behavior changed.
ChangedYour strongest open paths, in one call.
Newbasis explaining why it ranks where it does. Filterable by
accountId, tagIds/tagNames, title, and
ownerIds.Find a saved target by name.
Newname filter. Every word you pass must appear in the target's first or last
name, in any order, case-insensitive — ?name=Jim%20Smith and
?name=smith%20jim find the same people. When several targets share a name, all of
them are returned, so you can tell them apart by position.Tag filters can now match any of the tags you pass. Several tags used to mean "carries every one"; that is still the default.
NewtagMatch on GET /targets, GET /pool and
GET /connectors. any keeps a row carrying at least one of the
tags; all (the default) keeps only rows carrying every one. On
GET /connectors, all keeps its meaning: each tag reached, possibly
through different targets.The two search endpoints no longer cap how much you can send them.
POST /search/accounts and POST /search/supporters refused a request that
carried more than a set number of companies, titles or supporters. They now take as many as you have.
POST /search/accounts takes any number of companies and
titles. At least one of each is still required, and each company must
still be a website/domain or a linkedin.com/company/… URL.POST /search/supporters takes any number of supporters,
titles and companies. At least one supporter and one title
are still required; companies stays optional.POST /supporters/import now states its limit of 100 entries
per request. The limit is not new; this page never mentioned it, so a
larger list was refused for a reason the reference did not give.The out-of-credits refusal now answers in the same shape as every other error.
A 402 used to arrive in a body of its own, so a client that parsed our errors correctly
everywhere else failed to parse the one that says the balance ran out.
402 now returns the common error envelope —
{"status": 402, "timestamp": "…", "error": ["INSUFFICIENT_CREDITS"]}. The
machine-readable code moved to error[0], the same slot that carries
rate_limited on a 429.reason and message fields are gone from that body.
Read the code from error[0]; the wording shown to your users is yours to choose.
This is the only place either field appeared.400, the one body
that reports per-field errors instead of an error array — it was
described nowhere, and a client written from this page would have failed to read it.Documentation only — no endpoint, field or behavior changed. This page described two gates that do not exist and named the wrong codes for the ones that do. Everything below is a correction to the reference, not a change to the API.
Changed402, not 403. It carries
INSUFFICIENT_CREDITS as its machine-readable code.403, not 401.
The API never returns 401.402, 403 and 503 are now in the status-code
table, and 503 is described: when the credit balance cannot be
verified the write is refused rather than let through, and the call is safe to retry.error is an array, there is a timestamp, and there is no
message field.apiAccessMode in GET/me
is marked as informational: it is reported, and it gates nothing.The companies you cannot reach, and the strength behind the order. GET/accounts can now list the companies where you have saved people and hold no path at all, returns the rank it sorts by, and no longer shrinks a company's counts when your search matched a person.
NewconnectionDegree=none — the companies where you have saved targets and we hold no connection path to any of them. These rows exist today but sort last, so paging rarely reaches them. Note 1st and 2nd overlap (a company can have targets at both degrees); none shares nothing with either.score on every account — the highest relationship rank (0–100) across that company's connection paths, and the value the list is already sorted by. 0 means none of the paths carries a rank; it does not mean there is no path — pathsCount is what tells you that.query now selects which companies are returned without changing what their counts mean. It has always matched a company name or a saved person's name or job title. When it matched a person, the counts on that company silently shrank to cover only the people who matched, and nothing in the response distinguished that from a company-wide total — the same company could report targetsCount 8 for its own name and 1 for a person's. targetsCount, pathsCount, firstDegreeCount, secondDegreeCount and score now always describe the whole company, matching what these fields have always been documented to be. If you search by a person's name, expect larger numbers than before.connectionDegree and query are independent: the person who matched your search need not be the one at the requested degree. A company qualifies because it has a target at that degree.Documentation only — no endpoint, field or behavior changed.
Changedrelationships and relationshipDetails descriptions now state the contract plainly: each field is present when we hold that signal for the pair and omitted when we do not, so read them defensively (connection.relationships ?? []). An absent field is not a statement about the strength of the connection; scoreDetails carries the human-readable summary.tier and rating are each documented on their own terms — rating is 1..5 where higher is better, tier is 1..5 where lower is better — as two spellings of one setting.education record, so all three relationshipDetails shapes — employment, education, mutualConnections — appear in the examples.Structured relationships, and a rating that reads the right way up. Sort connections by how the connector knows the target without parsing our sentences, and read or write a connector's star rating in the same direction the app shows it.
Newrelationships — current_colleague / former_colleague / university_classmate — and relationshipDetails, the employment / education / mutual-connection facts behind the score, with ISO yyyy-MM-dd dates. Both fields are present when we hold that signal for the pair and omitted when we do not. Empty means the key is absent, not [] — read them as connection.relationships ?? []. An absent field is not a statement about the strength of the connection; scoreDetails is unchanged and stays the human-readable summary.rating — the same rating as tier, counted the way the app's stars do: 1..5, higher is better. Absent exactly when tier is absent (unreviewed); never 0.{ "rating": 1..5 } as an alternative to { "tier": 0..5 } — send exactly one of the two, or the request is rejected with 400. Clearing a rating stays { "tier": 0 }: rating has no zero.filters[rating][] — the same filter on the app's star scale, mirroring filters[tiers][]. Passing both is a union, not an intersection.tier and rating on each connector. Both are absent when you have not rated the connector.1 ("don't ask" — the same rung as tier 5) is hidden and does not appear in the default listings, exactly as before. It is now returned when GET/supporters is called with filters[rating][]=1 (or the equivalent filters[tiers][]=5) — the rating filter doubles as the "Hidden" scope, so there is no separate flag. Default result sets are unchanged.Connector ratings (tiers). Set and read a personal "star rating" for any connector, straight from the API.
New{ "tier": 0..5 } — 1 = "ask anytime" (the best), through 5 = "do not ask" (which also excludes the connector), 0 = clear. Lower is better. Personal to the API-key owner.tier field per supporter (your personal rating; absent when unreviewed) and accepts filters[tiers][] to filter by it.Supporter Search. Scan the networks of specific supporters (people you know) for people matching a persona, straight from the API.
Newsupporters (at least one LinkedIn /in/ profile URL), titles (at least one job title/role), optional companies (narrows the scan), an optional name, and an optional icp (ideal customer — each person found gets a fit, shown on the pool; not together with companies). Their networks are scanned; the search does not change anyone's rating. Returns { campaignId, importedSupporters, notImportedSupporters }. Asynchronous / fire-and-forget: everyone it finds waits in the pool for review — poll GET/pool with campaignId={campaignId} and confirm the people you want with POST/pool/confirm. Nothing becomes a target and nothing is charged until you confirm. No lifecycle emails are sent for API-sourced searches. Writing requires a funded credit balance — see Error Handling.Account Search. Kick off a search for people in specific roles at specific companies, straight from the API.
Newcompanies (at least one; each a company website/domain like acme.com or a LinkedIn company URL linkedin.com/company/…), titles (at least one job title/role), and an optional name. Returns { campaignId, imported }. Asynchronous / fire-and-forget: people it finds are added as targets automatically, up to a limit per company, and their paths are charged like any target's; the rest wait in the pool — read them with GET/pool and campaignId={campaignId} (there is no "search complete" signal). Writing requires a funded credit balance — see Error Handling. Each company must be a domain or a LinkedIn company URL — other values are dropped.Filter targets by title. Narrow your saved targets to a job title / position with a single query parameter.
Newtitle filter — a case-insensitive substring match over the target's title / position (e.g. ?title=Head%20of%20Sales). Combine it with accountId (resolved via GET/accounts, our company search) to find, for example, a "Head of Sales" at a specific company.Team-shared Shortlist. Pin the most promising intros (paths), hand them off between teammates with a note, and move them through their lifecycle — all over the API.
Newopen / sent / made / declined), or staleness; paginated.customerProfileId and a teamMembers[] roster — each member's customerProfileId, name, and LinkedIn URL. These are the ids you pass as assigneeCustomerProfileId when pinning or reassigning a shortlist intro. Previously a teammate's profile id was only discoverable indirectly, via owners[].id on a GET/targets/{target_id}/connections response.IntegrationTargetDto (so you have the target_id for the ID-scoped endpoints), or 404 if none match. Matching is on the LinkedIn vanity slug: case-insensitive and tolerant of www, trailing slashes, and query strings. Unlike GET/targets (which lists only targets that already have a warm-intro path), resolve finds any non-archived target, so the id is available even before indexing. Read-only.New endpoints:
INTRO_SENT).INTRO_MADE).reasonId: connector_declined / prospect_declined / other (default).tagIds[] and/or tagNames[] (names auto-create as manual, case-insensitive). Fills the gap where POST /targets/import silently dropped new tags when re-importing an existing LinkedIn URL.targetIds × tags ≤ 5000). Avoids N round-trips after a large /targets/import.targetsCount, pathsCount, 1st- vs 2nd-degree) and score. Filter by company name or person (query) or connection degree (connectionDegree, including none for companies with no path held).query, tagIds, campaignId, accountId, minIcpFit) and paging; each row carries its ICP fit (icpFits). Read-only.confirmedCount and remainingCapacity. Writing requires a funded credit balance — see Error Handling.new only); idempotent; confirmed prospects are unaffected (capacity-drift guard). Requires write access.New fields:
avatarUrl on Target, Connection, Supporter, TargetSummary, and the top-level connector of GET/connectors/{id}/intros — public URL of the LinkedIn-sourced avatar; absent when not enriched.logoUrl on Account — public URL of the company logo; absent when not enriched.degree on GET/targets — "1st" (you or a teammate are directly connected) or "2nd" (reachable via one connector); absent when no path exists.accountId filter on GET/targets — pass an account id from GET/accounts to list just that company's targets.filters[preferred]=true to restore the old "preferred only" behavior, or filters[preferred]=false for non-preferred only.score now populated — each supporter's relationship strength (0–100) to your team (previously always empty).subscription and tokens — subscription exposes plan code/name, seat limit/used, API access mode, and copilot flag; tokens exposes the target-token window (limit/used/remaining/window dates, unlimited flag). When unlimited is true, limit and remaining are omitted — branch on unlimited first.intros field on GET/connectors/{id}/intros — always [] after the connector-first redesign. Use the top-level connector + owners + items triple instead.Returns information about the authenticated customer: their profile, their team, their credit balance, and their subscription if one is in force. Writes are paid from credits; when it reaches 0 they are refused with 402 INSUFFICIENT_CREDITS.
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/me' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"customer": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Acme Corporation",
"user": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"firstName": "John",
"lastName": "Smith",
"linkedinUrl": "https://www.linkedin.com/in/johnsmith/"
},
"subscription": {
"planCode": "team",
"planName": "Team Plan",
"seatLimit": 3,
"seatUsed": 2,
"copilotEnabled": true
},
"credits": 148,
"customerProfileId": "c0ffee00-1111-2222-3333-444455556666",
"teamMembers": [
{
"id": "c0ffee00-1111-2222-3333-444455556666",
"firstName": "John",
"lastName": "Smith",
"linkedinUrl": "https://www.linkedin.com/in/johnsmith/"
},
{
"id": "d1d2d3d4-5566-7788-99aa-bbccddeeff00",
"firstName": "Sue",
"lastName": "Jones",
"linkedinUrl": "https://www.linkedin.com/in/suejones/"
}
]
}
}
| Field | Type | Description |
|---|---|---|
| id | string | Customer ID |
| name | string | Customer/Company name |
| user | IntegrationUserDto | API key owner info |
| subscription | IntegrationSubscriptionDto | Your subscription: planCode, planName, seatLimit, seatUsed, copilotEnabled. Absent unless you have a subscription in force — none, or one that has expired. |
| credits | number | Your organization's credit balance, in credits — the balance writes are paid from, shared by everyone on your team. At 0, writes are refused with 402 INSUFFICIENT_CREDITS. Absent when the balance could not be read: treat that as unknown, not as 0. |
| customerProfileId | string | Your own member id — the API key owner's. Pass it as filters[ownerIds][] on GET/supporters to list your own LinkedIn connections, and — like any teamMembers[].id — as assigneeCustomerProfileId for shortlist assignment. |
| teamMembers | TeamMemberDto[] | Active members of the caller's organization, you included — the assignee roster, and the ids to pass as filters[ownerIds][] on GET/supporters. |
| Field | Type | Description |
|---|---|---|
| id | string | User ID (owner_id) |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
| Field | Type | Description |
|---|---|---|
| id | string | Team member customer_profile_id — use as assigneeCustomerProfileId for shortlist assignment |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
Returns a paginated list of targets (prospects) with their profiles, status, positions, and connection paths information.
| Parameter | Type | Description |
|---|---|---|
| updatedSince | string | Return targets updated after this timestamp (ISO 8601 format) |
| tagIds | array[string] | Filter by tag IDs. An id that is not one of your tags is refused with 400. |
| tagNames | array[string] | Filter by tag names, case-insensitive. A name that is not one of your tags is refused with 400 naming it — take names from GET/tags. |
| tagMatch | string | How several tags combine: any returns targets carrying at least one of them, all only those carrying every one. Default all. |
| statuses | array[string] |
Filter by status. Available values:
new
completed
stopped
|
| accountId | string | Filter to targets at a single account (company) by id — e.g. the id returned by GET /accounts. Drill from a company to its targets. |
| title | string | Filter targets by title / position (case-insensitive substring; max 200 chars). e.g. ?title=Head%20of%20Sales. To narrow by company, resolve the account via GET /accounts then use accountId. |
| name | string | Find targets by name: every word must appear in the target's first or last name, in any order, case-insensitive (max 200 chars). e.g. ?name=Jim%20Smith. Like the other filters it only narrows this list, so a target this list does not show is not found by name either — to look one person up by their LinkedIn profile, use GET /targets/resolve. |
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page. Default: 20 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/targets?pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"targets": [
{
"id": "6f397389-dc11-4051-822a-5ffd25ec8417",
"firstName": "Michael",
"lastName": "Johnson",
"linkedinUrl": "https://www.linkedin.com/in/michaeljohnson/",
"status": "new",
"position": {
"title": "Chief Executive Officer",
"companyName": "Tech Corp",
"companyLinkedinUrl": "https://www.linkedin.com/company/techcorp/"
},
"headline": "CEO at Tech Corp | Building the future",
"score": 74,
"connectionsNumber": 68,
"degree": "1st",
"tags": ["Q1-2026", "VIP"],
"createdAt": "2026-01-13T09:24:58.468Z",
"updatedAt": "2026-01-13T09:25:02.612Z",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png"
},
{
"id": "bde92534-d194-462c-adee-6248bd2da55a",
"firstName": "Sarah",
"lastName": "Williams",
"linkedinUrl": "https://www.linkedin.com/in/sarahwilliams/",
"status": "completed",
"position": {
"title": "VP of Marketing",
"companyName": "Growth Inc"
},
"headline": "VP Marketing | Growth Expert",
"score": 45,
"connectionsNumber": 16,
"degree": "2nd",
"tags": ["Onboarding"],
"createdAt": "2026-01-12T12:40:12.456Z",
"updatedAt": "2026-01-12T12:40:13.994Z"
}
],
"count": 3,
"nextPage": 0
}
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
| status | string | Current status: new, processing, ready, in_progress, completed, no_paths, error, or stopped |
| position | IntegrationPositionDto | Current position |
| headline | string | LinkedIn headline (optional) |
| score | number | Relationship score (0-100) |
| connectionsNumber | number | Number of available connections |
| degree | string | Degree of separation to this target: "1st" (you or a teammate are directly connected) or "2nd" (reachable via one connector). Absent when no connection path exists. |
| tags | string[] | Assigned tag names |
| createdAt | string | Creation timestamp (ISO 8601) |
| updatedAt | string | Last update timestamp (ISO 8601) |
| avatarUrl | string | Absolute URL of the avatar image (LinkedIn-sourced via filestore), e.g. https://files-intros.draftboard.com/p/.... Omitted entirely when there is no stored avatar. |
| Field | Type | Description |
|---|---|---|
| title | string | Job title |
| companyName | string | Company name |
| companyLinkedinUrl | string | Company LinkedIn URL (optional) |
Resolves a LinkedIn profile URL to a single one of your targets — the intermediary step for going from a LinkedIn URL to a target_id you can then pass to the ID-scoped endpoints (e.g. GET /targets/{target_id}/connections, POST /targets/{target_id}/tags, DELETE /targets/{target_id}). Returns the full IntegrationTargetDto (same shape as items in GET /targets). Read-only.
Identity, not list visibility. Unlike GET /targets (which lists only targets that already have a warm-intro path), this endpoint resolves any of your non-archived targets — so the id is available even for a freshly imported, not-yet-indexed target (its score will be 0 and degree absent until paths are found). Matching is on the LinkedIn vanity slug: case-insensitive and tolerant of www, trailing slashes, and query strings.
| Parameter | Type | Description |
|---|---|---|
| linkedinUrl | string | Required. The target's LinkedIn profile URL, e.g. https://www.linkedin.com/in/janedoe/. Must be a profile (/in/…) URL — a non-profile or malformed value returns 400. |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/targets/resolve?linkedinUrl=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fjanedoe%2F' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"target": {
"id": "6f397389-dc11-4051-822a-5ffd25ec8417",
"firstName": "Jane",
"lastName": "Doe",
"linkedinUrl": "https://www.linkedin.com/in/janedoe/",
"status": "new",
"position": {
"title": "Chief Executive Officer",
"companyName": "Tech Corp",
"companyLinkedinUrl": "https://www.linkedin.com/company/techcorp/"
},
"headline": "CEO at Tech Corp | Building the future",
"score": 74,
"connectionsNumber": 68,
"degree": "1st",
"tags": ["Q1-2026", "VIP"],
"createdAt": "2026-01-13T09:24:58.468Z",
"updatedAt": "2026-01-13T09:25:02.612Z",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png"
}
}
The target object is the full IntegrationTargetDto — see the field reference under GET /targets.
Not found. When none of your non-archived targets match the URL, the endpoint returns HTTP 404 (the target field is not present). A 400 is returned when linkedinUrl is missing or is not a LinkedIn profile URL.
Import new targets by providing their LinkedIn URLs. Optionally assign tags to the imported targets.
Accepted, not finished — and how to confirm it. A 200 with
imported means the batch was accepted. The targets are created shortly afterwards and
their warm-intro paths are computed after that; both are asynchronous and can take several minutes.
To confirm a specific person landed, call
GET /targets/resolve?linkedinUrl=… with the URL you
imported. It returns the target as soon as the row exists — before any path is found — so a
200 confirms the write and hands you the target_id.
Do not confirm an import with
GET /targets (filtered by tags,
updatedSince or anything else): that endpoint lists only targets that
already have a warm-intro path, so a freshly imported person is legitimately absent
from it until paths are found. An empty result there is not evidence the import failed. Once
resolve returns the id, poll
GET /targets/{target_id}/connections for the paths as
they appear.
| Field | Type | Description |
|---|---|---|
| linkedinUrls required | array[string] | LinkedIn profile URLs to import. 1–1000 entries per request (a larger batch returns 400). |
| tags | array[string] | Tag names to assign to imported targets (auto-created if not present). Max 100 chars each; max 50 entries. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/targets/import' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{
"linkedinUrls": [
"https://www.linkedin.com/in/example-profile-1/",
"https://www.linkedin.com/in/example-profile-2/"
],
"tags": [
"Q1-2026",
"VIP"
]
}'
{
"status": 200,
"errors": [],
"imported": 2,
"notImported": 0,
"failedUrls": []
}
{
"status": 200,
"errors": ["Invalid LinkedIn URL format"],
"imported": 1,
"notImported": 1,
"failedUrls": ["invalid-url-format"]
}
| Field | Type | Description |
|---|---|---|
| linkedinUrls required | string[] | List of LinkedIn profile URLs to import. 1–1000 entries per request. |
| tags | string[] | Tag names to apply (will be created if not exist) |
| Field | Type | Description |
|---|---|---|
| status | number | Status code |
| errors | string[] | Error messages |
| imported | number | Number of successfully imported targets |
| notImported | number | Number of targets that failed to import |
| failedUrls | string[] | LinkedIn URLs that failed to import |
Returns a paginated list of connections (paths) to a specific target. Each connection includes the connector's profile, their relationship rank with the target, and details about their shared history — as prose in scoreDetails and, where we have it, as data in relationships and relationshipDetails.
The structured relationship fields are present when we hold that signal for the pair, and omitted when we do not. Read them defensively — connection.relationships ?? [] — and treat an absent field as "we have no structured signal for this pair": it is not a statement about the strength of the connection, and it does not mean the two people have no relationship. scoreDetails is unchanged and carries the human-readable summary; keep reading it.
Empty means absent, not []. Exactly like scoreDetails today, an empty list is omitted from the JSON entirely rather than serialized as an empty array. Read them defensively — connection.relationships ?? [] — and do not treat a missing key as an error.
The two fields are independent. Neither is derived from the other, and their positions do not line up: one relationships token can sit next to several relationshipDetails records, or none. A connection whose only structured signal is shared contacts produces a relationshipDetails record (mutualConnections) and no relationships entry at all, because "mutual connections" is not one of the three relationship types. Conversely a rank can carry relationships with no relationshipDetails. Never index-align either field against the other, or against scoreDetails.
| Parameter | Type | Description |
|---|---|---|
| target_id required | string | UUID of the target |
| Parameter | Type | Description |
|---|---|---|
| updatedSince | string | Return connections updated after this timestamp (ISO 8601 format) |
| ownerIds | array[string] | Filter by owner IDs (team member IDs) |
| connectorLabels | array[string] | Only paths through a connector carrying these labels, e.g. connectorLabels=investor — "paths to this target through our investors". Same tokens as labels on GET/supporters; an unknown token is a 400. |
| connectorLabelsMatch | string | How several connectorLabels combine: all (default) or any. |
| excludeConnectorLabels | array[string] | Paths through a connector carrying any of these labels are left out, e.g. excludeConnectorLabels=do_not_contact. |
| rating | array[number] | Only paths through connectors you rated with one of these stars, 1..5 where higher is better (5 = "ask anytime"). Repeat it or comma-join it: rating=4&rating=5 = "paths through people I would actually ask". filters[rating][]=4&filters[rating][]=5, the spelling GET/supporters uses, means the same. Your own rating, never a teammate's. Connectors you have not rated are left out while the filter is set. 1 ("do not ask") also returns the connectors you hid. |
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page. Default: 20 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/targets/6f397389-dc11-4051-822a-5ffd25ec8417/connections?pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/targets/6f397389-dc11-4051-822a-5ffd25ec8417/connections?connectorLabels=investor&excludeConnectorLabels=do_not_contact' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/targets/6f397389-dc11-4051-822a-5ffd25ec8417/connections?rating=4&rating=5' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"connections": [
{
"id": "daf886d4-d2ca-4ca2-8cd7-5546c50ada5f",
"firstName": "David",
"lastName": "Chen",
"linkedinUrl": "https://www.linkedin.com/in/davidchen/",
"position": {
"title": "Director of Engineering",
"companyName": "Draftboard",
"companyLinkedinUrl": "https://www.linkedin.com/company/draftboard/"
},
"headline": "Engineering Leader | Building great teams",
"score": 48,
"scoreDetails": [
"They overlapped for a long time (42 months) @ TechCorp, most recently in 2016"
],
"relationships": [
"former_colleague"
],
"relationshipDetails": [
{
"employment": {
"company": "TechCorp",
"department": "Engineering",
"overlapStartDate": "2012-09-01",
"overlapEndDate": "2016-03-01",
"loose": false
},
"score": 0.42
},
{
"mutualConnections": {
"count": 17
},
"score": 0.08
}
],
"owners": [
{
"id": "0dda7490-eea3-4c1c-8cec-a4824222f116",
"firstName": "John",
"lastName": "Smith",
"linkedinUrl": "https://www.linkedin.com/in/johnsmith/",
"score": 85
}
],
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png",
"rating": 5
},
{
"id": "47ba0bb4-c9f8-49be-bd97-f79d5148c1da",
"firstName": "Emily",
"lastName": "Rodriguez",
"linkedinUrl": "https://www.linkedin.com/in/emilyrodriguez/",
"position": {
"title": "Senior Software Engineer",
"companyName": "Innovation Labs"
},
"headline": "Full-stack Engineer | Open Source Contributor",
"score": 74,
"scoreDetails": [
"They overlapped for a long time (56 months) @ TechCorp, most recently in 2023",
"They overlapped for a long time (45 months) @ StartupXYZ, most recently in 2016"
],
"owners": [
{
"id": "0dda7490-eea3-4c1c-8cec-a4824222f116",
"firstName": "John",
"lastName": "Smith",
"score": 72
}
]
}
],
"count": 68,
"nextPage": 2
}
Both cases above are worth reading twice. The first connection carries one relationships token but two relationshipDetails records — the mutual-connections record has no token of its own, which is why the two lists must never be zipped together. The second connection carries neither key: we hold no structured signal for that pair. That is a normal response, not an error — read scoreDetails.
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
| position | IntegrationPositionDto | Current position |
| headline | string | LinkedIn headline (optional) |
| score | number | Relationship score (0-100, higher is better) |
| scoreDetails | string[] | Reasons for the relationship score, as finished English sentences. Unchanged by the addition of relationships / relationshipDetails — same strings, same order — and still the human-readable summary of the relationship. Absent when empty. |
| relationships | string[] | How the connector knows the target, as machine-readable tokens. Exactly three values are ever emitted: current_colleague (they are at the same company now), former_colleague (they were at the same company in the past), university_classmate (they attended the same school). More than one can apply. Absent when empty — present when we hold the signal for the pair, omitted when we do not, so read it as ?? []. Absence is not evidence of "no relationship" and says nothing about the strength of the connection; read scoreDetails. |
| relationshipDetails | IntegrationRelationshipDetailDto[] | The facts behind the relationship — one record per company, school, or mutual-connection signal, in no particular order. Absent when empty — present when we hold the records, omitted when we do not, so read it as ?? []. Independent of relationships: a mutualConnections record has no token counterpart, and a rank can carry tokens with no records. |
| owners | IntegrationMemberDto[] | Members of your team connected to this connector — i.e. who can request the intro on your behalf. Each score is that member's relationship strength (0–100) to the connector, strongest first. |
| avatarUrl | string | Absolute URL of the avatar image (LinkedIn-sourced via filestore), e.g. https://files-intros.draftboard.com/p/.... Omitted entirely when there is no stored avatar. |
| rating | number | Your own star rating of this connector, 1..5 where higher is better (5 = "ask anytime", 1 = "do not ask"), never a teammate's. Absent when you have not rated them — that is not a low rating. Filter on it with the rating query parameter. |
| labels | array[string] | The connector's labels as you see them, lowercase tokens — the same labels, with the same meaning, as labels on GET/supporters. Hidden labels are left out. Absent when the connector has no labels. Filter on them with connectorLabels / excludeConnectorLabels; the labels say which of them a connection matched. |
Exactly one of employment, education, mutualConnections is set on each record; the other two keys are absent. All dates in these shapes are ISO yyyy-MM-dd strings — day precision, no time and no timezone.
| Field | Type | Description |
|---|---|---|
| employment | IntegrationRelationshipEmploymentDto | The two were at the same company or organisation. Present on employment records only. |
| education | IntegrationRelationshipEducationDto | The two attended the same school. Present on education records only. |
| mutualConnections | IntegrationRelationshipMutualConnectionsDto | The two share contacts. Present on mutual-connection records only. Note this kind has no counterpart in relationships — "mutual connections" is not one of the three relationship types. |
| score | number | This record's contribution to the connection's overall relationship score, as a fraction. Use it to rank records of the same kind against each other — e.g. show the strongest employment overlap first. It is not on the same scale as the connection's 0–100 score and does not sum to it. Absent when the producer did not record one. |
Nothing here is precomputed — derive what you need from the overlap window: they overlapped when overlapStartDate is present; they work together now when overlapStartDate is present and overlapEndDate is absent; duration and "most recently in <year>" come from the two dates.
| Field | Type | Description |
|---|---|---|
| company | string | Company / organisation display name. Absent only when genuinely unknown. |
| department | string | Function or department context, when known (e.g. "Engineering"). |
| location | string | Shared location, when known. |
| overlapStartDate | string | ISO yyyy-MM-dd. Start of the window in which both were at the company. Present means they overlapped in time. Absent together with overlapEndDate means same company but never at the same time — or a loose affiliation, which omits the window on purpose (see loose). |
| overlapEndDate | string | ISO yyyy-MM-dd. End of the shared window. Absent while the overlap is ongoing — a record with overlapStartDate set and no overlapEndDate means they are colleagues right now. |
| loose | boolean | true = a low-confidence same-organisation affiliation. The overlap window is deliberately omitted on these records, because that is exactly where the underlying position dates are least trustworthy — so do not read the missing window as "they never overlapped". |
| unit | string | Shared military unit, when one is detected. |
| Field | Type | Description |
|---|---|---|
| school | string | School display name. Always present on an education record. |
| overlapStartDate | string | ISO yyyy-MM-dd. Start of the shared enrolment window. Absent together with overlapEndDate means both attended the school, but at different times. |
| overlapEndDate | string | ISO yyyy-MM-dd. Absent while both are currently students. |
| Field | Type | Description |
|---|---|---|
| count | number | Number of contacts the connector and the target share. |
| Field | Type | Description |
|---|---|---|
| id | string | The team member's id (the same id as teamMembers[].id on GET /me) — not the connector's id |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
| score | number | Owner's relationship score with the connection (0-100) |
Returns your strongest open paths — paths you have not yet requested — one row per target, strongest first. Each row carries the path itself (see the ConnectorIntroDto shape), the connector who can make the introduction (see the SupporterDto shape), and a basis explaining why the path ranks where it does. Once you request the intro, it drops out of this list. Read-only.
One row per target. When several connectors or several of your team's profiles could reach the same target, only the single best path for that target is returned.
| Parameter | Type | Description |
|---|---|---|
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page, 1–100. Default: 20 |
| accountId | string | Only targets at this account (id from GET /accounts). |
| tagIds | array[string] | Only targets carrying these tag ids. |
| tagNames | array[string] | Only targets carrying these tag names (alternative to tagIds). Matched case-insensitively. |
| tagMatch | string | How several tags combine: all (default) or any. |
| title | string | Case-insensitive substring of the target's title. |
| ownerIds | array[string] | Paths through these team members instead of you (member ids from GET /me). |
| rating | array[number] | Only paths through connectors you rated with one of these stars, 2..5 where higher is better, e.g. rating=5 (or filters[rating][]=5). Your own rating, also when ownerIds picks teammates. Connectors you have not rated are left out while the filter is set. 1 ("do not ask") is a 400: a connector you hid never appears here. |
| connectorLabels | array[string] | Only paths through a connector carrying these labels, e.g. connectorLabels=investor — "my strongest paths through our investors". Repeat it or comma-join it. Same tokens as labels on GET/supporters; an unknown token is a 400. |
| connectorLabelsMatch | string | How several connectorLabels combine: all (default) or any. |
| excludeConnectorLabels | array[string] | Paths through a connector carrying any of these labels are left out, e.g. excludeConnectorLabels=do_not_contact. |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/paths/strongest?pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"paths": [
{
"path": {
"id": "1b9d8f04-2c7a-4e3d-9a11-7f0c2d8e44aa",
"score": 48,
"scoreDetails": [
"They overlapped for a long time (42 months) @ TechCorp, most recently in 2016"
],
"target": {
"id": "6f397389-dc11-4051-822a-5ffd25ec8417",
"firstName": "Michael",
"lastName": "Johnson",
"linkedinUrl": "https://www.linkedin.com/in/michaeljohnson/",
"position": {
"title": "Chief Executive Officer",
"companyName": "Tech Corp"
},
"headline": "CEO at Tech Corp | Building the future"
}
},
"connector": {
"id": "daf886d4-d2ca-4ca2-8cd7-5546c50ada5f",
"firstName": "David",
"lastName": "Chen",
"linkedinUrl": "https://www.linkedin.com/in/davidchen/",
"position": {
"title": "Director of Engineering",
"companyName": "Draftboard"
}
},
"basis": "overlap_both_sides",
"owners": [
{
"id": "c2e4a890-3f1b-4a2c-9d55-1e0f7a3b9c44",
"firstName": "Sarah",
"lastName": "Miller",
"linkedinUrl": "https://www.linkedin.com/in/sarahmiller/",
"score": 95
}
]
}
],
"count": 1,
"nextPage": 0
}
| Field | Type | Description |
|---|---|---|
| status | number | HTTP status code |
| errors | string[] | Error messages (empty on success) |
| paths | StrongestPathDto[] | One entry per target — see the table below. Paginated. Omitted when there are none (an empty list is not sent as []) — read it as response.paths ?? []. |
| count | number | Number of targets with a qualifying path — one path per target, so this is not a total path count. |
| nextPage | number | Next page number (0 if no more pages) |
| Field | Type | Description |
|---|---|---|
| path | ConnectorIntroDto | The intro itself — id, score, scoreDetails, target, and (when we hold the signal) relationships / relationshipDetails. Same shape as an item of GET /connectors/{connector_id}/intros — see the field reference there. |
| connector | SupporterDto | The connector who can make the introduction. Same shape as an entry of GET /supporters, including your personal tier / rating when you have one for this connector and its labels — see the field reference there. |
| basis | string | Why this path ranks where it does. Exactly one of three values:
|
| owners | IntegrationMemberDto[] | Team members with a recorded relationship to this connector, strongest first. Same shape as owners on GET /connectors/{connector_id}/intros — see the nested DTO there. |
ownerIds entry is not a team member; an unknown tagIds or tagNames entry; resultPerPage outside 1–100.Archives a target (soft-delete via STATUS_DELETED) — equivalent to the "Archive" action in the Draftboard UI. The prospect row stays in the database; the target disappears from GET /api/v1/integration/targets and its capacity slot is released. Associated intros and indexing rows are not cascade-deleted — they remain as historical records. The action is not reversible through the public API. Returns 404 if target_id does not exist or belongs to another customer.
| Parameter | Type | Description |
|---|---|---|
| target_id required | string (UUID) | UUID of the target to delete |
curl -X 'DELETE' \ 'https://intros.draftboard.com/api/v1/integration/targets/6f397389-dc11-4051-822a-5ffd25ec8417' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"error": []
}
Note: Despite the DELETE verb, this is an archive action — same behavior as clicking "Archive" in the Draftboard UI. The prospect row stays; only status moves to STATUS_DELETED. Historical intros and indexing rows are preserved.
Marks an intro as requested (status moves to INTRO_SENT). Idempotent — re-running while already at INTRO_SENT returns 200 with no side effect.
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id from GET /targets/{id}/connections or GET /connectors/{id}/intros. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/intros/{intro_id}/requested' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{ "status": 200, "error": [] }
intro_id is missing or owned by another customer or archived.Marks an intro as complete (status moves to INTRO_MADE). Idempotent — re-running while already at INTRO_MADE returns 200 with no side effect.
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id from GET /targets/{id}/connections or GET /connectors/{id}/intros. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/intros/{intro_id}/completed' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{ "status": 200, "error": [] }
intro_id is missing or owned by another customer or archived.Marks an intro as declined (status moves to CUSTOMER_REJECTED). Idempotent — re-running while already at CUSTOMER_REJECTED returns 200 with no side effect.
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id from GET /targets/{id}/connections or GET /connectors/{id}/intros. |
| Field | Type | Description |
|---|---|---|
| reasonId | string | Rejection reason. One of: connector_declined, prospect_declined, other (default). Defaults to other when omitted. |
| customReason | string | Free-text reason (≤ 200 chars). |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/intros/{intro_id}/declined' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{ "reasonId": "prospect_declined", "customReason": "Wrong company fit" }'
{ "status": 200, "error": [] }
intro_id is missing or owned by another customer or archived.reasonId is not one of connector_declined / prospect_declined / other, or customReason exceeds 200 chars.Returns a paginated list of the companies (accounts) where you have saved targets, each with per-company counts: total saved targets, total connection paths, how many targets are reachable 1st- vs 2nd-degree, and score, the highest relationship rank among those paths. query selects which companies come back — by company name, or by a saved person's name or job title — and the counts on each one always describe the whole company. connectionDegree narrows to companies with a target at that degree, including none for the companies where you have saved people and hold no path at all. Results are sorted strongest-first. To list the individual targets at a company, call GET /targets?accountId=<id>.
| Parameter | Type | Description |
|---|---|---|
| query | string | Selects which companies are returned: matches a company name, or a saved target's name or job title. The counts on each returned company always cover the whole company, whichever way it matched. Min 2 characters. |
| connectionDegree | string | Narrow to companies that have a target at this degree. Available values: 1st (directly connected), 2nd (reachable through a connector), none (saved targets, no path held). 1st and 2nd overlap — a company can have targets at both. Independent of query: the person who matched the search need not be the one at that degree. |
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page. Default: 20 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/accounts?query=Stripe&pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"accounts": [
{
"id": "7958f1c3-f3e6-4b32-8c32-227d01f032d7",
"name": "Stripe",
"linkedinUrl": "https://www.linkedin.com/company/stripe/",
"targetsCount": 6,
"pathsCount": 9,
"firstDegreeCount": 2,
"secondDegreeCount": 3,
"score": 87,
"logoUrl": "https://files-intros.draftboard.com/p/l/acme/logo/acme.png"
}
],
"count": 12,
"nextPage": 0
}
| Field | Type | Description |
|---|---|---|
| id | string | Account (company) id. Use with GET /targets?accountId=<id> to list its targets. |
| name | string | Company name |
| linkedinUrl | string | Company LinkedIn URL (optional) |
| targetsCount | number | Number of saved targets at this company |
| pathsCount | number | Total connection paths across those targets |
| firstDegreeCount | number | Targets you or a teammate are directly connected to (1st degree) |
| secondDegreeCount | number | Targets reachable only via a connector (2nd degree) |
| score | number | Highest relationship rank (0–100) across the connection paths at this company — the value the list is sorted by. 0 when none of the paths carries a rank; it is not a statement that there is no path, which pathsCount tells you. |
| logoUrl | string | Absolute URL of the company logo, e.g. https://files-intros.draftboard.com/p/.... Omitted entirely when there is no stored logo. |
Start a search for people with the given titles at the given companies. This
initiates the search and returns a campaignId; people are discovered
asynchronously, and there is no terminal "complete" signal (fire-and-forget).
People found are normally added as targets automatically, up to a limit per
company, and their paths are charged like any target's. Only the people beyond that limit wait
in the pool for review — read them with GET /pool?campaignId={campaignId}, then
confirm or reject them. Every call starts a new search, so do not repeat a call
to retry it. Writing requires a funded credit balance — see Error Handling.
| Parameter | Type | Description |
|---|---|---|
| companies | array[string] | Companies to search — at least one. Each a company website/domain (acme.com) or a LinkedIn company URL (linkedin.com/company/acme). A value that is neither is dropped. |
| titles | array[string] | Job titles / roles to find at those companies (the search persona) — at least one. |
| name | string | Optional label for the search (shown app-side). |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/search/accounts' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{"companies":["acme.com","https://www.linkedin.com/company/acme"],"titles":["Head of Sales"]}'
{
"status": 200,
"errors": [],
"campaignId": "b1e7…", // people held for review: GET /pool?campaignId=b1e7…
"imported": 2, // companies accepted into the search
"notImportedAccounts": [] // passthrough from campaign creation; typically empty
}
Scan the networks of the given supporters (people you know, given as LinkedIn
/in/ URLs) for people matching the given titles, optionally narrowed to
specific companies. This initiates the search and returns a
campaignId; people are discovered asynchronously. Everyone the
search finds waits in the pool for your review — read them with
GET /pool?campaignId={campaignId} and confirm the ones you want with
POST /pool/confirm. Nothing becomes a target and
nothing is charged until you confirm. The search does not change the rating of the supporters
you send or of anyone it finds. There is no terminal
"complete" signal (fire-and-forget), and no lifecycle emails are sent for API-sourced
searches. Writing requires a funded credit balance — see Error Handling.
| Parameter | Type | Description |
|---|---|---|
| supporters | array[string] | Supporters to scan — at least one. Each a LinkedIn /in/ profile URL (e.g. https://www.linkedin.com/in/alice). Duplicates are deduped. |
| titles | array[string] | Job titles / roles to find in the supporters' networks (the search persona) — at least one. |
| companies | array[string] | Optional. Narrow the scan to specific companies (domain or LinkedIn company URL). Omit to scan the whole network. |
| name | string | Optional label for the search (shown app-side). |
| icp | object | Optional ideal customer. With enabled: true, every person the search finds is scored against it, and the fit appears as icpFits on GET/pool. Cannot be combined with companies — sending both returns 400. |
| icp.enabled | boolean | Required when icp is sent. false is the same as sending no icp. true needs at least one of the four fields below, or the request is a 400. |
| icp.industry | string | Optional. Industry of the ideal customer company, e.g. B2B SaaS. Up to 500 characters; surrounding spaces are trimmed and a blank value counts as not given. |
| icp.companySize | string | Optional. Company headcount, e.g. 50-1000, 200+ or SMB. Up to 500 characters. |
| icp.location | string | Optional. Company location, e.g. US, UK. Up to 500 characters. |
| icp.description | string | Optional. Anything else about the ideal customer. Up to 500 characters. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/search/supporters' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{"supporters":["https://www.linkedin.com/in/alice"],"titles":["Head of Sales"],"icp":{"enabled":true,"industry":"B2B SaaS","companySize":"50-1000"}}'
{
"status": 200,
"errors": [],
"campaignId": "b1e7…", // poll GET /pool?campaignId=b1e7…
"importedSupporters": 1, // supporter URLs accepted into the search
"notImportedSupporters": 0
}
Note: results are asynchronous — poll GET/pool with campaignId={campaignId} for the people found — it fills in over time, so retry with backoff — then confirm the ones you want with POST/pool/confirm.
This is your Queue. What the API calls the pool is shown as the Queue in the Draftboard web app — the same list of suggested prospects awaiting your review. The API path stays /pool.
Potential prospects in the pool — your Queue (status new) — awaiting confirm/reject, surfaced by auto-prospecting and searches. Everyone a supporter search finds appears here, and so does everyone an account search finds beyond its per-company limit. To follow a search: call GET /pool?campaignId={campaignId} with the campaignId from the search response, repeating with backoff until people appear, then send the ids you want to POST /pool/confirm. Use POST /pool/reject to discard the rest.
| Parameter | Type | Description |
|---|---|---|
| query | string | Substring match on LinkedIn URL |
| tagIds | string[] | Filter by tag IDs |
| tagMatch | string | How several tags combine: any returns prospects carrying at least one of them, all only those carrying every one. Default all. |
| campaignId | string (uuid) | Filter to a specific campaign |
| accountId | string (uuid) | Filter to a specific account (company) |
| minIcpFit | integer | Optional, 0–100. Only people the search given by campaignId scored at or above this fit; people it could not assess are left out. Requires campaignId — without it the request is a 400. Omitted, everyone is returned. |
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page. Default: 20, max 100 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/pool?pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"prospects": [
{
"id": "...",
"status": "new",
"name": "Jane Doe",
"linkedinUrl": "https://www.linkedin.com/in/jane",
"headline": "VP Sales",
"source": "SOURCE_ACCOUNT_SEARCH",
"tags": [{ "id": "...", "title": "Hot", "type": "manual" }],
"createdAt": "2026-06-01T00:00:00.000Z",
"accountName": "Stripe",
"avatarUrl": "https://files-intros.draftboard.com/p/u/.../avatar/abc123.png",
"icpFits": [
{ "campaignId": "b1e7…", "percent": 72, "reason": "industry ✓, location ✓" }
]
}
],
"count": 1,
"nextPage": 0
}
icpFits)Each prospect carries icpFits: one entry per supporter search that was sent an enabled icp and found this person. An entry is campaignId (the search), percent (0–100, how well the person's company matches that search's ideal customer) and reason (why). When percent is absent, the search could not assess this person — that is not a score of 0. An empty list means no such search found this person. The fit is per search: with campaignId, icpFits carries that search's entry only. Every prospect is returned whatever its fit, unless you ask for minIcpFit.
minIcpFit outside 0–100, or sent without campaignId.Confirm pool prospects (from your Queue, shown as Queue in the app) into saved targets. Every id you send that is still waiting in the pool becomes a target; from then on its paths are charged like any target's. confirmedCount is how many were confirmed by this call; remainingCapacity is for information only and does not limit what is confirmed. Idempotent — re-confirming an already-confirmed prospect is a no-op. Writing requires a funded credit balance.
| Field | Type | Description |
|---|---|---|
| ids required | string[] | Pool prospect UUIDs to confirm |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/pool/confirm' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{ "ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] }'
{
"status": 200,
"errors": [],
"confirmedCount": 2,
"remainingCapacity": 8
}
INSUFFICIENT_CREDITS in error[0]: the credit balance is exhausted. Top up to continue writing.Soft-deletes pending (status new) pool prospects from your Queue (shown as Queue in the app). Idempotent — rejecting an already-rejected prospect is a no-op. Confirmed prospects are unaffected (capacity-drift guard: a confirmed prospect is never silently un-counted). Writing requires a funded credit balance.
| Field | Type | Description |
|---|---|---|
| ids required | string[] | Pool prospect UUIDs to reject |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/pool/reject' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{ "ids": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"] }'
{
"status": 200,
"error": []
}
INSUFFICIENT_CREDITS in error[0]: the credit balance is exhausted. Top up to continue writing.Returns a paginated list of the customer's supporters — by default the full non-excluded connection network. Pass filters[preferred]=true to narrow the list to preferred supporters (connectors explicitly marked as preferred, prioritised by the matching engine). Pass filters[ownerIds][] to list only the LinkedIn connections of yourself or chosen teammates. Supports search by name.
Reading the whole list. One page holds at most 100 people, and most networks are larger. To read everything, request page 1, 2, 3 … until nextPage is 0; the order is stable, so each person comes back once and the number you read matches count. If people are added or removed while you page (for example while a network scan is running), a person can be skipped or repeated — read again once it has finished.
Note: This endpoint returns a view, not a persistent collection. Connectors with excluded = true are hidden. Use POST /api/v1/integration/connectors/{connector_id}/exclude to hide a connector and DELETE /api/v1/integration/connectors/{connector_id}/exclude to bring it back.
Hidden connectors — rating 1, "don't ask". Rating a connector 1 (equivalently tier 5) hides it: it is dropped from this listing by default, exactly as an excluded connector is. It is not gone — pass filters[rating][]=1 (or the equivalent filters[tiers][]=5) and the hidden connectors come back, each serialized with rating: 1 and tier: 5. The rating filter doubles as the "Hidden" scope; there is no separate flag, and default result sets are unchanged. One team exception: if a teammate keeps that connector visible, it stays in your default listing and carries your rating — rating: 1 — because you always read back your own rating, never a teammate's. Solo accounts never see this.
The consequence to design around: on a default listing rating is 2..5 or absent. If you rate someone 1 and re-list without the filter, they disappear rather than coming back with a 1. That is the rating working, not a write that failed.
| Parameter | Type | Description |
|---|---|---|
| filters[query] | string | Search by supporter name (matches first and last name) |
| filters[preferred] | boolean | When set, narrows the list. true = preferred supporters only, false = non-preferred only. Omit to return the full non-excluded network. |
| filters[tiers][] | number[] | Multi-select by your personal rating expressed as a cadence tier, 1..5, where lower is better. Repeat for several, e.g. filters[tiers][]=1&filters[tiers][]=2. Cumulative. Prefer filters[rating][] below, which counts the same way the app's stars do. Including 5 also opts into the hidden connectors (see the note above). Set a connector's tier via PUT /connectors/{connector_id}/tier. |
| filters[rating][] | number[] | The same filter the way the app shows it — 1..5, higher is better. Repeat for several, e.g. filters[rating][]=5&filters[rating][]=4. It mirrors filters[tiers][] exactly: filters[rating][]=5 returns the same set as filters[tiers][]=1. Passing both is a union, not an intersection. Including 1 also opts into the hidden connectors (see the note above). |
| filters[labels][] | string[] | Only connectors carrying these labels, e.g. filters[labels][]=investor. Repeat for several; how they combine is filters[labelsMatch]. Tokens: Org (shared with your team): investor, customer, advisor, partner, do_not_contact. Personal (yours only): close_friend, family, mentor, supporter (supporter = you rated them 2..5). Automatic: current_colleague, former_colleague, university_classmate. An unknown token is a 400. Hidden labels never match. |
| filters[labelsMatch] | string | How several filters[labels][] combine: all (default) = the connector carries every one, any = at least one. |
| filters[excludeLabels][] | string[] | Connectors carrying any of these labels are left out, e.g. filters[excludeLabels][]=do_not_contact. |
| filters[ownerIds][] | string[] | Only the people these team members are connected to on LinkedIn (1st degree). Member ids come from GET/me: your own is customerProfileId, your teammates' are teamMembers[].id. Repeat for several, e.g. filters[ownerIds][]=ID1&filters[ownerIds][]=ID2, to get anyone any of them is connected to. At most 50. An id that is not a UUID, or not an active member of your team, is a 400. Combines with every other filter. Omit it for the whole team's list, which also includes people marked as supporters without a LinkedIn connection. |
| paging[pageNumber] | number | Page number (1-based). Default: 1 |
| paging[resultPerPage] | number | Results per page. Default: 20, maximum 100 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/supporters?filters[query]=david&paging[pageNumber]=1&paging[resultPerPage]=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/supporters?filters[preferred]=true&paging[pageNumber]=1&paging[resultPerPage]=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/supporters?filters[rating][]=5&filters[rating][]=4&paging[pageNumber]=1&paging[resultPerPage]=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/supporters?filters[labels][]=investor&filters[labels][]=advisor&filters[labelsMatch]=any&filters[excludeLabels][]=do_not_contact' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
customerProfileId from GET /me), 100 per pagecurl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/supporters?filters[ownerIds][]=YOUR_CUSTOMER_PROFILE_ID&paging[pageNumber]=1&paging[resultPerPage]=100' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/supporters?filters[rating][]=1&paging[pageNumber]=1&paging[resultPerPage]=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"supporters": [
{
"id": "daf886d4-d2ca-4ca2-8cd7-5546c50ada5f",
"firstName": "David",
"lastName": "Chen",
"linkedinUrl": "https://www.linkedin.com/in/davidchen/",
"position": {
"title": "Director of Engineering",
"companyName": "Draftboard",
"companyLinkedinUrl": "https://www.linkedin.com/company/draftboard/"
},
"headline": "Engineering Leader | Building great teams",
"score": 85,
"createdAt": "2026-04-15T10:30:00.000Z",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png",
"tier": 1,
"rating": 5,
"labels": ["investor", "former_colleague", "supporter"],
"owners": [
{
"id": "3f9c2a71-5b8e-4d0a-9c6f-7e1b2d4a8c90",
"firstName": "Maria",
"lastName": "Lopez",
"linkedinUrl": "https://www.linkedin.com/in/marialopez/",
"score": 85
}
]
}
],
"count": 1,
"nextPage": 0
}
| Field | Type | Description |
|---|---|---|
| id | string | Connector ID (use this with /connectors/{connector_id}/* endpoints) |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
| position | IntegrationPositionDto | Current position |
| headline | string | LinkedIn headline (optional) |
| score | number | Relationship rank (0-100) between authenticated customer and supporter |
| createdAt | string | Creation timestamp (ISO 8601) |
| avatarUrl | string | Absolute URL of the avatar image (LinkedIn-sourced via filestore), e.g. https://files-intros.draftboard.com/p/.... Omitted entirely when there is no stored avatar. |
| tier | number | Your personal rating for this connector on the cadence scale: 1 = "ask anytime" (the best), 2, 3, 4, 5 = "do not ask". Lower is better — see rating below for the same setting as the app shows it; the two are always consistent, and always present or absent together. Absent when unreviewed. A 5 is only ever returned when filters[rating][]=1 / filters[tiers][]=5 explicitly asked for hidden connectors. Set via PUT /connectors/{connector_id}/tier; personal to the API-key owner. |
| rating | number | The star rating, as the app shows it: 5 = ★★★★★ ("ask anytime"), 4 = ★★★★, 3 = ★★★, 2 = ★★, 1 = ★ ("don't ask"). Higher is better. tier and rating are two spellings of one setting, so rating is absent exactly when tier is absent (unreviewed). Never 0: "0 stars" is not a product state, so clearing a rating is PUT { "tier": 0 }, after which both fields are absent. On a default listing the value is 2..5; 1 appears only when filters[rating][]=1 asked for the hidden connectors. Personal to the API-key owner. |
| labels | string[] | Labels on this person as the API-key owner sees them, as lowercase tokens. Each label is listed once, even when it was set both on the person and on their company. Org labels — investor, customer, advisor, partner, do_not_contact — are shared across your team, including the ones that come from the company the person currently works at (set with PUT/companies/{company_id}/labels). Personal labels — close_friend, family, mentor, and supporter (you rated them 2..5) — are your own. Automatic labels — current_colleague, former_colleague, university_classmate — appear when anyone on your team has that relationship with the person. Hidden labels are left out: an automatic label you hid in the app, or a company label your team excepted for this person. An empty array means no labels. The same field, with the same meaning, is on every response that returns this shape: GET/supporters, GET /connectors, GET /connectors/{connector_id}/intros and GET /paths/strongest. Set labels on a person with PUT/connectors/{connector_id}/labels. |
| owners | IntegrationMemberDto[] | Members of your team who know this person, each with their relationship strength (score, 0-100) to them, strongest first — the same owners as on GET/targets/{target_id}/connections. Each id is a member id you can pass back as filters[ownerIds][]. Always the whole team, whatever filters[ownerIds][] asked for. It is broader than that filter, which counts LinkedIn connections only, so a teammate can be listed here for a person who is not in that teammate's filtered list. Filled on this endpoint, where an empty array means no teammate has a relationship with them; where this shape is the connector of another response, read that response's own owners instead. |
How your connections break down by label and by rating, so you can see what a filter on GET/supporters would return before asking for the list. For every label — 0 included — how many connections carry it; how many you rated at each rating 5..1 and how many you have not rated; and the total. Read-only.
Same filters as GET /supporters. Send any of its filters[…] — filters[query], filters[preferred], filters[ownerIds][] (a teammate's network), filters[rating][] / filters[tiers][], filters[labels][] / filters[labelsMatch] / filters[excludeLabels][] — and the counts describe that list.
Each section ignores its own filter. Label counts ignore the label filters (each is the list with only that label); rating counts and unrated ignore filters[rating][] and filters[tiers][] (rating 1 includes the connections it hides, as filters[rating][]=1 does). total applies every filter and equals the count GET /supporters returns.
Labels are counted as you see them: org labels team-wide, personal labels your own, hidden labels never.
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/labels/counts?filters[ownerIds][]=c2e4a890-3f1b-4a2c-9d55-1e0f7a3b9c44&filters[excludeLabels][]=do_not_contact' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"labels": [
{ "label": "investor", "scope": "org", "count": 12 },
{ "label": "customer", "scope": "org", "count": 0 },
{ "label": "family", "scope": "personal", "count": 3 },
{ "label": "supporter", "scope": "personal", "count": 41 },
{ "label": "former_colleague", "scope": "personal", "count": 87 }
],
"ratings": [
{ "rating": 5, "count": 9 },
{ "rating": 4, "count": 14 },
{ "rating": 3, "count": 11 },
{ "rating": 2, "count": 7 },
{ "rating": 1, "count": 2 }
],
"unrated": 268,
"total": 310
}
The example is shortened: labels always lists every label, in the order org, personal, supporter, automatic.
| Field | Type | Description |
|---|---|---|
| labels | LabelCountDto[] | Every label: label (token, as in labels on a supporter), scope (org or personal) and count. A 0 is listed, never left out. |
| ratings | RatingCountDto[] | One entry per rating, 5 down to 1: rating (higher is better) and count. |
| unrated | number | Connections you have not rated. |
| total | number | Connections with every filter applied. |
filters[ownerIds][] that is not on your team).supporter are counted as yours, so nothing is counted rather than half of it — read this as unknown, never as zero. Connect it in the app and retry.Import supporters by providing their LinkedIn URLs. Each URL is resolved to a connector and added to the customer's network.
Imported people are not rated. The import leaves ratings as they are: someone new stays unrated, and a rating you already gave someone is kept. To make someone a supporter, rate them 2..5 with PUT/connectors/{connector_id}/tier.
| Field | Type | Description |
|---|---|---|
| linkedinUrls required | string[] | LinkedIn profile URLs to import. Must contain at least one entry and at most 100 — an empty array, or more than 100, is rejected with 400 Bad Request. Split a larger list across several calls. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/supporters/import' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{
"linkedinUrls": [
"https://www.linkedin.com/in/davidchen/",
"https://www.linkedin.com/in/emilyrodriguez/"
]
}'
{
"status": 200,
"error": []
}
Note: The response does not include per-URL results. After import, re-fetch GET /api/v1/integration/supporters to verify which supporters are now present in the list.
Marks a connector as preferred. Preferred connectors are boosted by the matching engine when building intro paths. No request body.
| Parameter | Type | Description |
|---|---|---|
| connector_id required | string (UUID) | UUID of the connector. Obtain this from Connection.connector_id in any list response (e.g. /targets/{target_id}/connections or /supporters). |
curl -X 'POST' \ 'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/prefer' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"error": []
}
Unmarks a connector as preferred (sets preferred = false). No request body.
curl -X 'DELETE' \ 'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/prefer' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"error": []
}
Note: Both endpoints return 404 Not Found if the supplied connector_id is not part of the authenticated customer's network.
Marks a connector as excluded. Excluded connectors are hidden from default-view queries (e.g. they will not appear in GET /supporters) and are deprioritised by the matching engine. No request body.
| Parameter | Type | Description |
|---|---|---|
| connector_id required | string (UUID) | UUID of the connector. Obtain this from Connection.connector_id in any list response. |
curl -X 'POST' \ 'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/exclude' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"error": []
}
Unmarks a connector as excluded (sets excluded = false) so it reappears in default-view queries. No request body.
curl -X 'DELETE' \ 'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/exclude' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"error": []
}
Note: Both endpoints return 404 Not Found if the supplied connector_id is not part of the authenticated customer's network. preferred and excluded are independent flags — a connector can be both preferred and excluded simultaneously.
Sets your personal rating for a connector — the "star rating" that prioritises who you reach out to. Send it either as rating (the app's star scale, higher is better) or as tier (the internal cadence scale, lower is better); they are the same value. The rating is personal to the API-key owner: it is returned as both tier and rating on GET/supporters and GET/connectors, and is filterable via filters[rating][] / filters[tiers][].
Two spellings of one setting. rating counts the way the app's stars do — higher is better. tier is the internal cadence scale — lower is better. Pick whichever direction suits your client; the two never disagree.
rating — 5 = ★★★★★ "ask anytime" · 4 = ★★★★ · 3 = ★★★ · 2 = ★★ · 1 = ★ "don't ask".tier — 1 is the best ("ask anytime") and 5 is "do not ask"; 0 clears the rating back to unreviewed.
Send exactly one of the two. A body carrying both rating and tier, or neither, is rejected with 400.
Clearing a rating stays { "tier": 0 }. rating has deliberately no zero — "0 stars" is not a state the product has — so there is no rating: 0 and no way to clear via rating. After clearing, both tier and rating are absent from read responses (back to unreviewed).
Rating a connector 1 hides it — that is the rating working, not a failed write. rating 1 / tier 5 is the "don't ask" rung: it excludes the connector from paths and drops it from the default GET/supporters and GET/connectors listings, so re-listing will not show it back with a 1. Read it back — and list everything you have hidden — with GET /supporters?filters[rating][]=1 (or the equivalent filters[tiers][]=5): the rating filter doubles as the "Hidden" scope, and there is no separate flag. To un-hide, rate the connector 2..5 or clear with { "tier": 0 }. One team exception: if a teammate keeps that connector visible, it stays in your default listing and carries your rating — rating: 1 — because you always read back your own rating, never a teammate's. Solo accounts never see this.
| Parameter | Type | Description |
|---|---|---|
| connector_id required | string (UUID) | UUID of the connector. Obtain this from Connection.connector_id in any list response. |
Exactly one of rating or tier is required. Sending both, sending neither, or sending a value outside its range returns 400.
| Field | Type | Description |
|---|---|---|
| rating | number | The rating on the app's star scale — an integer 1–5, higher is better (see the scale above). There is no 0; to clear a rating use tier: 0. Send this or tier, never both. |
| tier | number | The same rating on the internal cadence scale — an integer 0–5, lower is better, where 0 clears the rating back to unreviewed. Send this or rating, never both. (Previously this field was required; presence has moved to the exactly-one-of rule, so existing tier-only clients are unaffected.) |
curl -X 'PUT' \
'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/tier' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-d '{ "rating": 5 }'
curl -X 'PUT' \
'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/tier' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-d '{ "tier": 1 }'
curl -X 'PUT' \
'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/tier' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-d '{ "tier": 0 }'
{
"status": 200,
"error": []
}
Note: Returns 404 Not Found if the supplied connector_id is not part of the authenticated customer's network, and 400 if the body carries both rating and tier, neither of them, or a value outside its range (rating 1–5, tier 0–5). Returns 412 when your own LinkedIn is not connected to Draftboard: a rating is personal, so there is no one to save it for, and nothing was written. Read the current rating back via the rating and tier fields on GET/supporters and GET/connectors, or list all connectors at a given rating with GET /supporters?filters[rating][]=5 (equivalently filters[tiers][]=1).
Adds and removes labels on one person in your network — who they are to you. Labels are then returned on labels in GET/supporters and narrow that list (filters[labels][]) and the paths to a target (GET/targets/{target_id}/connections, connectorLabels). Labels only filter: they do not change what is scanned or which paths exist.
Who sees a label depends on the label, not on the call. Org labels — investor, customer, advisor, partner, do_not_contact — are facts about your organization's relationship with the person: your whole team sees them, and a teammate can remove one you added. Personal labels — close_friend, family, mentor — belong to the API-key owner and nobody else sees them.
Two kinds of label cannot be set here. supporter is your rating (2..5) — change it with PUT/connectors/{connector_id}/tier. The automatic labels — current_colleague, former_colleague, university_classmate — come from shared history. Sending either is a 400.
do_not_contact is descriptive. The person still appears in default lists; leave them out with filters[excludeLabels][]=do_not_contact or excludeConnectorLabels=do_not_contact.
| Parameter | Type | Description |
|---|---|---|
| connector_id required | string (UUID) | UUID of the connector — id on a supporter, connector_id on a connection. |
At least one of add or remove must carry a label, and the same label cannot be in both — either is a 400.
| Field | Type | Description |
|---|---|---|
| add | string[] | Labels to add. Already there = nothing changes. Tokens are case-insensitive. |
| remove | string[] | Labels to remove. Not there = nothing changes. |
curl -X 'PUT' \
'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/labels' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-d '{ "add": ["investor"] }'
curl -X 'PUT' \
'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/labels' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-d '{ "add": ["mentor"], "remove": ["close_friend"] }'
{
"status": 200,
"error": []
}
connector_id; an unknown token; supporter (set it with the rating, PUT/connectors/{connector_id}/tier); an automatic label (these cannot be set by hand); a label in both lists; or neither list carrying a label.PUT /connectors/{connector_id}/tier).INSUFFICIENT_CREDITS: writing requires a funded credit balance — see Error Handling.Labels a company for your whole team. Everyone in your team's network who currently works there carries the label — including people found later — and it shows on their labels and matches the label filters exactly like a label set on the person. Someone who leaves the company stops carrying it; a label you set on the person yourself stays.
Only org labels a company can carry: investor, customer, partner, do_not_contact. Anything else is a 400.
Take the id from GET/companies, which also tells you how many people the label reaches. One company can appear there as several rows, each with its own id: the label reaches only the people behind the row you label, so label every row that is the same company. An account id from GET/accounts is not a company id and is a 400.
A company nobody in your network works at yet can still be labelled; the label applies as soon as someone who works there is found.
Removing a label removes it from everyone who carried it through this company; the same label set directly on one of those people stays.
| Parameter | Type | Description |
|---|---|---|
| company_id required | string (UUID) | UUID of the company — an id from GET/companies. An account id is not a company id and is a 400. |
At least one of add or remove must carry a label, and the same label cannot be in both — either is a 400.
| Field | Type | Description |
|---|---|---|
| add | string[] | Labels to add. Already there = nothing changes. |
| remove | string[] | Labels to remove. Not there = nothing changes. |
curl -X 'PUT' \
'https://intros.draftboard.com/api/v1/integration/companies/0c2f5f1e-8a2b-4c55-9d1e-3b7a1d2e4f60/labels' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-d '{ "add": ["investor"] }'
{
"status": 200,
"error": []
}
company_id, an account id in place of a company id, a token a company cannot carry, a label in both lists, or neither list carrying a label.INSUFFICIENT_CREDITS: writing requires a funded credit balance — see Error Handling.Returns the companies where someone in your team's network currently works, found by name and / or by label, with the people count first. Each row's id is the company_id PUT/companies/{company_id}/labels takes, and connectorsCount is how many people a label on that company reaches — read it before you label.
At least one of query or labels is required — without either the request is a 400.
One company can appear as several rows — for example "Sequoia Capital" with 7 people and "Sequoia" with 2 — each with its own id and count. A label reaches only the people behind the row you label, so label every row that is the same company.
Not an account. These are the employers of the people you know, not the companies of your saved targets — an account id from GET/accounts is not a company id.
| Parameter | Type | Description |
|---|---|---|
| query | string | Company name, case-insensitive, matched at the start of a word: sequoia finds "Sequoia Capital". Up to 200 characters. |
| labels | array[string] | Only companies carrying these labels: investor, customer, partner, do_not_contact. Repeat it or comma-join it. Any other token is a 400. A labelled company nobody in your network works at now is included too, with connectorsCount 0 and no name. |
| labelsMatch | string | How several labels combine: all (default) = the company carries every one, any = at least one. |
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page. Default: 20, max: 100 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/companies?query=sequoia&pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"companies": [
{
"id": "0c2f5f1e-8a2b-4c55-9d1e-3b7a1d2e4f60",
"name": "Sequoia Capital",
"connectorsCount": 7,
"labels": ["investor"]
},
{
"id": "6a1d9e2b-3f4c-4b8a-9e7d-1c2b3a4d5e6f",
"name": "Sequoia",
"connectorsCount": 2
}
],
"count": 2,
"nextPage": 0
}
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | Company id — the company_id for PUT/companies/{company_id}/labels. |
| name | string | Company name as the people's positions spell it. Absent when nobody in your network works there now. |
| connectorsCount | number | People in your network who currently work here — who a label on this company reaches. Always present; 0 when nobody works there now. Rows come most people first. |
| labels | array[string] | Labels on this company, lowercase tokens. Absent when the company has none. |
query nor labels, a token a company cannot carry, an unknown labelsMatch, or a page size outside 1..100.Returns the connector-first view of the network: the queried connector and its owners (team members who can reach it) are returned once at the top level, with a paginated items array of intros — one per target the connector can introduce. Each item carries only the per-intro fields (id, score, scoreDetails, relationships, relationshipDetails, target); the connector data is not repeated per row.
Each item carries the same structured relationship fields as GET/targets/{target_id}/connections: relationships (current_colleague / former_colleague / university_classmate) and relationshipDetails (the employment / education / mutual-connection facts, ISO yyyy-MM-dd dates). The full field reference and the nested shapes are documented once, on that endpoint.
The same absence rule applies. Both fields are present when we hold that signal for the pair and omitted when we do not. An absent field means "we have no structured signal for this pair", not "no relationship", and is not a statement about the strength of the connection — scoreDetails is unchanged and carries the human-readable summary.
Empty means absent, not [] — read them as item.relationships ?? []. And the two fields are independent: a mutual-connections-only signal produces a relationshipDetails record with no relationships entry, so never index-align one against the other or against scoreDetails.
| Parameter | Type | Description |
|---|---|---|
| connector_id required | string (UUID) | UUID of the connector |
| Parameter | Type | Description |
|---|---|---|
| paging[pageNumber] | number | Page number (1-based). Default: 1 |
| paging[resultPerPage] | number | Results per page. Default: 20 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/connectors/daf886d4-d2ca-4ca2-8cd7-5546c50ada5f/intros?paging[pageNumber]=1&paging[resultPerPage]=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"count": 1,
"nextPage": 0,
"connector": {
"id": "daf886d4-d2ca-4ca2-8cd7-5546c50ada5f",
"firstName": "David",
"lastName": "Chen",
"linkedinUrl": "https://www.linkedin.com/in/davidchen/",
"position": {
"title": "Director of Engineering",
"companyName": "Draftboard"
},
"headline": "Director of Engineering at Draftboard",
"createdAt": "2026-02-06T10:45:20.359Z",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png",
"tier": 2,
"rating": 4,
"labels": ["advisor"]
},
"owners": [
{
"id": "c2e4a890-3f1b-4a2c-9d55-1e0f7a3b9c44",
"firstName": "Sarah",
"lastName": "Miller",
"linkedinUrl": "https://www.linkedin.com/in/sarahmiller/",
"score": 95
}
],
"items": [
{
"id": "1b9d8f04-2c7a-4e3d-9a11-7f0c2d8e44aa",
"score": 48,
"scoreDetails": [
"They overlapped for a long time (42 months) @ TechCorp, most recently in 2016",
"They attended Stanford University at the same time"
],
"relationships": [
"former_colleague",
"university_classmate"
],
"relationshipDetails": [
{
"employment": {
"company": "TechCorp",
"department": "Engineering",
"overlapStartDate": "2012-09-01",
"overlapEndDate": "2016-03-01",
"loose": false
},
"score": 0.42
},
{
"education": {
"school": "Stanford University",
"overlapStartDate": "2004-09-01",
"overlapEndDate": "2008-06-01"
},
"score": 0.19
}
],
"target": {
"id": "6f397389-dc11-4051-822a-5ffd25ec8417",
"firstName": "Michael",
"lastName": "Johnson",
"linkedinUrl": "https://www.linkedin.com/in/michaeljohnson/",
"position": {
"title": "Chief Executive Officer",
"companyName": "Tech Corp"
},
"headline": "CEO at Tech Corp | Building the future",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png"
}
}
]
}
| Field | Type | Description |
|---|---|---|
| status | number | HTTP status code |
| errors | string[] | Error messages (empty on success) |
| connector | SupporterDto | The connector being queried (same as connector_id). Returned on every successful response, including empty pages. Carries the same shape as IntegrationSupporterDto, including your personal tier and rating when you have rated this connector (both absent when unreviewed) and its labels, with the same meaning as on GET/supporters. |
| owners | IntegrationMemberDto[] | Team members in your organization who have a recorded relationship to this connector. Each score is that member's relationship rank (0–100) to the connector. |
| items | ConnectorIntroDto[] | One entry per intro for this connector — see the table below. Paginated. |
| count | number | Total number of intros for this connector |
| nextPage | number | Next page number (0 if no more pages) |
| Field | Type | Description |
|---|---|---|
| id | string | Intro id — the value the /intros/{intro_id}/* and shortlist routes take. |
| score | number | Relationship rank (0–100) between the connector and this target. |
| scoreDetails | string[] | Reasons for the score, as finished English sentences. Unchanged by the addition of the two structured fields below, and still the human-readable summary of the relationship. Absent when empty. |
| relationships | string[] | How the connector knows this target: current_colleague, former_colleague, university_classmate. No other value is ever emitted. Absent when empty — present when we hold the signal for the pair, omitted when we do not. See the note at the top of this section, and the full description. |
| relationshipDetails | IntegrationRelationshipDetailDto[] | The facts behind the relationship — one record per company, school, or mutual-connection signal, with exactly one of employment / education / mutualConnections set and ISO yyyy-MM-dd dates. Absent when empty. Shapes documented under Get Target Connections. |
| target | TargetSummaryDto | The target this intro reaches — see below. |
| Field | Type | Description |
|---|---|---|
| id | string | Target id |
| firstName | string | First name |
| lastName | string | Last name |
| linkedinUrl | string | LinkedIn profile URL (optional) |
| position | IntegrationPositionDto | Current position |
| headline | string | LinkedIn headline (optional) |
| avatarUrl | string | Absolute URL of the avatar image (LinkedIn-sourced via filestore), e.g. https://files-intros.draftboard.com/p/.... Omitted entirely when there is no stored avatar. |
Note: Returns 404 Not Found if the supplied connector_id is not part of the authenticated customer's network. For a valid connector with no intros (or a page past the end), returns 200 with connector and owners populated and an empty items array.
Returns a paginated list of your connectors filtered by the tags of the targets they can introduce, each with the matching targets. Use tagIds or tagNames to narrow results, and tagMatch to say how several tags combine. labels / excludeLabels narrow by who the connector is to you; together with tags, a connector must satisfy both.
Several tags: With tagMatch=all (the default) and tagIds=[VIP,Hot], only connectors who can introduce you to at least one VIP-tagged target and at least one Hot-tagged target are returned (the covered targets may be different for each tag). With tagMatch=any, every connector who can introduce you to at least one target carrying any of the tags is returned. The targets listed under each connector are always those carrying any of the requested tags. Without any tag filter, all connectors with at least one reachable target are returned. An unknown or foreign tag ID or name returns 400.
Ratings on this endpoint. Each connector carries your personal tier and rating — two spellings of one setting. rating is 1..5 with higher better and is never 0; tier is 1..5 with lower better. Both are absent when you have not rated the connector. Set them with PUT/connectors/{connector_id}/tier.
Hidden connectors are normally not listed here. A connector rated 1 ("don't ask", equivalently tier 5) is dropped from the default listings, and this endpoint has no rating filter with which to ask for it — so rating here is 2..5 or absent — unless a teammate keeps that connector visible, in which case it remains listed carrying your own rating: 1. To list what you have hidden, use GET/supporters with filters[rating][]=1 (or the equivalent filters[tiers][]=5), where the rating filter doubles as the "Hidden" scope.
| Parameter | Type | Description |
|---|---|---|
| tagIds | array[string] | Target tag IDs. How several combine is tagMatch. |
| tagNames | array[string] | Target tag names (resolved to IDs; case-insensitive; must belong to your account). Optional. |
| tagMatch | string | How several tags combine: with all (the default), a connector is returned only if it covers every tag — for each tag, at least one of its eligible targets carries it, and the targets may differ per tag. With any, a connector is returned if it can introduce you to at least one target carrying any of the tags. Default all. |
| labels | array[string] | Only connectors carrying these labels, e.g. labels=investor. Repeat it or comma-join it. Same tokens as labels on GET/supporters; an unknown token is a 400. |
| labelsMatch | string | How several labels combine: all (default) = the connector carries every one, any = at least one. |
| excludeLabels | array[string] | Connectors carrying any of these labels are left out, e.g. excludeLabels=do_not_contact. |
| pageNumber | number | Page number (1-based). Default: 1 |
| resultPerPage | number | Results per page. Default: 20, max: 100 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/connectors?tagNames=VIP&pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/connectors?pageNumber=1&resultPerPage=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"count": 1,
"nextPage": 0,
"connectors": [
{
"connector": {
"id": "daf886d4-d2ca-4ca2-8cd7-5546c50ada5f",
"firstName": "David",
"lastName": "Chen",
"linkedinUrl": "https://www.linkedin.com/in/davidchen/",
"position": {
"title": "Director of Engineering",
"companyName": "Draftboard",
"companyLinkedinUrl": "https://www.linkedin.com/company/draftboard/"
},
"headline": "Engineering Leader | Building great teams",
"score": 85,
"createdAt": "2026-04-15T10:30:00.000Z",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/abc123/avatar/abc123.png",
"tier": 1,
"rating": 5,
"labels": ["investor", "supporter"]
},
"targets": [
{
"id": "6f397389-dc11-4051-822a-5ffd25ec8417",
"firstName": "Michael",
"lastName": "Johnson",
"linkedinUrl": "https://www.linkedin.com/in/michaeljohnson/",
"position": {
"title": "Chief Executive Officer",
"companyName": "Tech Corp"
},
"headline": "CEO at Tech Corp | Building the future",
"avatarUrl": "https://files-intros.draftboard.com/p/profiles/def456/avatar/def456.png"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"firstName": "Sarah",
"lastName": "Williams",
"linkedinUrl": "https://www.linkedin.com/in/sarahwilliams/",
"position": {
"title": "VP of Sales",
"companyName": "Tech Corp"
},
"headline": "VP Sales | Revenue Growth Expert",
"avatarUrl": null
}
]
}
]
}
| Field | Type | Description |
|---|---|---|
| status | number | HTTP status code |
| errors | string[] | Error messages (empty on success) |
| count | number | Total number of connectors matching the filter |
| nextPage | number | Next page number (0 if no more pages) |
| connectors | ConnectorWithTargetsDto[] | Paginated list of connectors with their matching targets |
| Field | Type | Description |
|---|---|---|
| connector | IntegrationSupporterDto | The connector profile: id, firstName, lastName, linkedinUrl, position, headline, score, createdAt, avatarUrl, tier, rating, labels. tier and rating are your personal rating for the connector — the same pair documented on GET/supporters — and both are absent when the connector is unreviewed. labels has the same meaning as there. |
| targets | TargetSummaryDto[] | The targets this connector can introduce you to that match the requested tag filter. Each target has: id, firstName, lastName, linkedinUrl, position, headline, avatarUrl |
The shortlist is a team-shared list of pinned intros. Each entry pins one intro (path) — a possible warm introduction between a connector and a target — so the team can rally around the best opportunities, assign an owner, and track them to completion. Entries are scoped to the organization (the org owner), so every teammate sees the same shortlist.
Returns a paginated list of the team's shortlist entries. Filter by assignee, account, connector, intro state, or staleness.
Filters use bracket notation (filters[…]); paging uses paging[…].
| Parameter | Type | Description |
|---|---|---|
| filters[assigneeCustomerProfileId] | string (uuid) | Only entries currently assigned to this team member (customer profile id) |
| filters[accountId] | string (uuid) | Only entries whose intro targets this account (company) id — the id from GET /accounts, not the LinkedIn company id |
| filters[connectorId] | string (uuid) | Only entries whose intro goes through this connector |
| filters[introState] | string | Intro lifecycle state: one of open, sent, made, declined |
| filters[stale] | boolean | true = only stale entries (untouched for at least 14 days), false = only fresh ones. Omit to return both. |
| paging[pageNumber] | number | Page number (1-based). Default: 1 |
| paging[resultPerPage] | number | Results per page. Default: 20, max 100 |
curl -X 'GET' \ 'https://intros.draftboard.com/api/v1/integration/shortlist?filters[introState]=open&paging[pageNumber]=1&paging[resultPerPage]=20' \ -H 'accept: application/json' \ -H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{
"status": 200,
"errors": [],
"entries": [
{
"id": "9b1c0e2a-7c4d-4a9f-9d2b-1f0a3c5e7b8d",
"introId": "6f397389-dc11-4051-822a-5ffd25ec8417",
"ownerCustomerProfileId": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
"currentAssigneeProfileId": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80910",
"note": "Warm intro — knows the VP personally",
"createdAt": "2026-06-01T09:24:58.468Z",
"updatedAt": "2026-06-10T12:40:12.456Z",
"stale": false,
"pastAssigneeProfileIds": []
}
],
"count": 1,
"nextPage": 0
}
Note: pastAssigneeProfileIds is populated only in single-entry mutation responses (add / reassign) and the in-app groups feed; it is empty in this flat list.
| Field | Type | Description |
|---|---|---|
| id | string | Shortlist entry id |
| introId | string | Intro (path) id this entry pins |
| ownerCustomerProfileId | string | Org owner profile id that scopes this team-shared entry |
| currentAssigneeProfileId | string | Team member currently assigned; absent on solo accounts |
| note | string | Personal memo (self-assigned) or handoff message (assigned to a teammate). Optional |
| createdAt | string | Creation timestamp (ISO 8601) |
| updatedAt | string | Last update timestamp (ISO 8601) |
| stale | boolean | true when the open entry has been untouched for at least 14 days |
| pastAssigneeProfileIds | string[] | Anti-bounce: profiles ever assigned to this entry. Populated only in single-entry mutation responses and the groups feed; empty in this flat list |
Pins an intro (path) to the team-shared shortlist. Idempotent — re-adding an already-shortlisted intro returns 200 with the existing entry (a differing note is applied on re-add). On team accounts, assigneeCustomerProfileId is required and must be an active member of the organization. On solo accounts, omit it or pass your own customer profile id. Writes require a funded credit wallet (402 INSUFFICIENT_CREDITS when the wallet is exhausted).
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id from GET /targets/{id}/connections or GET /connectors/{id}/intros. |
| Field | Type | Description |
|---|---|---|
| assigneeCustomerProfileId | string (uuid) | Team member to assign the entry to. Required on team accounts (must be an active org member, else 400). On solo accounts: omit it, or pass your own profile id — any other id is rejected with 400. |
| note | string | Personal memo (self-assigned) or handoff message (assigned to a teammate). Max 2000 chars. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/shortlist/intros/{intro_id}' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{ "assigneeCustomerProfileId": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80910", "note": "Warm intro — knows the VP personally" }'
{
"status": 200,
"error": [],
"entry": {
"id": "9b1c0e2a-7c4d-4a9f-9d2b-1f0a3c5e7b8d",
"introId": "6f397389-dc11-4051-822a-5ffd25ec8417",
"ownerCustomerProfileId": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
"currentAssigneeProfileId": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80910",
"note": "Warm intro — knows the VP personally",
"createdAt": "2026-06-01T09:24:58.468Z",
"updatedAt": "2026-06-01T09:24:58.468Z",
"stale": false,
"pastAssigneeProfileIds": ["2b3c4d5e-6f70-8192-a3b4-c5d6e7f80910"]
}
}
assigneeCustomerProfileId missing or not an active org member. On solo accounts: a foreign assigneeCustomerProfileId was passed.INSUFFICIENT_CREDITS: the org credit wallet is exhausted; top up to continue writing.intro_id does not exist or belongs to another customer.Removes the open shortlist entry for this intro (unpins it). Writes require a funded credit wallet (402 INSUFFICIENT_CREDITS when the wallet is exhausted).
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id of the shortlist entry to remove. |
curl -X 'DELETE' \
'https://intros.draftboard.com/api/v1/integration/shortlist/intros/{intro_id}' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{ "status": 200, "error": [] }
INSUFFICIENT_CREDITS: the org credit wallet is exhausted; top up to continue writing.Hands the open entry for this intro to another team member, optionally with a handoff note. An anti-bounce rule prevents reassigning an entry to a member it was previously assigned to (the org owner bypasses this rule). Writes require a funded credit wallet (402 INSUFFICIENT_CREDITS when the wallet is exhausted).
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id of the shortlist entry to reassign. |
| Field | Type | Description |
|---|---|---|
| assigneeCustomerProfileId required | string (uuid) | Team member to hand the entry to. Must be a member of the organization. |
| note | string | Optional handoff message for the new assignee. Max 2000 chars. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/shortlist/intros/{intro_id}/reassign' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE' \
-H 'Content-Type: application/json' \
-d '{ "assigneeCustomerProfileId": "3c4d5e6f-7081-92a3-b4c5-d6e7f8091021", "note": "You have the closest relationship here" }'
{
"status": 200,
"error": [],
"entry": {
"id": "9b1c0e2a-7c4d-4a9f-9d2b-1f0a3c5e7b8d",
"introId": "6f397389-dc11-4051-822a-5ffd25ec8417",
"ownerCustomerProfileId": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
"currentAssigneeProfileId": "3c4d5e6f-7081-92a3-b4c5-d6e7f8091021",
"note": "You have the closest relationship here",
"createdAt": "2026-06-01T09:24:58.468Z",
"updatedAt": "2026-06-11T08:15:00.000Z",
"stale": false,
"pastAssigneeProfileIds": ["2b3c4d5e-6f70-8192-a3b4-c5d6e7f80910", "3c4d5e6f-7081-92a3-b4c5-d6e7f8091021"]
}
}
INSUFFICIENT_CREDITS: the org credit wallet is exhausted; top up to continue writing.Permanently dismisses this (connector, target) pair — it is removed from the shortlist and is not suggested for shortlisting again. Only the current assignee or the org owner may reject. Writes require a funded credit wallet (402 INSUFFICIENT_CREDITS when the wallet is exhausted).
| Parameter | Type | Description |
|---|---|---|
| intro_id required | UUID | Intro (path) id of the shortlist entry to reject. |
curl -X 'POST' \
'https://intros.draftboard.com/api/v1/integration/shortlist/intros/{intro_id}/reject' \
-H 'accept: application/json' \
-H 'Authorization: Bearer YOUR_API_TOKEN_HERE'
{ "status": 200, "error": [] }
INSUFFICIENT_CREDITS: the org credit wallet is exhausted; top up to continue writing.Quick reference for all Data Transfer Objects used in the Integration API.
| DTO Name | Used In | Description |
|---|---|---|
| GetMeResponseDto | GET /integration/me | Customer information response |
| GetTagsResponseDto | GET /integration/tags | Paginated list of tags |
| GetTargetsResponseDto | GET /integration/targets | Paginated list of targets |
| ImportTargetsResponseDto | POST /integration/targets/import | Import results with success/failure counts |
| GetConnectionsResponseDto | GET /integration/targets/{id}/connections | Paginated list of connections |
| BaseResponseDto / ActionResponseDto | DELETE /integration/targets/{id}, POST /integration/targets/{id}/tags, POST /integration/targets/tags, POST /integration/supporters/import, POST/DELETE /integration/connectors/{id}/prefer, POST/DELETE /integration/connectors/{id}/exclude, POST /integration/intros/{id}/requested · /completed · /declined, DELETE /integration/shortlist/intros/{id}, POST /integration/shortlist/intros/{id}/reject | Standard envelope with status and error (singular, repeated string — empty on success) |
| GetPoolProspectsResponseDto | GET /integration/pool | Paginated list of pool prospects (the app's Queue) awaiting confirm/reject |
| ConfirmPoolProspectsResponseDto | POST /integration/pool/confirm | Confirm result with confirmedCount and remainingCapacity |
| GetSupportersResponseDto | GET /integration/supporters | Paginated list of supporters |
| GetConnectorIntrosResponseDto | GET /integration/connectors/{id}/intros | Paginated list of intros for a connector (connector-first view) |
| GetNetworkCompaniesResponseDto | GET /integration/companies | Paginated list of companies in your network (companies) |
| GetShortlistResponseDto | GET /integration/shortlist | Paginated list of shortlist entries (entries) |
| ShortlistEntryResponseDto | POST /integration/shortlist/intros/{id}, POST /integration/shortlist/intros/{id}/reassign | Single shortlist entry after add/reassign |
| DTO Name | Description |
|---|---|
| IntegrationCustomerDto | Customer/company information with user, subscription, and token details |
| IntegrationUserDto | User profile (API key owner) |
| IntegrationSubscriptionDto | Subscription details: planCode, planName, seatLimit, seatUsed, copilotEnabled |
| IntegrationTagDto | Tag with id, title, and type |
| IntegrationTargetDto | Target profile with status, position, score, connection degree, and tags |
| IntegrationAccountDto | Account (company) summary with target/path counts and 1st/2nd-degree counts |
| IntegrationNetworkCompanyDto | A company people in your network work at: the id to label, its name, the people count and its labels |
| IntegrationConnectionDto | Connection with score, score details, owners, the connector's labels, and — when the rank carries them — the structured relationships tokens and relationshipDetails records |
| IntegrationRelationshipDetailDto | One structured relationship signal between connector and target. Exactly one of employment, education, mutualConnections is set, plus an optional score (this record's contribution, for ranking same-kind records against each other). Absent when empty — the key is omitted, never []. |
| IntegrationRelationshipEmploymentDto | Same-company signal: company, department, location, overlapStartDate, overlapEndDate, loose, unit. Dates are ISO yyyy-MM-dd strings. Overlap present ⇒ they overlapped; overlapEndDate absent ⇒ still colleagues; loose: true ⇒ low-confidence affiliation with the window deliberately omitted. |
| IntegrationRelationshipEducationDto | Same-school signal: school, overlapStartDate, overlapEndDate (ISO yyyy-MM-dd). Both dates absent ⇒ attended at different times. |
| IntegrationRelationshipMutualConnectionsDto | Shared-contacts signal: count. Has no counterpart token in relationships. |
| IntegrationPositionDto | Job position (title, company name, company URL) |
| IntegrationMemberDto | Team member/connector information with relationship score |
| IntegrationSupporterDto | Supporter — a connector in the customer's network. Returned by GET /supporters and GET /connectors; use filters[preferred]=true to narrow to preferred supporters only. Carries your personal rating as both tier (1..5, lower is better) and rating (1..5, higher is better) — two spellings of one setting; both absent when unreviewed. It also carries labels — who the person is to you — on every endpoint that returns it (see Label a Connector) |
| TargetSummaryDto | Compact target description embedded in connector-first intro responses |
| ConnectorIntroDto | Single intro in the connector-first view: intro id, score, scoreDetails, relationships, relationshipDetails, and target |
| ShortlistEntryDto | A pinned intro on the team shortlist: introId, ownerCustomerProfileId, currentAssigneeProfileId, note, stale, pastAssigneeProfileIds |
| DTO Name | Used In | Description |
|---|---|---|
| ImportTargetsRequestDto | POST /integration/targets/import | LinkedIn URLs and optional tags to import |
| ImportSupportersRequestDto | POST /integration/supporters/import | LinkedIn URLs of supporters to import (min 1 entry) |
| SetConnectorTierRequestDto | PUT /integration/connectors/{id}/tier | Exactly one of rating (1–5, higher is better) or tier (0–5, lower is better, 0 clears). Both or neither returns 400 |
| AddToShortlistBodyDto | POST /integration/shortlist/intros/{id} | Optional assigneeCustomerProfileId and note (≤ 2000 chars) |
| ReassignShortlistBodyDto | POST /integration/shortlist/intros/{id}/reassign | Required assigneeCustomerProfileId and optional note (≤ 2000 chars) |
All list endpoints support pagination using the following parameters:
| Parameter | Default | Description |
|---|---|---|
| pageNumber | 1 | Page number (1-based indexing) |
| resultPerPage | 20 | Number of results per page |
The response includes:
count - Total number of itemsnextPage - Next page number (0 if no more pages)The Integration API enforces per-customer rate limits using two independent buckets that share a 60-second window:
| Bucket | Limit (requests / 60s) | Applies to |
|---|---|---|
read |
50 | All GET endpoints (e.g. /me, /targets,
/supporters, /connectors/{id}/intros)
|
write |
30 | All mutating endpoints (e.g. POST /targets/import,
POST /supporters/import, DELETE /targets/{id},
prefer/exclude toggles)
|
Important: write requests consume from both buckets.
Mutating endpoints are counted against the write bucket (the stricter ceiling
trips first) and against the read bucket. A burst of 30 writes within
60 seconds also burns 30 of the 50 read slots. Plan read polling around your write volume,
or stagger writes if your client mixes both.
Every response carries rate-limit headers per bucket:
| Header | Meaning |
|---|---|
X-RateLimit-Limit-{read|write} |
The configured threshold for that bucket. |
X-RateLimit-Remaining-{read|write} |
Requests remaining in the current window for this customer. |
X-RateLimit-Reset-{read|write} |
Unix timestamp (seconds) when the window resets. Use this as the "retry after" target rather than computing your own. |
When a bucket is exhausted, the API returns
HTTP 429 Too Many Requests with body
{"status": 429, "error": ["rate_limited"]}. The
X-RateLimit-Reset-* header on the 429 response tells you when to retry.
Operational note. Counters are maintained per-pod in process memory. With multiple gateway replicas the effective ceiling a single client can hit is roughly N × limit, where N is the number of replicas behind the load balancer. Treat the published limits as the minimum guaranteed ceiling, not the maximum: a well-behaved client should stay well below them so it does not depend on routing luck.
Branch on the HTTP status code, and read a machine-readable code from
error[0]. An error carries a status, an error
array, and — on every code except the rate-limit 429 — a
timestamp. There is no message field. A request that fails
validation is the one exception, and is shown below.
{
"status": 403,
"timestamp": "2026-09-10T09:14:22.815Z",
"error": ["Forbidden resource"]
}
Where the API has a code for the condition, error[0] is that code rather than a
sentence — INSUFFICIENT_CREDITS on a 402,
rate_limited on a 429. Match on it exactly, including case:
{
"status": 402,
"timestamp": "2026-09-10T09:14:22.815Z",
"error": ["INSUFFICIENT_CREDITS"]
}
A 400 raised by request validation is the one body that does not
follow the shape above: it names each offending field separately, under errors
(plural, and objects rather than strings), and carries no timestamp:
{
"status": 400,
"errors": [{"key": "linkedinUrls", "error": "linkedinUrls must not be empty"}]
}
| Status Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request — invalid parameters |
| 402 | Insufficient credits — the credit balance is exhausted and the write was refused. Reads are unaffected. Top up to continue writing. |
| 403 | Forbidden — missing, unknown or revoked API key, or a resource that is not yours |
| 404 | Not Found — resource doesn't exist |
| 412 | Your own LinkedIn is not connected to Draftboard. Ratings and personal labels belong to a
person, so a rating or label write, a rating / label filter, or label counts are refused
instead of guessing whose to use. Nothing was read or written; connect LinkedIn in the app and retry. |
| 429 | Too Many Requests — rate limit exceeded |
| 500 | Internal Server Error |
| 503 | The credit balance could not be verified, so the write was refused rather than let through. Safe to retry. |
There is no 401. An unauthenticated or revoked key is
rejected with 403.