Skip to main content
POST
/
api
/
deep-research
/
people
Create a person and kick off research
curl --request POST \
  --url https://app.puffle.ai/api/deep-research/people \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "firstName": "<string>",
  "lastName": "<string>",
  "company": "<string>",
  "linkedinUrl": "<string>"
}
'
{ "person": { "id": "11111111-1111-1111-1111-111111111111", "user_id": "22222222-2222-2222-2222-222222222222", "first_name": "", "last_name": "", "linkedin_url": "https://www.linkedin.com/in/janesmith", "role": null, "company": null, "company_domain": null, "photo_url": null, "status": "running", "last_enriched_at": null, "created_at": "2026-04-21T17:55:00Z", "updated_at": "2026-04-21T17:55:00Z" } }

Overview

Creates a new deep_research_people row and queues the deep-research-enrich Trigger.dev task in the same request. The row starts at status: "running" and transitions to complete (or failed) minutes later as the pipeline finishes its multi-hop web search plus LLM synthesis. Two input modes are accepted, and you must pick exactly one:
  • LinkedIn URL — pass linkedinUrl containing linkedin.com/in/. The pipeline derives identity from the profile; firstName, lastName, and company are ignored.
  • Name + company — pass all three of firstName, lastName, and company. Every field is required; the pipeline resolves the company domain itself.
Costs 1 DEEP_RESEARCH credit, charged only after the Trigger.dev task is successfully queued. If dispatch fails, the row is reset to idle and no credit is charged. This is the internal UI-facing entry point. Partner-authenticated callers should use createResearchV1.

AI agent notes

Async via Trigger.dev. The 201 response means the task is queued, not that the report is ready. Follow the polling pattern below.Polling sequence:
  1. createDeepResearchPerson returns 201 with person.id and person.status: "running".
  2. Call getDeepResearchReport with that id every 30 seconds.
  3. Stop when report is non-null (pipeline complete) or when re-reading the person via listDeepResearchPeople shows status: "failed".
Idempotency. The DB has a unique constraint on identity; resubmitting the same person returns 409 with error: "This person has already been added". Read the person back via listDeepResearchPeople instead of retrying.Credit gating. A 402 response carries required and balance — surface these to the human and do not retry until credits are topped up.Input mode. Pick ONE. Passing neither (or only a partial name + company trio) returns 400.

Authorizations

Authorization
string
header
required

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Body

application/json

Provide EITHER linkedinUrl OR the full trio firstName + lastName + company. Every other combination returns 400.

firstName
string

Required when using the name + company form. Ignored when linkedinUrl is provided.

Minimum string length: 1
lastName
string

Required when using the name + company form. Ignored when linkedinUrl is provided.

Minimum string length: 1
company
string

Required when using the name + company form. Stored as-is — the pipeline resolves the actual domain.

Minimum string length: 1
linkedinUrl
string

Full LinkedIn profile URL (must contain linkedin.com/in/). When provided, the name + company fields are optional; the pipeline derives identity from the LinkedIn profile.

Response

Person created and enrichment task queued. Poll getDeepResearchReport for the final report.

person
object
required

A person tracked for deep research. The row is created first; enrichment + report generation happen asynchronously in a Trigger.dev pipeline.