---
title: "A quick overview of a company: what it does, why now, and fit"
method: POST
path: "/v1/research/company"
tags: ["Research"]
---

# A quick overview of a company: what it does, why now, and fit

`POST /v1/research/company`

## Request

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

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

Given a company — any company, by its website (or LinkedIn page or name), or an account in your workspace — return a short overview: the firmographic facts, `whyNow` hooks drawn from its recent LinkedIn updates, and `fit` against what your workspace sells.

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.** The company is identified exactly as the Trayo app identifies one — LinkedIn page, then website (following its redirect), then name, against the same data; `company.resolvedBy` says which key matched. **Read `company.name` and `company.website` back before you act on the overview** — a guessed handle or a common name can match a different company, and the overview will faithfully describe that one. The record Trayo holds — description, industry, size, tech stack and its recent company updates — informs the overview together with your workspace's `solutions` and stakeholder definition (`GET /v1/workspace`), which is where `fit` comes from. No web search: the "why now" signal is our own recent updates, so `whyNow` is empty when the company has posted nothing substantive.

**Nothing is written.** Adding a company as an account is what puts it on record — background work that persists the company, its people and its events. This is a read: seconds, nothing stored, any company, whether or not you hold it.

## Request body

- CompanyResearchBodyDto
  - `company` object, required
    - `companyId` union: Trayo's own id for the company, as a number or numeric string: the `companyId` a post, post-engager or person-research result carries, or the `id` of a company find result. Not a workspace account UUID. When sent, it is the key: the company is read by this id and nothing else is tried, so an id Trayo does not hold is `company_not_found`. A `name`, `website` or `linkedinUrl` sent beside it is not used.
      - integer
      - string
    - `accountId` string, uuid: An account in your workspace, on its own. Its existing match is used when it has one; otherwise the account is resolved from its website, LinkedIn handle and name exactly as the app would, so an account we have not yet matched still works when the company is in our data.
    - `website` string: The company's own website, as a URL or a domain (`https://zapier.com`, `zapier.com`). The strongest key after the LinkedIn page: it is matched against our data, then, on a miss, followed through a cross-domain redirect, so a legacy or vanity domain that forwards to the current site still resolves. Sent on its own as the bare site (no path or subdomain), a data or social platform's domain names the platform itself (`zoominfo.com` is ZoomInfo); a page on such a site (`linkedin.com/company/acme`) is not a company website. When our data files the company under a different website, a website sent on its own is also tried as the LinkedIn page it spells (`gong.io` as `gong-io`), accepted only for an established company whose name matches the domain. An alias that does not forward to the company's current site is a miss: send the current website, the LinkedIn page or the name. Does not have to be in your workspace.
    - `linkedinUrl` string: The company's LinkedIn page — the `linkedinUrl` a find result or a person row carries, copied through (`https://www.linkedin.com/company/zapier`); the bare handle (`zapier`) is accepted too. Tried first when present, and the most precise key there is. Showcase pages are not company pages: send the company's own. Copy the handle from a Trayo result or the company's own site rather than guessing it from the name — a slug that belongs to a different company resolves to that company.
    - `name` string: The company name. Tried last and the weakest key, since a common name can match the wrong company — send the website when you have it. A name-only lookup finds only companies Trayo holds under that name; an employer that a Trayo person row names with `companyId: null` has no company record, and its name will usually miss.

## Response `200`

The overview, with the company as resolved.

- CompanyResearchResponseDto
  - `company` object, required
    - `companyId` string, required: Trayo's id for the company. Send it back as `companyId` to name this exact company again.
    - `name` string, required: The company as Trayo records it.
    - `website` string, nullable, required: The company's website domain as Trayo files it.
    - `linkedinUrl` string, nullable, required: The company's LinkedIn page, when Trayo has it.
    - `accountId` string, uuid, nullable, required: The workspace account for this company: the one you sent, or the account that already holds its website or LinkedIn page. Null when the company is not in your workspace.
    - `resolvedBy` 'account_match' | 'company_id' | 'linkedin' | 'website' | 'redirect' | 'name', required: Which key identified the company — the same resolver the Trayo app uses, in its order. `account_match`: the account's existing match. `linkedin`: the LinkedIn page, as sent or as spelled by a website sent on its own. `website`: the website domain. `redirect`: the website, after following a cross-domain redirect to the company's current site. `name`: the name, the weakest key. A match is exact on the key you sent, which is only the company you meant if the key was. A slug or name can belong to a different company than the one you mean, and the answer will then describe that company. Read `name` and `website` back before acting, and prefer `website` as the key — a domain resolves to whoever owns it.
    - `nameMatch` 'exact' | 'approximate', nullable, required: Set only when `resolvedBy` is `name`, null otherwise. `exact`: the company's name equals the name looked up once legal suffixes (Inc, GmbH, Ltd) are dropped. `approximate`: the closest name Trayo holds, which can be a different company ("CR Management Consulting" answered with "m3 management consulting"). Check an `approximate` match against what you know of the company before using it, and leave it out when it does not fit.
  - `research` object, required: The overview as structured fields. `reportMarkdown` is the same content rendered for display. Neither field carries source links.
    - `displayName` string, required
    - `oneLiner` string, required: One sentence on what the company does.
    - `industry` string, nullable, required
    - `hqLocation` string, nullable, required
    - `employeeCount` string, nullable, required: A count or a band, as Trayo states it.
    - `foundingYear` string, nullable, required
    - `whyNow` object[], required: Timely hooks drawn from the company's recent LinkedIn updates — launches, funding, M&A, leadership changes, hiring. Empty when Trayo holds no substantive recent update, which is an honest answer, not an error.
      - `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.
    - `fit` object[], required: Why this company is a target for what your workspace sells, inferred from its industry, size and tech stack against your `solutions`.
      - `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.
  - `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`: `company` with neither an `accountId` nor any of `website` / `linkedinUrl` / `name`, or with `accountId` alongside them; a `linkedinUrl` that is a LinkedIn URL but not a company page; 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`: `account_not_found` (`retry: do_not_retry`) — No such account in your workspace. Another workspace's account is indistinguishable from a non-existent one. `company_not_found` (`retry: do_not_retry`) — We could not identify this company — not from the account's own record, nor from a LinkedIn page, website (including its redirect), or name — so there are no people to search. Retrying will not help. Send the company's own website if you did not; for any company, `POST /v1/find` on the contacts lane searches the web instead. From `POST /v1/find` with `filters.companyWebsites`, this means NO website resolved and nothing was searched; the message names every website. For one that is ambiguous (several possible companies on the domain) it also names the candidates and their ids: send the right id as `filters.companyIds`. When only some websites fail, the search runs over the rest and lists the others in `unresolvedWebsites`.
- `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)
