---
title: "Ask a question about a person"
method: POST
path: "/v1/people/{id}/ask"
tags: ["People"]
---

# Ask a question about a person

`POST /v1/people/{id}/ask`

## Request

- Base URL: `https://api.trayo.ai`
- URL: `https://api.trayo.ai/v1/people/{id}/ask`
- Auth: HTTP bearer

**Required permissions:** `people:read`.

**Derived-context capability: people:read includes derived briefing context.** For this operation, that scope permits the person record, stored contacts, notes, list membership, synthesis and stakeholder reports, the linked account and its stored research, attached events and rationale, sequences and outcomes, sent emails, composed messages, the LinkedIn profile and posts, and workspace solution and stakeholder criteria. Answers may include facts from this derived context. No additional source scopes are required here, including when the owner role grants only people:read. The key and its owner's current workspace role must both allow people:read. Losing unrelated owner permissions does not block this operation. GET /v1/whoami reports the key's current effective scopes in key.effectiveScopes. Direct account and workspace reads still require accounts:read, and direct event reads events:read. This capability grants no writes or new research. Sources remain limited to the existing person dossier and workspace context; the receipt describes which records informed the answer, subject to existing limits.

Ask anything about one person and get an answer grounded in everything Trayo holds on them. **Synchronous and read-only**: the answer comes back in one response, typically in 5–15 seconds (the server gives up at 120 seconds by default with `503 person_ask_failed`, so set your client timeout above that and retry once on `retry_later`). The answer is not saved. Public REST and MCP usage is recorded without daily usage caps, workspace quota cutoffs or lookup quotas for now. Workspace request pacing still applies. **What goes into the answer.** Two kinds of source, rendered into one dossier with sane limits: the person's **Trayo record** — the contact details on record, the account and its research, the signals (events) the person is attached to and Trayo's reason for each, every sequence composed for them with each step's send/open/reply outcome, emails sent, messages composed, lists, any stored synthesis or stakeholder report, and the `note` you wrote — plus the **LinkedIn profile** from Trayo's own data: headline, about, work history with dates, and the person's recent posts.

**`intent` steers the answer.** Say what you will do with it — "qualifying whether to sequence them, I need a verdict and the reason", "drafting a first email, I need hooks I can cite", "briefing a rep before a call" — and the focus, depth and form follow. Without it you get a direct, concise answer.

**No confidence score is returned — read `sources` to see what the answer is grounded in.** It is a receipt of the records used: how many signals, whether any outreach exists, whether the LinkedIn profile was found. `sources.linkedin.status` is `not_linked` when the person has no LinkedIn identity in Trayo (set `linkedinUsername` with `PATCH /v1/people/{id}` and ask again), `not_found` when Trayo has no record for that profile, and `unavailable` when that data could not be reached this time — the answer then rests on the Trayo record alone. `gaps` names what was missing.

**Grounded, not researched.** Trayo answers from these records only: no web search, no guessing. A fact that is not on record is named as unknown. To put more on record first: `POST /v1/people/{id}/enrich` finds an email or phone, and `POST /v1/discoveries` puts events and the people attached to them on the account's record.

## Path parameters

- `id` string, uuid, required

## Request body

- AskPersonBodyDto
  - `question` string, required: The question, in plain prose. Anything a rep might ask a colleague who knows the person: "Is she the right buyer for us?", "What have we sent him and did he respond?", "How long has she been in this role, and where was she before?", "Give me three talking points for a first call."
  - `intent` string: What you will do with the answer — one or two sentences. It steers focus, depth and form. "Qualifying whether to add them to a sequence; I need a yes/no with the reason" gets a verdict; "Drafting a first-touch email; I need specific hooks I can cite" gets hooks with their sources; "Briefing a rep before a call" gets a short brief. Omit it for a direct, concise answer.

## Response `200`

The answer, what was missing from the record (`gaps`), and a receipt of the records used (`sources`).

- AskPersonResponseDto
  - `personId` string, uuid, required: The person this answer is about — the id you sent in the path.
  - `question` string, required: Your question, as answered.
  - `intent` string, nullable, required: Your intent, as applied; null when you sent none.
  - `answer` string, required: The answer, in Markdown. Grounded in the dossier only: a fact the dossier does not hold is named as unknown, never guessed.
  - `gaps` string[], required: What was missing that would have made the answer better, up to five items — e.g. "no LinkedIn profile linked", "no outreach on record". Empty when nothing material was missing.
  - `sources` object, required: A receipt of what the answer was grounded in. Read it before trusting an assured-sounding answer, and to decide what to look up or link before asking again.
    - `trayo` object, required: Which workspace records went into the prompt.
      - `contact` object, required
        - `email` boolean, required: An email is on record (written or found).
        - `phone` boolean, required: A found phone is on record.
      - `account` boolean, required: The person is linked to an account and its record was included.
      - `accountResearch` boolean, required: That account carries research Trayo has gathered for it, and it was included.
      - `events` integer, required: Signals (events) the person is attached to, included.
      - `sequences` integer, required: Sequences composed for the person, included.
      - `emailsSent` integer, required: Emails sent to the person, included.
      - `composedMessages` integer, required: One-off messages composed and copied for the person, included.
      - `lists` integer, required: Lists the person is on, included.
      - `synthesis` boolean, required: A stored person synthesis exists and was included.
      - `stakeholderReport` boolean, required: A stored stakeholder report exists and was included.
      - `note` boolean, required: The `note` you wrote on the person was included.
    - `linkedin` object, required: Whether the LinkedIn profile from Trayo's own data went into the prompt, and how much of it.
      - `status` 'found' | 'not_found' | 'not_linked' | 'unavailable', required: `found`: the profile was included. `not_found`: the person is linked to LinkedIn but Trayo has no record. `not_linked`: the person has no LinkedIn identity in Trayo — set `linkedinUsername` with `PATCH /v1/people/{id}`. `unavailable`: the data could not be reached this time; the answer used Trayo data only.
      - `matchedBy` 'stored_id' | 'linkedin_username', nullable, required: How the profile was found: `stored_id`, the id Trayo stored when it collected the person; `linkedin_username`, a lookup by the LinkedIn handle. Null unless `status` is `found`.
      - `linkedinUrl` string, nullable, required: The profile URL, when found.
      - `experience` integer, required: Work-history entries included.
      - `posts` integer, required: Recent posts included.

## Other responses

- `400`: `id` is not a UUID; `question` missing, blank, or over 2000 characters; `intent` blank or over 2000 characters; 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.
- `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`: `person_ask_not_configured` (`retry: do_not_retry`) — Questions about a person are unavailable. Retrying will not help. Contact support. To read the saved person record, use `GET /v1/people/{id}`. `person_ask_failed` (`retry: retry_later`) — The answer failed, came back unusable, or ran past the time budget. Nothing was written. Send the same request again shortly. A LinkedIn-profile failure is NOT this error: the answer proceeds on the Trayo record and `sources.linkedin.status` says `unavailable`.

---

[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)
