---
title: "Deep Research"
method: POST
path: "/api/v2/research/deep"
tags: ["Company Research"]
---

# Deep Research

`POST /api/v2/research/deep`

Decide whether a single, known company fits your criteria in terms of recent activity and organizational structure as a qualifier.

<Info>**No API key yet?** [Sign up via Agent Auth](/agent-primitives/authentication) to get your `X-API-Key` - the only required header for this endpoint.</Info>

Combine an activity question (answered from job postings) and a team-composition question (answered from LinkedIn people) in a single call, with cited evidence per stage. Provide a `domain` plus at least one of `activity_question` or `qualifier_question`.

If you are not sure which domain OpenFunnel has indexed for a company, call [Lookup Companies](/helpers/lookup-companies) first. It accepts company names, domains, and LinkedIn URLs and returns the normalized company/domain records you can pass into Deep Research.

Each parameter is hard-wired to a specific data source - pick the one that matches where the answer lives:
- **`activity_question`** → answered from the company's **recent job postings**. Use for hiring intent, team expansion, tech-stack adoption visible in JDs, geographic hiring footprint, count/spike questions, first-time hiring or first-time JD mentions, and recent-hire intent.
- **`qualifier_question`** → answered from **LinkedIn people data** (current-employee profiles). Use for team composition, roles already filled, org structure (e.g. "has a Head of Security", "has ≥5 SDRs and a Sales Director", "has a dedicated AppSec team").

Activity question examples by category:
- **Single-role hiring:** "Is this company hiring SDRs based on recent job postings?"; "Do their recent job postings show developer relations engineer hiring?"
- **Initiative/team buildout:** "Do their job descriptions suggest they are building an AI customer support automation team?"; "Do recent postings indicate they are staffing a data governance program?"
- **Tech/vendor/concept mentions:** "Are they posting roles that mention Snowflake in at least 5 job descriptions in the last 90 days?"; "Do their job postings mention Salesforce more than 3 times this month?"
- **Role-count hiring:** "Have they posted more than 5 SDR roles in the last 90 days?"; "Are they expanding platform engineering with at least 4 platform engineer postings in the last 60 days?"
- **Concept-count hiring:** "Are they building around AI automation, with at least 4 related job postings this quarter?"; "Are they staffing generative AI work, with 6 or more related job postings in the last six months?"
- **Relative spikes:** "Does their recent job activity show a spike in SDR hiring?"; "Are data engineer postings up 40% week over week?"; "Are Kubernetes mentions in their job descriptions up 80%?"
- **Recent-hire intent:** "Does recent hiring activity suggest they brought someone in to build outbound sales?"; "Does recent hiring suggest they brought in a leader to stand up product security?"
- **First-time signals:** "Is this the first time their job postings mention Kubernetes?"; "Are they hiring Solutions Engineers for the first time?"; "Does this look like their first Head of Data hire?"

Qualifier question examples by category:
- **Named team / product ownership:** "Which engineers are in the Dropbox Dash team?"; "Which VP + sales people are in the AWS Textract team?"
- **Recent people changes:** "New joiners in sales in the last 60 days"; "Any recent GTM engineering hires in sales in the last 90 days?"
- **Team-size / count checks:** "Do they have more than 2 customer support people?"; "Do they have at least 5 SDRs/BDRs?"
- **Seniority / role coverage:** "Do they have a VP or Head of Data Platform?"; "Do they have a Head of Security?"
- **Specialized teams / skills:** "Do they have a dedicated AppSec / product-security team?"; "Do they employ Kubernetes-experienced platform engineers?"

Pass both `activity_question` and `qualifier_question` when a question spans both signals - each stage runs against its own data source and returns independent evidence.

Each stage returns a natural-language `answer` suitable for inline display, a boolean `qualified` verdict, and structured source evidence. Activity evidence is returned in `activity.sources`; qualifier evidence is returned in `qualifier.sources`. Job sources include title, URL, location, created timestamp, and why-relevant reason. People sources include name, title, LinkedIn URL, seniority, department, and start date when available.

Credits: charged **per call**, not per source returned. 1 successful call (at least one stage produced a finding) = 1 charge at the per-call price configured for your workspace. A 404 (domain could not be resolved) is free. The exact amount billed is returned in `credits_consumed` on every response.

## Headers

- `X-API-Key` string, required

## Request body

- DeepResearchRequest — Inputs for a single Deep Research call. Provide a `domain` plus at least one of: - `activity_question` - answered from the company's recent **job postings** (hiring intent, team expansion, JD-visible tech/vendor signals, count/spike signals, first-time hiring or first-time JD mentions). - `qualifier_question` - answered from **LinkedIn people data** (team composition, named teams/products, roles already filled, recent joiners, team-size checks, org shape). Pick the param whose backing data source contains the answer; pass both when the question spans both signals. If the company domain is ambiguous or does not resolve, use `POST /api/v1/account/lookup-companies` first to find the normalized domain OpenFunnel has indexed.
  - `domain` string, required — Company website domain to research (e.g. 'stripe.com'). Protocol and `www.` prefixes are stripped automatically. The domain is resolved to a LinkedIn org via the internal companies index before either stage runs. If this does not resolve, use [Lookup Companies](/helpers/lookup-companies) to find the normalized indexed domain.
  - `activity_question` string, nullable — **Answered from the company's recent job postings.** Natural-language description of the activity signal to look for in open roles. Use this for hiring intent, team expansion, tech-stack or vendor adoption visible in JDs, geographic hiring footprint, count/spike questions, first-time hiring, first-time job-post mentions, or recent-hire intent. Examples: `Is this company hiring SDRs based on recent job postings?`, `Do their job descriptions suggest they are building an AI customer support automation team?`, `Are they posting roles that mention Snowflake in at least 5 job descriptions in the last 90 days?`, `Have they posted more than 5 SDR roles in the last 90 days?`, `Are data engineer postings up 40% week over week?`, `Is this the first time their job postings mention Kubernetes?`. Pass `null` or omit to skip this stage.
  - `qualifier_question` string, nullable — **Answered from LinkedIn people data** (current-employee profiles). Natural-language criteria about who already works at the company or how the org is shaped. Use this for team composition, named teams/products, roles already filled, seniority coverage, team-size/count checks, recent joiners or start-date questions, and skill/team specialization. Examples: `Which engineers are in the Dropbox Dash team?`, `New joiners in sales in the last 60 days`, `Do they have more than 2 customer support people?`, `Has at least 5 SDRs/BDRs and a Sales Director`, `Has a dedicated AppSec / product-security team`. Pass `null` or omit to skip this stage.
  - `timeframe_days` integer — Lookback window (days) used by the activity stage. Has no effect when `activity_question` is omitted.
  - `max_jobs_to_check` integer — Upper bound on the number of recent job postings the activity stage scores with the LLM relevance pass. Higher values raise recall but add latency / token cost (each job is judged by an LLM call). Default 25 is tuned for interactive use; bump it for batch / data-extraction workflows where you want maximum recall. Has no effect when `activity_question` is omitted.

## Response `200`

Research findings with natural-language answers and source evidence.

- DeepResearchResponse — Top-level Deep Research response. Stage-level `activity.sources` and `qualifier.sources` contain cited evidence.
  - `domain` string, required — Echo of the requested domain (normalized).
  - `company` CompanyInfo — Resolved firmographic context for the researched company.
    - `name` string, nullable — Canonical company name.
    - `domain` string, required — Echo of the requested domain (normalized).
    - `linkedin_slug` string, nullable — LinkedIn company slug (lowercased).
    - `employee_count` integer, nullable — Estimated current employee count, if known.
    - `stage` string, nullable — Funding stage label (e.g. 'Series B', 'Public', 'Acquired').
    - `industry` string, nullable — Primary industry tag.
    - `location` string, nullable — HQ city/region/country string.
    - `description` string, nullable — Short company description from the companies index, truncated.
  - `activity` ActivityFinding — Result of the activity-question stage.
    - `question` string, required — Echo of the activity question.
    - `qualified` boolean, required — True iff the company's recent job-posting activity matches the question with high enough signal to qualify.
    - `answer` string, required — Natural-language answer (1-3 sentences) summarizing the verdict and the key evidence the LLM relied on.
    - `confidence` string, nullable — LLM-reported confidence label, typically 'low' / 'medium' / 'high'. May be empty when the qualifier returned a structured-recipe verdict without an explicit confidence.
    - `jobs_searched` integer, required — Number of recent job postings considered before LLM relevance scoring.
    - `jobs_matched` integer, required — Number of job postings the LLM judged relevant.
    - `sources` JobSource[] — Up to ~10 matching job postings used as evidence, ordered by recency.
      - `title` string, required — Job title as posted.
      - `url` string, nullable — Canonical URL to the job posting.
      - `location` string, nullable — Posted location string.
      - `created` string, nullable — ISO timestamp of when the posting first appeared in the index.
      - `why_relevant` string, nullable — Short LLM-generated explanation of why this posting is evidence for the activity question.
    - `elapsed_ms` integer, required — Stage latency in milliseconds.
    - `error` string, nullable — Set when the stage failed mid-flight (e.g. company missing from the jobs index). `qualified` is False and `sources` is empty in this case.
  - `qualifier` QualifierFinding — Result of the people-qualifier stage.
    - `question` string, required — Echo of the qualifier question.
    - `qualified` boolean, required — True iff the company's team matches the criteria.
    - `answer` string, required — One-sentence natural-language summary of the verdict - suitable for inline use in a CRM note or alert.
    - `detailed_reasoning` string, required — Longer LLM-written reasoning: which signals matched, which didn't, and any caveats. May reference specific people.
    - `people_evaluated` integer, required — Number of LinkedIn profiles the LLM evaluated (after department / seniority pre-filtering).
    - `sources` PersonSource[] — Profiles the LLM cited as matching the criteria. Empty when `qualified` is False or the criteria was about absence rather than presence.
      - `name` string, nullable — Full name from the profile.
      - `title` string, nullable — Current job title.
      - `linkedin_url` string, nullable — Canonical LinkedIn profile URL.
      - `seniority` string, nullable — Precomputed seniority bucket: FOUNDER, CXO, PARTNER, VP, HEAD, DIRECTOR, MANAGER, SENIOR, ENTRY, INTERN.
      - `department` string, nullable — Precomputed department bucket: ENGINEERING, PRODUCT, DATA, SALES, MARKETING, OPERATIONS, FINANCE, HR, LEGAL, EXECUTIVE, CUSTOMER_SUCCESS, SECURITY, IT, OTHER.
      - `started_on` string, nullable — ISO start date of the person's current role (populated only when the qualifier question implied tenure / recency, e.g. 'joined in the last 90 days').
    - `elapsed_ms` integer, required — Stage latency in milliseconds.
    - `error` string, nullable — Set when the stage failed mid-flight (e.g. company has no people indexed). `qualified` is False and `sources` is empty in this case.
  - `credits_consumed` integer — Credits charged for this call. Charge is recorded in the background after the response is sent. 0 when the call failed before producing any findings.
  - `latency_ms` integer, required — End-to-end request latency in milliseconds.

## Other responses

- `400` — Insufficient credits to run the call.
- `404` — Domain could not be resolved to a company in the OpenFunnel companies index. Check the domain or try the primary corporate domain.
- `422` — Request validation error (e.g. neither `activity_question` nor `qualifier_question` provided, invalid domain format, out-of-range `timeframe_days`).

---

[API](https://skmtc.net/openfunnel/apis/openfunnel-agent-primitives.md) · [All operations](https://skmtc.net/openfunnel/apis/openfunnel-agent-primitives/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/openfunnel/openfunnel-agent-primitives/revisions/68cc95acb1f2/schema)
