---
title: "A quick overview of a person: career, likely challenges, and angles"
method: POST
path: "/v1/research/person"
tags: ["Research"]
---

# A quick overview of a person: career, likely challenges, and angles

`POST /v1/research/person`

## Request

- Base URL: `https://api.trayo.ai`
- URL: `https://api.trayo.ai/v1/research/person`
- Auth: HTTP bearer

**Required permissions:** `research:trigger`.

Given a person — any person, by their LinkedIn profile, or a person in your workspace — return a short stakeholder overview: their career progression, `professionalInsights` tied to what your workspace sells, and the `challenges` they are likely up against.

Returns an overview in the same response, typically within 2–3 seconds. The server stops after 120 seconds by default with `503 research_failed`. Set your client timeout above 120 seconds and retry once on `retry_later`. Each call generates a new answer. It saves nothing to your workspace. The company or person does not need to be in your workspace. This is the same quick overview the Trayo app shows. Public REST and MCP usage is recorded without daily usage caps, workspace quota cutoffs or lookup quotas for now. Workspace request pacing still applies. 

**How it works.** A workspace person's existing match is read; without one, their LinkedIn username is looked up, which is also what a profile sent as `linkedinUrl` does. `person.resolvedBy` says which. The profile Trayo holds — headline and work history — informs the overview together with your workspace's `solutions` and stakeholder definition (`GET /v1/workspace`).

**`grounded`.** Ungrounded, the default: no web search, and the overview is built from the profile Trayo holds alone, so `personalInsights`, `companyIntel`, `location` and `education` are empty and `challenges` are inferences. Grounded, Trayo searches the web for timely company intel and personal facts — slower (5–15 seconds rather than 2–3), and it occasionally fails to answer (`503 research_failed`; retry once). Both modes draw on the quota the same way. A web fact Trayo could not source is withheld rather than stated, so an empty `location` means "could not be sourced", not "unknown". **Source links are not published** — the requirement to have one gates what is said, and the link itself is not returned, in `research` or in `reportMarkdown`. Treat a grounded overview as a lead to check, not as sourced evidence.

**Compared with `POST /v1/stakeholders/search`.** That finds WHO at a company fits a definition; this says what to know about ONE person you already have in hand — a stakeholder-search result's `linkedinUrl` goes straight into `person.linkedinUrl` here.

## Request body

- PersonResearchBodyDto
  - `person` object, required
    - `personId` string, uuid: A person in your workspace, on its own. Their existing match is read when they have one; otherwise their LinkedIn username is looked up, so a person you have only just imported still works when the profile is in our data.
    - `linkedinUrl` string: The person's LinkedIn profile — the `linkedinUrl` a find result or a stakeholder-search result carries, copied through (`https://www.linkedin.com/in/jane-doe`); the bare handle (`jane-doe`) is accepted too. Does not have to be in your workspace.
  - `grounded` boolean: Off by default: no web search, and the overview is built from the profile Trayo holds alone — fast and reliable. `true` adds web search to recover timely company intel and personal facts: slower (5–15 s rather than 2–3 s), and Trayo occasionally fails to answer in this mode (`503 research_failed`, retry once). Both modes draw on the workspace's usage quota the same way. What it finds must be sourced to be said, but the sources are not published — treat the result as a lead to check rather than as evidence.

## Response `200`

The overview, with the person as resolved.

- PersonResearchResponseDto
  - `person` object, required
    - `fullName` string, required: The person as Trayo records them.
    - `linkedinUrl` string, nullable, required: Their LinkedIn profile, when Trayo has it.
    - `photoUrl` string, nullable, required: The person's profile photo, as a directly loadable image URL. Null when Trayo holds no real photo for them — show initials then. Every route that returns a person carries this field under this name.
    - `location` object, nullable, required: Where the person says they are on their LinkedIn profile. Usually their home metro area, but it is the person's own statement, not a verified address, and it is not where their company is headquartered. Null when Trayo has no location for them. From the profile Trayo holds, in every mode. Not the same field as `research.location`, which is a web fact stated only when `grounded` is on and Trayo could cite it.
      - `city` string, nullable, required: City, e.g. `San Diego`. Null when the profile names only a region or metro area.
      - `state` string, nullable, required: State, province or region, e.g. `California`.
      - `country` string, nullable, required: ISO 3166-1 alpha-2 country code, e.g. `US`.
      - `raw` string, nullable, required: The location as written on the profile, e.g. `Greater San Diego Area`. Usually present; the only populated field when the profile names just a metro area.
    - `companyDomain` string, nullable, required: The website domain of the employer named in research.company, for displaying its logo. Null when Trayo has no matching employer domain.
    - `company` object, nullable, required: The employer of the person's current role on the profile Trayo holds. Usually the company `research.company` names, which is free text. Null when the profile shows no current role.
      - `companyId` string, nullable, required: Trayo's id for this employer. Send it back as `company.companyId` (company research, stakeholder search, job-change search) to name this exact company. Null when Trayo holds no company record for the employer: `name` is then the free text from the profile, and looking it up by name will usually miss. Search for it with a find companies `query` instead.
      - `name` string, nullable, required: The employer as the profile names it.
      - `website` string, nullable, required: The employer's website, when Trayo holds a record for it.
      - `linkedinUrl` string, nullable, required: The employer's LinkedIn page, when Trayo holds a record for it.
    - `personId` string, uuid, nullable, required: The person this call was scoped to, when you sent one.
    - `accountId` string, uuid, nullable, required: That person's account in your workspace, when they have one.
    - `resolvedBy` 'stored_match' | 'linkedin', required: `stored_match`: the workspace person's existing match. `linkedin`: the LinkedIn profile, looked up by its handle.
  - `grounded` boolean, required: Whether web-search grounding was used for this overview.
  - `research` object, required: The overview as structured fields. `reportMarkdown` is the same content rendered for display. Neither field carries source links: a grounded fact Trayo could not source is withheld rather than stated, and the link behind one it could source is not returned.
    - `fullName` string, required
    - `title` string, nullable, required
    - `company` string, nullable, required
    - `careerProgression` string[], required: Notable roles, oldest first, each as `Role at Company (start – end)`.
    - `professionalInsights` object[], required: Their role and trajectory, tied to how what your workspace sells could be relevant.
      - `label` string, required: A short categorisation of the point ("Acquisition", "Analyst Recognition", "Enterprise Scale") — what you can group or scan by. Not a restatement of `text`.
      - `text` string, required: The point itself.
    - `challenges` object[], required: What they are likely up against. Inferred from role, seniority and industry when `grounded` is off; evidenced and cited when it is on.
      - `label` string, required: A short categorisation of the point ("Acquisition", "Analyst Recognition", "Enterprise Scale") — what you can group or scan by. Not a restatement of `text`.
      - `text` string, required: The point itself.
    - `personalInsights` object[], required: Only ever filled when `grounded` is on; empty otherwise.
      - `label` string, required: A short categorisation of the point ("Acquisition", "Analyst Recognition", "Enterprise Scale") — what you can group or scan by. Not a restatement of `text`.
      - `text` string, required: The point itself.
    - `companyIntel` object[], required: Timely, cited facts about their company a seller should know now. Only ever filled when `grounded` is on; empty otherwise.
      - `label` string, required: A short categorisation of the point ("Acquisition", "Analyst Recognition", "Enterprise Scale") — what you can group or scan by. Not a restatement of `text`.
      - `text` string, required: The point itself.
    - `location` string, nullable, required: Present only when `grounded` is on AND Trayo could cite a source for it. An uncited location is withheld rather than guessed, so null means "could not be sourced", never "lives nowhere". The source itself is not published.
    - `education` string, nullable, required: Same rule as `location`: sourced or withheld.
  - `reportMarkdown` string, required: The same overview rendered as Markdown, ready to show — headings and bullet points. `research` is the structured form of it. Carries no source links; see the note on `research`.
  - `generatedAt` string, date-time, required: When this overview was generated — always this call; nothing is stored.

## Other responses

- `400`: `person` with neither or both of `personId` / `linkedinUrl`; a `linkedinUrl` that is a LinkedIn URL but not a profile; `grounded` not a boolean; or an unknown field. `param` names it. `validation_failed` (`retry: fix_input`) — The request did not pass input validation: a missing or malformed body field, a bad `limit`, a path parameter that is not a UUID, a `signalKey` that already exists or is reserved. `message` lists every failed constraint; `param` names the first offending field when one can be identified.
- `401`: Missing, malformed, or incorrect bearer token. Every way authentication can fail returns the identical body, so do not try to infer which one it was, and do not repeat the call with the same key. `unauthorized` (`retry: do_not_retry`) — Missing, malformed, or incorrect bearer token. Identical body for every cause.
- `402`: `quota_exceeded` (`retry: do_not_retry`) — This workspace has insufficient quota for this operation. Retrying or changing the request does not help; raise the quota or wait for it to renew.
- `403`: The key grant and its owner's current workspace role must both include every scope required by the operation. `insufficient_scope` (`retry: do_not_retry`) — Scope denial details distinguish missing_key_scope (use a key granting every missing scope), owner_permission_limit (the owner needs the missing workspace permissions), and invalid_key_scope (replace the key with supported scopes). Membership and undeclared-route denials remain generic.
- `404`: `person_not_found` (`retry: do_not_retry`) — No such person in your workspace. Another workspace's person is indistinguishable from a non-existent one. `person_profile_not_found` (`retry: do_not_retry`) — We hold no public profile for this person — the LinkedIn profile is not in our data, or the person in your workspace carries neither a match to it nor a LinkedIn username. Retrying will not help. Send the person's LinkedIn profile URL if you sent a `personId`. Distinct from `person_not_found`, which means there is no such person in your workspace at all.
- `429`: Every route is rate limited per workspace across REST, MCP, all keys and OAuth users, `GET /v1/whoami` included, so a balance poll competes with the work it is watching. For `rate_limited`, wait `Retry-After` seconds (also `details.retryAfterSeconds`), then send the same request once; do not retry sooner. `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` describe the workspace's bucket, and they come back on successful responses too: read them and slow down BEFORE you are refused. A burst from one network can also be refused before the key is checked; that 429 carries `Retry-After` and no `RateLimit-*` headers. `rate_limited` (`retry: retry_later`) — The workspace or source network exhausted its request budget for one bucket. `details.retryAfterSeconds` (and `Retry-After` on REST) is the wait: wait that long, then retry the same request once. Do not poll or retry sooner; an early retry is refused again. If you cannot wait, stop and tell the user when to retry; do not fill the wait with other calls. `details.limit` and `details.bucket` name the exhausted budget. When present, RateLimit-* headers describe the active budget and reset. `quota_daily_limit` (`retry: retry_later`) — The workspace daily usage limit blocks new work. Wait for recent usage to leave the 24-hour window, or contact support; changing scopes does not help.
- `500`: Something failed on our side. Nothing about the request needs changing. `internal_error` (`retry: retry_later`) — Something failed on our side. `message` never describes it — quote `requestId` when reporting. Retry with exponential backoff and GIVE UP after a few attempts: `retry_later` here does not mean the failure is known to be transient, only that nothing about your request needs changing. A request that fails this way repeatedly will not start working.
- `503`: `research_not_configured` (`retry: do_not_retry`) — Company and person research are unavailable. Retrying will not help. Contact support. `research_failed` (`retry: retry_later`) — A research route failed or ran past the time budget. Nothing was written. Send the same request again shortly.

---

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