---
title: "Get a signal's rejection funnel"
method: GET
path: "/api/v1/signals/{signal_id}/funnel"
tags: ["Signals"]
---

# Get a signal's rejection funnel

`GET /api/v1/signals/{signal_id}/funnel`

## 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/{signal_id}/funnel`
- Auth: HTTP bearer

See why a signal delivers fewer leads than you expect: how many people it found, how many each of your filters removed, and the job titles, company sizes, locations and industries it rejected most often. Built for tuning a signal (by hand or with an agent): pick the filter that removes the most candidates you would actually want, relax it with `PATCH /api/v1/signals/{signal_id}`, and read the funnel again a few days later.

**Input:** the signal ID in the path; optional `days` (1-90, default 30), the window ending now.

**Output:** `candidatesFound` (candidates found before any filter; `candidateUnit` says whether a candidate is a person or a job posting), `rejectedBy` (`persona`, `companySize`, `location`, `industry`, `excluded`, `duplicate`, `other`; job signals add `jobTitle`, `jobDescription` and `publishedDate`), `otherBreakdown` (splits `other`), `passedFilters`, `awaitingReview` (found, not yet checked), `unattributed` (checked, but the record does not say which of your settings removed them; left out of `rejectedBy` rather than guessed; `unattributedReason` explains it when the cause is known), `delivered` (leads delivered in the window) and `topRejected` (the five most frequent rejected values per filter; `unknown` means the value was missing, which usually means accepting unknown values is the lever).

**How candidates are counted:** each candidate is counted once, under the FIRST filter it failed; `filterOrder` gives the order for this signal type (for competitor connections: company size, location, persona, industry, excluded companies, duplicates). A later filter never saw what an earlier one removed, so relaxing company size alone can move candidates on to the persona filter instead of into your leads. For social engagement and technology signals (`countingMethod: per_run_counters`) the counts are summed per collection run, so a person seen by several runs is counted each time, and no values are sampled.

**Job signals** (job offers, republished job offers, recruitment campaigns): the filters judge job postings (`candidateUnit: job_posting`), then the people you target are searched at each company that passed, so `delivered` counts people and can exceed `passedFilters`.

**Company followers:** the people each follower extraction returned, the role check and the company-size check they failed, and what was delivered. People already delivered on the signal are skipped without a per-person record, so they appear in `unattributed`.

**Account watchlists:** `sources` holds one funnel per source (`countingMethod: per_source`); the top-level numbers add up the measured sources.

**Coverage:** competitor connections, competitor customer care, job changes, new decision makers, employee departures, dormant LinkedIn posters, social engagement and technology signals, job offers, republished job offers, recruitment campaigns, company followers and account watchlists. Other signal types answer `supported: false` with a reason; that means 'not measured', never 'nothing was rejected'. Numbers are refreshed at most every 10 minutes.

## Path parameters

- `signal_id` string, required

## Query parameters

- `days` integer: Size of the window, in days, ending now. 1 to 90; default 30.

## Response `200`

Successful Response

- object
  - `signalTypeId` string, required: The signal's type.
  - `supported` boolean, required: False when this signal type does not record per-filter rejections yet. Every count below is then absent: 'not measured', not zero.
  - `unsupportedReason` string, nullable: Why the funnel is not available.
  - `countingMethod` 'per_candidate' | 'per_run_counters' | 'per_source', nullable: `per_candidate`: each candidate found is counted once, under the first filter it failed. `per_run_counters`: the counts are summed over every collection run in the window, so a person seen by several runs is counted each time, the stages can overlap slightly (they need not add up to `candidatesFound`), and no rejected values are sampled (`topRejected` is empty). `per_source` (account watchlists): the sum of `sources`, one entry per source of the watchlist.
  - `candidateUnit` 'person' | 'job_posting' | 'mixed', nullable: What one candidate is. `person` for most signals. `job_posting` for job signals: the filters judge postings, then people are searched at each company that passed, so `delivered` (people) can exceed `passedFilters` (postings). `mixed` for an account watchlist whose sources count different things: read `sources` for comparable numbers.
  - `filterOrder` string[], nullable: The order this signal type applies its filters in, using the `rejectedBy` keys (`jobChangeAge` is the job-age part of `other`). Null for `per_run_counters`, whose stages are not strictly ordered.
  - `dataAvailableSince` string, date-time, nullable: For `per_run_counters`: the date counting started. A window that begins earlier only covers the period since then.
  - `candidatesFound` integer, nullable: People the signal found in the window, before any of your filters. A candidate that aged out before it was reviewed is not counted here, nor anywhere else in the funnel.
  - `rejectedBy` object, nullable: How many candidates each filter removed. A candidate is counted under the FIRST filter it failed, in the order given by `filterOrder`. Filters later in that order never saw the candidates an earlier filter removed, so relaxing only an early filter can move them on to the next one.
    - `persona` integer, required: Removed because the person's role or job title does not match your target personas / custom job titles.
    - `companySize` integer, required: Removed because the company's headcount band is outside your company-size filters, or is unknown while unknown sizes are not accepted.
    - `location` integer, required: Removed because the person's location is outside your locations, or unknown.
    - `industry` integer, required: Removed because the company's industry is outside your industry filters.
    - `excluded` integer, required: Removed because the company is on your excluded-companies list.
    - `duplicate` integer, required: Already delivered to you on this signal (inside your deduplication window). Candidates refused by the final role check made just before delivery count under `persona`; before 2026-09-25 some of them were counted here instead.
    - `other` integer, required: Removed for any other reason. `otherBreakdown` splits this number.
    - `jobTitle` integer, nullable: Job-posting signals only (null otherwise): postings removed because the job title does not contain your included keywords, or contains an excluded one.
    - `jobDescription` integer, nullable: Job-posting signals only: postings removed on your job-description keywords (included or excluded).
    - `publishedDate` integer, nullable: Job-posting signals only: postings removed because they were published before your `publishedDate` window.
  - `otherBreakdown` object: Splits `rejectedBy.other`. Keys: `companyNotIdentified` (no company name, page or website to check your company filters against), `jobChangeTooOld` / `jobChangeDateUnknown` (job changes older than your maximum job age, or undated), `noWatchedCompany`, `noFormerCompany`, `notReactivated`, `sourceRemovedFromSettings`, `noLeadCreated`, `competitorEmployee` (the person works at the competitor you watch), `ownCompany` (the person works at your own company), `profileUnavailable` (the profile's details could not be read when it was reviewed), `noJobTitle` (the profile shows no job title or headline to check your personas against), `notMutual` (connected to only one of the two profiles of a mutual-connections signal), `existingNetwork` (already connected to the watched person when the signal started watching, and not part of your one-time extract: not delivered, not billed), `commentKeyword` (LinkedIn engagement signals: the person's comment contains one of your `excludedCommentKeywords`; not delivered, not billed), `unclassified`.
  - `passedFilters` integer, nullable: Candidates that passed every filter. More than `delivered` when your daily lead limit or credit balance stopped a run.
  - `awaitingReview` integer, nullable: Candidates found but not yet checked against your filters.
  - `unattributed` integer, nullable: Candidates that were checked, but whose record does not say which of YOUR settings removed them. They are left out of `rejectedBy` rather than guessed.
  - `unattributedReason` string, nullable: Why `unattributed` is not zero, or why `candidatesFound` is empty, when this signal type has a known explanation.
  - `delivered` integer, nullable: Leads delivered by this signal in the window: the leads you can see. A lead removed by a final automatic check (e.g. a company-size recheck) is counted under that filter, not here.
  - `topRejected` object, nullable: The most frequent values among recently rejected candidates, per filter. Use them to choose what to relax: e.g. a job title rejected often that you would in fact want, or a size band just outside your filters.
    - `persona` object[]: Top rejected job titles.
      - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
      - `count` integer, required: Rejected candidates with this value, among those sampled.
    - `companySize` object[]: Top rejected company-size bands.
      - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
      - `count` integer, required: Rejected candidates with this value, among those sampled.
    - `location` object[]: Top rejected locations.
      - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
      - `count` integer, required: Rejected candidates with this value, among those sampled.
    - `industry` object[]: Top rejected industries.
      - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
      - `count` integer, required: Rejected candidates with this value, among those sampled.
    - `jobTitle` object[], nullable: Job-posting signals only: top rejected job-posting titles.
      - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
      - `count` integer, required: Rejected candidates with this value, among those sampled.
  - `signalId` string, required: The signal's id.
  - `window` object, required
    - `days` integer, required: Length of the window, in days.
    - `from` string, date-time, required: Start of the window (UTC).
    - `to` string, date-time, required: End of the window (UTC).
  - `sources` object[], nullable: Account watchlists only: one funnel per source, each counted the way that source's signal type counts. The top-level numbers add up the sources that are measured; `delivered` adds up all of them.
    - `signalTypeId` string, required: The signal's type.
    - `supported` boolean, required: False when this signal type does not record per-filter rejections yet. Every count below is then absent: 'not measured', not zero.
    - `unsupportedReason` string, nullable: Why the funnel is not available.
    - `countingMethod` 'per_candidate' | 'per_run_counters' | 'per_source', nullable: `per_candidate`: each candidate found is counted once, under the first filter it failed. `per_run_counters`: the counts are summed over every collection run in the window, so a person seen by several runs is counted each time, the stages can overlap slightly (they need not add up to `candidatesFound`), and no rejected values are sampled (`topRejected` is empty). `per_source` (account watchlists): the sum of `sources`, one entry per source of the watchlist.
    - `candidateUnit` 'person' | 'job_posting' | 'mixed', nullable: What one candidate is. `person` for most signals. `job_posting` for job signals: the filters judge postings, then people are searched at each company that passed, so `delivered` (people) can exceed `passedFilters` (postings). `mixed` for an account watchlist whose sources count different things: read `sources` for comparable numbers.
    - `filterOrder` string[], nullable: The order this signal type applies its filters in, using the `rejectedBy` keys (`jobChangeAge` is the job-age part of `other`). Null for `per_run_counters`, whose stages are not strictly ordered.
    - `dataAvailableSince` string, date-time, nullable: For `per_run_counters`: the date counting started. A window that begins earlier only covers the period since then.
    - `candidatesFound` integer, nullable: People the signal found in the window, before any of your filters. A candidate that aged out before it was reviewed is not counted here, nor anywhere else in the funnel.
    - `rejectedBy` object, nullable: How many candidates each filter removed. A candidate is counted under the FIRST filter it failed, in the order given by `filterOrder`. Filters later in that order never saw the candidates an earlier filter removed, so relaxing only an early filter can move them on to the next one.
      - `persona` integer, required: Removed because the person's role or job title does not match your target personas / custom job titles.
      - `companySize` integer, required: Removed because the company's headcount band is outside your company-size filters, or is unknown while unknown sizes are not accepted.
      - `location` integer, required: Removed because the person's location is outside your locations, or unknown.
      - `industry` integer, required: Removed because the company's industry is outside your industry filters.
      - `excluded` integer, required: Removed because the company is on your excluded-companies list.
      - `duplicate` integer, required: Already delivered to you on this signal (inside your deduplication window). Candidates refused by the final role check made just before delivery count under `persona`; before 2026-09-25 some of them were counted here instead.
      - `other` integer, required: Removed for any other reason. `otherBreakdown` splits this number.
      - `jobTitle` integer, nullable: Job-posting signals only (null otherwise): postings removed because the job title does not contain your included keywords, or contains an excluded one.
      - `jobDescription` integer, nullable: Job-posting signals only: postings removed on your job-description keywords (included or excluded).
      - `publishedDate` integer, nullable: Job-posting signals only: postings removed because they were published before your `publishedDate` window.
    - `otherBreakdown` object: Splits `rejectedBy.other`. Keys: `companyNotIdentified` (no company name, page or website to check your company filters against), `jobChangeTooOld` / `jobChangeDateUnknown` (job changes older than your maximum job age, or undated), `noWatchedCompany`, `noFormerCompany`, `notReactivated`, `sourceRemovedFromSettings`, `noLeadCreated`, `competitorEmployee` (the person works at the competitor you watch), `ownCompany` (the person works at your own company), `profileUnavailable` (the profile's details could not be read when it was reviewed), `noJobTitle` (the profile shows no job title or headline to check your personas against), `notMutual` (connected to only one of the two profiles of a mutual-connections signal), `existingNetwork` (already connected to the watched person when the signal started watching, and not part of your one-time extract: not delivered, not billed), `commentKeyword` (LinkedIn engagement signals: the person's comment contains one of your `excludedCommentKeywords`; not delivered, not billed), `unclassified`.
    - `passedFilters` integer, nullable: Candidates that passed every filter. More than `delivered` when your daily lead limit or credit balance stopped a run.
    - `awaitingReview` integer, nullable: Candidates found but not yet checked against your filters.
    - `unattributed` integer, nullable: Candidates that were checked, but whose record does not say which of YOUR settings removed them. They are left out of `rejectedBy` rather than guessed.
    - `unattributedReason` string, nullable: Why `unattributed` is not zero, or why `candidatesFound` is empty, when this signal type has a known explanation.
    - `delivered` integer, nullable: Leads delivered by this signal in the window: the leads you can see. A lead removed by a final automatic check (e.g. a company-size recheck) is counted under that filter, not here.
    - `topRejected` object, nullable: The most frequent values among recently rejected candidates, per filter. Use them to choose what to relax: e.g. a job title rejected often that you would in fact want, or a size band just outside your filters.
      - `persona` object[]: Top rejected job titles.
        - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
        - `count` integer, required: Rejected candidates with this value, among those sampled.
      - `companySize` object[]: Top rejected company-size bands.
        - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
        - `count` integer, required: Rejected candidates with this value, among those sampled.
      - `location` object[]: Top rejected locations.
        - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
        - `count` integer, required: Rejected candidates with this value, among those sampled.
      - `industry` object[]: Top rejected industries.
        - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
        - `count` integer, required: Rejected candidates with this value, among those sampled.
      - `jobTitle` object[], nullable: Job-posting signals only: top rejected job-posting titles.
        - `value` string, required: The observed value (a job title, a size band, a location or an industry). `unknown` means the value was missing.
        - `count` integer, required: Rejected candidates with this value, among those sampled.
    - `source` string, required: The watchlist source, as named in the signal's `sources` settings (e.g. `fundraising`, `job_offers`, `new_decision_makers`, `competitor_connections`).
  - `countedAt` string, date-time, required: When these numbers were computed. They can be up to an hour old.

## Other responses

- `404`: No signal with this id on your account
- `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)
