---
title: "Create Person Connections signal"
method: POST
path: "/api/v1/signals/person-connections"
tags: ["Signals"]
---

# Create Person Connections signal

`POST /api/v1/signals/person-connections`

## Request

- Base URL: `https://api.rodz.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.rodz.io/api/v1/signals/person-connections`
- Auth: HTTP bearer

Get the people in the LinkedIn networks of the people you name — your whole team and your happy clients, your investors, advisors, champions, or any industry figure, up to 500 people in one signal — filtered to the roles and companies you sell to, each lead with the person who can introduce you. Two parts, which you can use together or apart: a **one-time extract** of their existing connections, delivered as fast as they are read, then, if you want, the people who **newly connect** with them, every day.

**Input:** `linkedinProfileUrls` (1-500 LinkedIn profile URLs of the people to watch, required; a person known to several of them is delivered and billed once, with the first watched person whose network it was read in); `oneTimeExtractCount` (how many people to deliver once from their existing connections, counted in leads delivered after your filters, across all watched people; 0-25,000, defaults to 500; when their networks hold fewer matching people, everyone who matches is delivered; not metered by `dailyLeadLimit`); `dailyNewConnections` (true to keep delivering new connections after the one-time extract, up to `dailyLeadLimit` a day: only people who connect with a watched person after the signal started watching them; defaults to false, which stops the signal once the one-time extract is complete). Existing connections are delivered only by the one-time extract, up to `oneTimeExtractCount`: with `0`, none is delivered or billed; optional persona targeting (`targetPersonas`, `customPersonaTitles`); optional ICP filters (`companySizeFilters`, `locations`, `industryFilters`, `excludedLocations`, `excludedCompanySizeFilters`, `excludedIndustryFilters`, `excludedTitles`, `excludedCompanyUrls`) — with no filter at all, everyone is delivered; optional `restrictToCompanies` to deliver only people who work today at your target accounts (see **Only at your target accounts** below); optional enrichment toggles (`getEmails`, `getPhones`, `getSireneData`, `detectTechnologies`, `getHomepageContent`); `webhookUrl`, `dailyLeadLimit`, `duplicateHandling`, `status`. Each lead is billed as it is delivered: **3 credits per lead for the one-time extract, then 5 credits per lead for the daily new connections**. Activating with `status: active` checks that your balance covers the one-time extract (`oneTimeExtractCount` × 3 credits) and answers 402 with the estimate when it does not. A watched person who hides their connection list on LinkedIn cannot be read.

**Only at your target accounts** (`restrictToCompanies`): your team's network, limited to the accounts you are working. Send your account list and only the connections whose CURRENT employer is on it are delivered; a connection who only worked there before is not. Everyone else is left out before billing: no lead, no credit, and it does not count toward `oneTimeExtractCount`. Each entry is a domain (`"payfit.com"`), a LinkedIn company page (`"https://www.linkedin.com/company/doctolib"`), a numeric LinkedIn company id (`"10426145"`) or a Sales Navigator company link, a company name, or an object `{name, domain, linkedinUrl}`; send every identifier you have, the domain and the LinkedIn page are the most reliable. Matching is exact (case, punctuation and legal suffixes such as SAS or GmbH ignored), never fuzzy. Set it when you create the signal: the list then becomes the signal's company list, read and edited at `GET|POST|DELETE /api/v1/watchlists/{signalId}/companies` (up to 10,000 entries per call), and is not echoed in `config`. An empty or missing `restrictToCompanies` means no restriction; once a signal is restricted, removing every company from its list delivers nothing until you add some back. `restrictToProfiles` (LinkedIn profile URLs) works the same way for named people, and a person on either list is delivered. Example `config`: `{"linkedinProfileUrls": ["https://www.linkedin.com/in/julie-moreau-sales/"], "restrictToCompanies": ["payfit.com", "https://www.linkedin.com/company/doctolib", {"name": "Alan", "domain": "alan.com"}], "oneTimeExtractCount": 1000}`.

**Output:** each matching person is pushed to your `webhookUrl` as an enriched person lead: the contact (name, email if enriched, phone if enriched, title, location, LinkedIn profile URL, matched persona), their employer (company name, website, LinkedIn URL, plus tech stack and homepage content when those enrichments are enabled), and `signal.tracked_contact` — the watched person whose network the lead belongs to, so a signal watching several people tells you which one produced each lead. `signal.tracked_company` is always empty. The single-signal responses report progress in `applied.oneTimeExtract` (`target`, `delivered`, `status`).

**Use cases:**
- Intro paths from your team and happy clients - put every salesperson, founder and satisfied customer in one signal (up to 500 people) and get the people in their networks who match your ICP, each with the name of the person who can introduce you.
- Warm-network prospecting - reach the people around your investors, advisors, customers or champions, filtered to the roles and companies you sell to, instead of cold lists.
- Your team's network, only at your target accounts - add `restrictToCompanies` with your account list (domains, LinkedIn company pages or names) and get only the people your team knows who work there today, each with who can introduce you; everyone else is left out before billing.
- Account mapping from one contact - watch a key decision maker and get the colleagues and peers in their network who match your ICP.
- Event and ecosystem follow-up - watch a speaker, a co-marketing ally or an industry figure and turn their audience of peers into a targeted list, then keep receiving the people who join it.

## Request body

- object: Create a Person Connections signal.
  - `name` string, required: Name for this signal
  - `webhookUrl` string, nullable: Where each lead is POSTed as it is produced. Optional (since 2026-09-23): omit it when you read leads with GET /api/v1/signals/{signalId}/leads or route them to a connected CRM or sequencer with `destinations` on PATCH /api/v1/signals/{signalId}.
  - `config` PersonConnectionsConfig, required: Config for person_connections: the network of people you name. Two parts, independent of each other, and your filters (personas, company size, location, industry, exclusions) apply to both. With no filter at all, everyone is delivered. - **One-time extract** (`oneTimeExtractCount`): up to that many people from the watched people's EXISTING networks, delivered once, as fast as they are read — not metered by `dailyLeadLimit`. The count is of leads delivered, after your filters, across all watched people together. 3 credits per lead. - **Daily new connections** (`dailyNewConnections`): once the one-time part is complete, keep delivering the people who newly connect with them, under `dailyLeadLimit`. Off, the signal stops after the one-time part. 5 credits per lead. Existing connections are delivered ONLY by the one-time extract, up to `oneTimeExtractCount`. The daily part delivers only people who connect with a watched person after the signal started watching them: with `oneTimeExtractCount` 0, nothing from the existing networks is delivered or billed, and the first read only records where each network stands. `restrictToCompanies` keeps only the people whose CURRENT employer is on your account list: your team's network, only at your target accounts. Everyone else is left out before billing and does not count toward `oneTimeExtractCount`. Empty or missing: no restriction.
    - `restrictToCompanies` union[]: Only deliver leads at these companies. Each entry is either a plain string — a name, a domain, or a LinkedIn company URL (e.g. `"Doctolib"`, `"payfit.com"`) — or an object `{name, domain, linkedinUrl}` (all optional, at least one required) carrying everything you know about the company; both forms can be mixed. The more identifiers you send, the more of your list is recognised. Matching is exact once punctuation and legal suffixes are ignored. Consumed on create, so it is NOT echoed back in `config`; read and edit it at `GET|POST|DELETE /api/v1/watchlists/{signalId}/companies`. **Accepted entry forms:** a company name (`"Doctolib"`); a domain or website URL (`"payfit.com"`, `"https://www.payfit.com/fr"`); its LinkedIn company page (`"https://www.linkedin.com/company/payfit"`, in any spelling: with or without `https://` and `www.`, a country subdomain, a trailing path or query; school and showcase pages too); its numeric LinkedIn company id (`"10426145"`) or a Sales Navigator company link (`"https://www.linkedin.com/sales/company/10426145"`), both read as that id; or an object `{name, domain, linkedinUrl}` combining several. Any other LinkedIn link names no company: a person's profile (`/in/`), a post or a group page is refused with 422 `INVALID_COMPANY_ENTRY`, which lists each offending entry, and nothing is saved.
      - union
        - string
        - WatchedCompanyObject: One company, with everything you know about it. The object form of a company-list entry. Send every identifier you have: each one is another way to recognise the company, and together they separate two companies that share a name.
          - `name` string, nullable: The company name as you know it, e.g. `"PayFit"`.
          - `domain` string, nullable: The company website, e.g. `"payfit.com"`. A full URL is accepted and reduced to its domain.
          - `linkedinUrl` string, nullable: The company LinkedIn page, e.g. `"https://www.linkedin.com/company/payfit"`. The most precise identifier of the three. A Sales Navigator company link (`/sales/company/<id>`) or the numeric LinkedIn company id is accepted too, and so are school and showcase pages; any other LinkedIn link (a person's profile, a post) is refused with 422 `INVALID_COMPANY_ENTRY`.
    - `restrictToProfiles` string[]: Only deliver leads for these people — LinkedIn profile URLs. Consumed on create like `restrictToCompanies`; read and edit it at `GET|POST|DELETE /api/v1/watchlists/{signalId}/profiles`.
    - `companySizeFilters` string[]: Company size filter. Values: "Self-employed", "1-10", "11-50", "51-200", "201-500", "501-1000", "1001-5000", "5001-10000", "10001+". A company qualifies when its declared size band OR its employee count falls in a selected band; a count within 10% of a band edge counts for both neighbouring bands, and a company declaring 501+ employees is never placed in a smaller band by a low count. The delivered `employee_range` / `employee_count` are the pair the lead was judged on.
    - `targetPersonas` string[]: Filter by job-role persona (decision-maker targeting). Accepted values are the persona display names listed here; see GET /v1/personas for the full taxonomy with department, seniority and EN/FR job titles. Titles are matched by meaning, not as text: `targetPersonas` and `customPersonaTitles` form ONE list and a person qualifies by matching ANY entry. Each entry is read as a function plus a seniority level, in any language ("Directeur Commercial" satisfies "Sales Director", "Président" or "Directeur général" a CEO target), and a role at that level or above qualifies: a Director or Head target refuses a plain Manager; a founder running the company counts as CEO. A custom title carries the level its own words state; one with no level word accepts every level. Valid values: "Account Executive", "Account Manager", "Accountant", "Agency Director", "Business Analyst", "Business Developer", "Business Unit Director", "Buyer", "CEO", "CIO", "CSR Director", "CSR Manager", "CTO", "Communication Director", "Community Manager", "Content Manager", "Copywriter", "Creative Director", "Customer Success Director", "Customer Success Manager", "Customer Support Agent", "Cybersecurity Director", "Data Analyst", "Data Director", "Designer", "DevOps Engineer", "Digital Director", "Digital Project Manager", "Ecommerce Director", "Ecommerce Manager", "Engineering Manager", "Engineering Office Director", "Ergonomist", "Executive Assistant", "Finance Director", "Finance Manager", "GTM Director", "GTM Manager", "Growth Director", "Growth Manager", "HR Assistant", "HR Development", "HR Director", "Happiness Manager", "IT Project Manager", "Industrial Director", "Innovation Director", "Lab Manager", "Legal Counsel", "Legal Director", "Legal Manager", "Logistics Coordinator", "Logistics Director", "M&A Director", "Marketing Director", "Marketing Manager", "Office Manager", "Omnichannel Director", "Operations Director", "Operations Manager", "Payroll Manager", "Payroll Officer", "Procurement Specialist", "Product Director", "Product Manager", "Project Manager", "Purchasing Director", "Purchasing Manager", "Quality Health and Safety", "Quality Manager", "Quality Technician", "R&D Director", "R&D Engineer", "R&D Manager", "Recruitment Manager", "Research Director", "Research Scientist", "SDR / BDR", "Sales Director", "Sales Manager", "Social Media Director", "Social Media Manager", "Software Engineer", "Supply Chain Director", "Support Director", "Support Manager", "Talent Acquisition", "Technical Director", "Training Director", "Training Officer", "Transformation Director", "UX Designer", "Warehouse Director", "Web Developer"
    - `locationFilter` string: Filter by location (e.g. 'France', 'Paris')
    - `locations` string[]: Target locations (e.g. ['France', 'Paris']). Applies to the PERSON's LinkedIn profile location, not the company's headquarters: a person in Paris working for a company headquartered abroad matches France. One matching entry is enough. Only fundraising and M&A signals filter on headquarters (`hqLocations`).
    - `excludedLocations` string[]: Locations to exclude from results
    - `industryFilters` string[]: Only return companies in these LinkedIn industries. Two kinds of value work: a single industry (e.g. 'Software Development') or a broad category that covers many (e.g. 'Technology and Media'). Matching is exact, so a near-miss like 'Software development' matches nothing and the signal quietly produces no leads. Empty = all industries. Call GET /api/v1/signals/industries for the valid list.
    - `companyTypeFilters` string[]: Only return companies of these legal/ownership types. Valid values: 'Privately Held', 'Public Company', 'Nonprofit', 'Educational Institution', 'Partnership', 'Sole Proprietorship', 'Government Agency', 'Self-Employed'. Empty = all types. A company whose type is unknown is KEPT, not dropped, so this narrows results only where the type is known.
    - `excludedIndustryFilters` string[]: LinkedIn industries to exclude. Same vocabulary as `industryFilters` (single industry or category name); an unrecognised value excludes nothing. Call GET /api/v1/signals/industries for the valid list.
    - `excludedCompanyUrls` string[]: LinkedIn company URLs to exclude from results
    - `excludedCompanySizeFilters` string[]: Company sizes to exclude from results
    - `customPersonaTitles` string[]: Custom free-text job titles to match in addition to predefined personas. Titles are matched by meaning, not as text: `targetPersonas` and `customPersonaTitles` form ONE list and a person qualifies by matching ANY entry. Each entry is read as a function plus a seniority level, in any language ("Directeur Commercial" satisfies "Sales Director", "Président" or "Directeur général" a CEO target), and a role at that level or above qualifies: a Director or Head target refuses a plain Manager; a founder running the company counts as CEO. A custom title carries the level its own words state; one with no level word accepts every level.
    - `excludedTitles` string[]: Job titles to exclude. Matched as whole words, case- and accent-insensitive: "intern" excludes "Intern" and "Interns", never "International"; a phrase matches the same words in the same order; end a word with * to match a prefix ("infirmi*" excludes "Infirmière").
    - `getEmails` boolean: Enable email enrichment (0.5 credit/lead, charged only on success)
    - `getPhones` boolean: Enable phone enrichment (5 credits/lead, charged only on success)
    - `getSireneData` boolean: Enrich with French SIRENE registry data (1 credit/lead, charged only on success)
    - `detectTechnologies` boolean: Detect technologies used on the company's public website (1 credit/lead, charged only on success)
    - `getHomepageContent` boolean: Fetch the company's homepage content (1 credit/lead, charged only on success)
    - `linkedinProfileUrls` string[]: LinkedIn profile URLs of the people whose connections you want, 1 to 500. A person who hides their connection list on LinkedIn cannot be read; the signal reports them as not visible.
    - `oneTimeExtractCount` integer: How many people to deliver, once, from the watched people's existing connections — counted in leads delivered after your filters, across all watched people. If their networks hold fewer matching people, everyone who matches is delivered. Not metered by `dailyLeadLimit`; billed 3 credits per lead delivered. `0` skips this part: no existing connection is delivered or billed.
    - `dailyNewConnections` boolean: After the one-time extract, keep delivering the people who newly connect with the watched people, up to `dailyLeadLimit` a day, billed 5 credits per lead delivered. Only people who connect after the signal started watching are delivered: existing connections beyond `oneTimeExtractCount` never are. `false` stops the signal once the one-time extract is complete.
    - `searchUrl` string: Operational — set by the system
  - `dailyLeadLimit` integer: Max leads per day for the daily part (`dailyNewConnections`). The one-time extract is bounded by `oneTimeExtractCount` instead.
  - `duplicateHandling` string, nullable: Duplicate lead handling: 'refuse' (never accept), 'allow' (always accept), or 'Xd' (accept a duplicate after X days, e.g. '7d'). Optional — defaults to 'allow', so a contact who triggers this signal a second time is delivered a second time.
  - `status` string, nullable: Initial status (default: draft). Accepts draft, active, or paused. If active, the config is validated and the balance must cover the one-time extract (`oneTimeExtractCount` x the price per lead), otherwise 402.

## Response `201`

Signal created

- object: A single signal: create, GET /signals/{id} and PATCH. It carries two fields the list does not. A customer reads their stored `config` back and assumes it is the one in force; on `job_changes` three fields were not (`maxJobAgeDays` 90 stored / 180 applied, `publishedDate` stored and never read, `allowUnknownCompanySize` true and applied false), with nothing anywhere saying so.
  - `id` string, required
  - `name` string, required
  - `status` string, required
  - `signalTypeId` string, required
  - `webhookUrl` string, nullable
  - `config` object, nullable: Signal-specific configuration
  - `dailyLeadLimit` integer, required
  - `leadsToday` integer, required
  - `leadsTotal` integer, required
  - `duplicateHandling` string, required: Duplicate lead handling: 'refuse' (never accept), 'allow' (always accept), or 'Xd' (accept after X days)
  - `createdAt` string, required
  - `updatedAt` string, required
  - `lastLeadAt` string, nullable
  - `applied` object: Settings this signal runs with where they differ from `config`, keyed like `config`. `{}` when every stored setting is applied as stored. A `null` value means the stored field is not used by this signal. Every entry is explained by a warning naming the same field.
  - `warnings` SignalConfigWarning[]: What you should know about this configuration: values applied differently from what is stored, and filter values that are probably not what you meant. Computed from the stored configuration, so they appear on GET as well as on create and update - a signal created before these fields existed reports them too. Never an error: the signal is created or updated regardless.
    - `code` string, required: Stable identifier. `VALUE_BELOW_MINIMUM` (a value under the documented minimum is applied as the minimum), `VALUE_ADJUSTED` (a value the search cannot use as written is applied as the nearest one it can, e.g. a `publishedDate` of 14d searches 15 days), `FIELD_NOT_USED` (a stored field this signal ignores), `UNKNOWN_SIZE_NOT_ACCEPTED`, `CATEGORY_WITH_OWN_INDUSTRIES` (a category listed together with industries it already covers), `UNKNOWN_INDUSTRY` (a value matching no published industry), `UNKNOWN_FIELD` (a config key this signal does not have), `UNKNOWN_TOOL` / `UNKNOWN_CATEGORY` (a tracked tool or category name that is not in the catalog of GET /api/v1/signals/technographic-tools; on a GMB Discovery signal, a `category` that is not a Google Maps category, so the signal matches no business), `UNKNOWN_CITY` (GMB Discovery: cities in `cities` that are not in the signal's `country` — `values` lists them; when none is, the signal matches no business until `cities` or `country` is changed; never an error, nothing stored is changed), `NO_FOLLOWER_SOURCE` (a Company Followers signal with no follower extraction set up yet, so it produces nothing), `DAILY_LIMIT_PENDING` (a job signal's `dailyLeadLimit` changed less than a day after its search was last rebuilt: the search keeps its previous volume until the time in `values`, while the leads delivered per day already follow the new limit), `WATCHED_LIST_EMPTY` (a watchlist reading its list from your CRM holds no company or person — the CRM is not connected, the list could not be read, or it is empty — so it produces nothing until the list fills; cleared on its own once it does), `QUOTE_REQUIRED` (an `active` Company Followers signal in `icp` mode whose follower extraction has not been priced and accepted, so it produces nothing yet: `values[0]` says why — `not_requested`, `awaiting_acceptance` (then `values[1]` is the quoted price in credits), `expired`, `declined`, `failed`, or, after an automatic quote, `auto_quote_refused` / `auto_accept_refused` (then `values[1]` is the error code); the message names the call to make — see POST /api/v1/signals/{signal_id}/extraction/quote; cleared once a quote is accepted), `WATCHED_LIST_IMPORTING` (a company watchlist whose HubSpot list is still being read: companies are added as they arrive; `values[0]` is the number of members read so far, `values[1]` the list id; cleared once the whole list is read), `MATCHING_CONFIRMATION_REQUIRED` (a New Decision Maker or Account watchlist whose companies must be matched to their LinkedIn company page before they can be watched, at a cost above the amount we match without asking: nothing is matched or charged until you confirm with POST /api/v1/watchlists/{signalId}/companies/confirm-matching; `values[0]` is the maximum cost, `values[1]` the number of companies to match; cleared once confirmed), `DESTINATION_UNAVAILABLE` (the outreach campaign this signal sends leads to cannot receive them — not found, archived, ended or without any step — so leads are not reaching it: `values[0]` is `campaign_not_found`, `campaign_archived`, `campaign_ended` or `campaign_no_steps`; cleared once a lead reaches the campaign or the signal's destination is changed), `DESTINATION_NEEDS_CONTACT` (the signal delivers companies with no contact and a destination in `destinations` only accepts people, so none of its leads reach that destination), `DESTINATION_SKIPS_COMPANY_ONLY` (some leads are companies with no contact, e.g. no one matching your personas was found, and a destination only accepts people), `DESTINATION_NEEDS_EMAIL` (a destination only accepts leads with an email and `getEmails` is off) — for these three, `values[0]` is the destination key in `destinations`, the next values are the fixes: `add_persona` (turn on `enablePersonaTargeting` with `targetPersonas`), `require_contact` (turn on `requireContact`), `add_company_destination` (add a destination that accepts companies: `hubspot`, `pipedrive`, `googleSheets` or `customWebhook`), `enable_email_finding` (turn on `getEmails`); severity `warning` when those leads reach no destination at all, `info` when another destination still receives them; never blocks the save; `COMPANY_PAGE_UNAVAILABLE` (a Company Followers signal whose extraction stopped because a page in `linkedinCompanyUrls` does not exist on LinkedIn: nothing was extracted or charged; `values` lists the pages; replace them with PATCH /api/v1/signals/{signal_id}, then start the extraction again; cleared once the pages are replaced or a new extraction runs), `COMPANY_PAGE_UNVERIFIED` (create / PATCH responses only: a page in `linkedinCompanyUrls` could not be verified at that moment and was accepted; `values` lists them — if one does not exist, its extraction stops without using credits and the signal carries `COMPANY_PAGE_UNAVAILABLE`).
    - `severity` string, required: `info`: nothing to do. `warning`: probably not what you meant.
    - `field` string, required: The `config` key concerned, camelCase (or `dailyLeadLimit`).
    - `message` string, required
    - `values` string[]: The offending values, when there are some.

## Other responses

- `400`: Invalid request: missing required config (People to Watch (LinkedIn profile URLs)), or an unrecognized location
- `401`: Missing or invalid API key
- `402`: Account has no credits
- `422`: Validation Error
- `429`: Rate limit exceeded. Retry after the number of seconds in the `Retry-After` response header.

---

[API](https://skmtc.dev/rodz/apis/rodz-api.md) · [All operations](https://skmtc.dev/rodz/apis/rodz-api/llms.txt) · [OpenAPI document](https://skmtc.dev/rodz/apis/rodz-api/revisions/3003505ff896?raw)
