---
title: "Find companies or people not yet in your workspace"
method: POST
path: "/v1/find"
tags: ["Find"]
---

# Find companies or people not yet in your workspace

`POST /v1/find`

## Request

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

**Required permissions:** `body.target` equals `people`: `people:read`; `body.target` equals `companies`: `accounts:read`. Other or missing values: no additional scopes; request validation still applies.

Search for companies and people you do not already have. It never writes: you get candidates, and you import the ones you want.

**For explicit criteria, use `mode: direct` with `filters`.** Reads stored company and people records with no sentence interpretation or external search. `query` is rejected. Each call returns one page of at most 100 records; follow `nextCursor` to continue. Company records without websites are kept and can be read again by `filters.companyIds`. A direct companies search can combine company criteria with `filters.title` to return companies with a matching current employee, once per company. People results include their matching current company. A company reaching `perCompany` sets `truncated: true` with a note because additional matching people may exist. Omitting mode preserves the usual search behavior below.

**Say what you want in one of two ways, or both.**

- `query` — a sentence. Trayo interprets it and ranks results by relevance. `interpreted` in the response tells you what it read out of the sentence, in the same `filters` shape you can send back. One ranked page of at most 50; no cursor.
- `filters` — exact attributes: industry, headcount, headquarters, funding stage, founded year, technologies, legal form, and for people a title. Applied exactly as sent, never interpreted, never fail-open: a company with no value for a filtered attribute is excluded. Paginated. A companies search without a title can use only country, founded year, technology or company type: it checks a bounded set of the largest companies and reports `led_by_headcount`, so its matches are not exhaustive. A people search or a company search with a title needs named companies or `industries`, `hq.cities`, `hq.states`, `fundingStages` or a `headcount` band. A funding stage selects a set on its own. Read `GET /v1/find/industries` before your first industry filter, because the values are a closed taxonomy.
- both — the sentence ranks, the filters decide. Rows Trayo cannot check are dropped.

**`target: people` returns people with their current company.** `"founders at seed-stage software companies"` is `{"target": "people", "filters": {"industries": ["software development"], "fundingStages": ["seed"], "title": {"any": ["founder"]}}}`. Company filters describe the employer; `title` describes the person. With a `query`, a people search takes `title` only.

**Searching inside companies you already know.** Name them instead of describing them: `filters.companyWebsites` takes their websites (`["stripe.com", "https://www.acme.example"]`), the form you already have without asking this API for anything. It replaces every other company filter, and takes no `query` beside it. (`filters.companyIds`, which takes an `id` a previous search returned, is the older form of the same thing and replaces it in turn; websites are what to reach for.) Each website resolves to at most one company, and `resolvedWebsites` in the response says which: several company profiles can share a domain, so a website resolves only to a profile whose name matches the domain; check it before trusting the page. A website that does not resolve is left out of the search and listed in `unresolvedWebsites`, never dropped without saying so: the page covers only the websites in `resolvedWebsites`. `reason: "not_found"` means no company on that domain: fix the spelling, send the company's own domain, or describe that company with filters in a separate search. `reason: "uncertain"` means the domain carries several possible companies and none clearly owns it; its `alternatives` list them (id, name, headcount). Show them to the user, or search again with `filters.companyIds` set to the right alternative's id. When no website resolves at all, the answer is a `404 company_not_found` naming them.

**Reading the response.** `data` holds one row shape per target. `hasMore`/`nextCursor` page a filters-only search. `truncated` means more matched than this search can show — narrow, do not page deeper. `known: false` on a company means the sentence search found something Trayo does not hold. `state: alreadyAdded` means it is in your workspace; for "find N new companies", send `filters.excludeWorkspace: true` and those rows are left out, so the page fills with companies you do not have.

**Before re-running a thin search, read `interpreted`, `truncated` and `degraded`.** They say whether your criteria were enforced, whether the set is larger than reachable, and whether the search was incomplete — and a search that ran counts against the quota whether or not it was useful. Outside direct mode, every company row carries a `url`: rows with no website are dropped before the page is filled and counted in `droppedUnaddressable`, because `POST /v1/accounts` could not import them.

**Importing.** Companies: `POST /v1/accounts/batch` with `{name, url}` from each row (`url` is ready to send). People: `POST /v1/people/batch` with `{fullName, title, linkedinUrl}`; the company links by website when known. Contact details are a separate step: `POST /v1/contacts/lookup` with the `linkedinUrl`.

`target: companies` needs the `accounts:read` scope, `target: people` needs `people:read`. A search with a `query` draws on the workspace's search quota when it runs; a filters-only search does not.

## Headers

- `idempotency-key` string

## Request body

- FindRequestDto
  - `target` 'companies' | 'people', required: What to return: `companies` or `people`. People are returned with their current company.
  - `mode` 'direct': Use `direct` for a bounded search of stored company and people data with exact `filters`. No sentence interpretation or external search. Requires filters, rejects query, and returns one page of at most 100 records. Company records without websites are kept and can be searched again by companyIds. A direct title search checks at most 30 companies per request in `sort` order (largest by default, smallest with `smallest_first`): send at most 30 companyIds or companyWebsites at a time. Omit mode to retain the usual query or filters search.
  - `query` string: A sentence describing what to find. Trayo interprets it and ranks results by relevance; `interpreted` in the response says what it read. Omit it to search by `filters` alone, which is exact and paginated. Send both to rank by the sentence and keep only rows that pass the filters. On a people search, the company filters (`companyWebsites`, `companyIds`, `industries`, `headcount`, `hq` and the rest) are checked against each ranked person's current employer, which the row then carries: `companyWebsites` must be that employer's own website. A person whose employer fails them, or whose current employer Trayo cannot identify, is left out. The page is not refilled, and a `query_page_filtered` note counts who was left out. For a filters-only people search, omit `query` and use `mode: "direct"` with `filters.title` and named companies or an indexed company filter such as `industries` or `headcount`; country, founded-year, technology and company-type filters alone are insufficient beside a title. A title-free companies search can use those filters alone, checking a bounded set of the largest companies (`led_by_headcount`).
  - `filters` object: Exact attributes to match. Never interpreted, never fail-open. Alone, `industries`, `hq.cities`, `hq.states`, `fundingStages` and `headcount` select the set and the rest narrow it; a companies search without `title` may send only a founded year, company type, technology or place filter, then checks the largest companies (`led_by_headcount` in `notes`). With a `query`, any combination narrows the ranked results.
    - `industries` string[]: Exact values from the industry taxonomy — list them with `GET /v1/find/industries?contains=software`. "SaaS" is not a value; "software development" is. Case-insensitive, up to 99. Several values are a union. An uncapped page merges the same rows each value returns alone; when `truncated` is true, read the notes before treating the page as complete. When present, industries drive the indexed scan, unless `fundingStages` is sent: then each company is checked against them. Exact: a company with no value for this attribute does NOT match.
    - `headcount` object: Inclusive employee-count band, e.g. `{"min": 11, "max": 200}`. Either bound may be omitted. Selects a set on its own. In a filters-only search with no company attributes, or beside a title without an indexed company filter, send a saved workspace profile's `buyerProfile.sizeBand` (its max capped at 50,000) beside its mapped industries; with a partial profile, no mapped industry or no profile, send `{"min": 1, "max": 10000}`. Never `{"min": 1}` alone: the largest employers make common titles time out. Beside a title with only a founded year, technology or company type, fit the band to it (founded in recent years: `{"min": 1, "max": 50}`). A funding stage selects a set on its own; send no profile industries or band beside it. Without a title, founded year, technology and company type may go alone. With a country and no industries, pin its main cities or states: the band beside a country alone can time out. Exact: a company with no value for this attribute does NOT match.
      - `min` integer
      - `max` integer
    - `hq` object: Where the company is headquartered, not where any person is based. For where a person is based, use `filters.person` on a people search (`target: people`, no `query`).
      - `countries` string[]: Countries of the headquarters: ISO-2 codes (`["US", "DE"]`) or country names (`["United States", "Germany"]`), any case. A name is read as its ISO-2 code, and `applied` echoes the codes. Applied inside the scan, cheap, any number up to 60. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
      - `regions` string[]: Shorthand for a block of countries: `north_america`, `europe`, `asia_pacific`, `latin_america`, `middle_east`, `africa`. Expands to ISO-2 codes and merges with `countries`. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
      - `cities` string[]: Headquarters city, exact match, case-insensitive (`"san francisco"`). At most 20. Can select a set on its own. A bare city name is a NAME, not a place, and matches it everywhere on earth: `"dublin"` is Ireland and Ohio and California, `"newark"` is New Jersey and California. Pin one by writing `"city|state"`, `"city|state|country"` or `"city||country"` — `"newark|california"`, `"dublin||IE"`, `"richmond|virginia|US"`. The state is its full name and the country an ISO-2 code, each pinned city narrows on its own, and pinned and bare values mix freely. When a bare value turns out to span more than one place, the response says so in `notes`. Exact: a company with no value for this attribute does NOT match.
      - `states` string[]: Headquarters state or province, full name, case-insensitive (`"california"`, `"bavaria"`, not `"CA"`). At most 20. Can select a set on its own. There is no US-region shorthand; spell the states out. Applied alongside `cities` as a separate AND, so `cities: ["newark", "dublin"]` with `states: ["california", "ohio"]` admits all four pairings — write the pins into `cities` to avoid that. Exact: a company with no value for this attribute does NOT match.
    - `person` object: `target: people` without `query` only. Where the person says they are, from their LinkedIn profile location: not a verified address, and not the employer's headquarters (that is `hq`). `countries`, `states` and `cities` are each any-of, and they AND together. A person with no recorded value for a part you sent does NOT match. `states` and `cities` narrow the people at a set of companies: send them beside `companyWebsites`, `companyIds` or an indexed company filter (`industries`, `hq.cities`, `hq.states`, `fundingStages`, `headcount`). Applied before `perCompany`, so each company still returns its most senior people who match.
      - `countries` string[]: Countries the person is in: ISO-2 codes (`["US", "DE"]`) or country names (`["United States", "Germany"]`), any case. A name is read as its ISO-2 code, and `applied` echoes the codes. At most 60.
      - `states` string[]: State or province the person is in, full name, case-insensitive (`"california"`, not `"CA"`). At most 20.
      - `cities` string[]: City the person is in, whole name, case-insensitive (`"san diego"`). A person whose profile names only a metro area ("Greater San Diego Area") has no city and does not match one. A city name can exist in several places: send `countries` or `states` beside it. At most 20.
    - `fundingStages` string[]: The most recent funding round Trayo recorded, as a closed set: `pre_seed`, `seed`, `series_a`, `series_b`, `series_c`, `series_d_plus`, `private_equity`, `debt`, `post_ipo`, `grant`. Only a minority of companies carry funding data at all, so this keeps ONLY companies with a recorded round of that kind — an unfunded or unrecorded company never matches. It is a record, not a stage of life: an old or listed company can carry a stale `seed`. For "early-stage", pair it with `foundedYear` and `headcount`. With a `query`, the sentence path's notion of stage is checked against this record and loses: a company recorded as "Series B" that has since raised a Series C is dropped. Selects a set on its own, with or without a title. Beside it, `industries` are checked company by company: do not add profile industries the user did not ask for.
    - `foundedYear` object: Inclusive founded-year band, e.g. `{"min": 2018}`. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
      - `min` integer
      - `max` integer
    - `technologies` string[]: Technologies detected on the company's public web presence, any of, up to 20. Matched on the name with punctuation and case removed, so `"Amazon Web Services"`, `"aws"` and `"HubSpot"` are all valid. A web-stack scan: good for marketing, analytics and cloud tooling, blind to internal systems such as an ERP. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
    - `companyTypes` string[]: Legal form as the company describes itself, a closed set: `privately held`, `public company`, `nonprofit`, `educational`, `government agency`, `partnership`, `self-employed`, `self-owned`. `public company` includes wholly-owned subsidiaries of listed groups, not only the listed entity. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
    - `companyIds` string[]: **Reach for `companyWebsites` instead** — it names the same companies by something you already have. This is the older form of that filter, kept for callers holding ids: exact companies, by the `id` a companies search returned with `known: true`, at most 200. For `target: companies` it is a lookup of exactly those rows; for `target: people` it names whose people to search. Other company filters narrow only these companies (AND). Without `query`, `named_companies_filtered` notes identify companies the attributes exclude. With `query`, the ranked candidates are intersected with these IDs; this does not search every named company. Do not combine with `companyWebsites`.
    - `companyWebsites` string[]: The same thing as `companyIds`, named by something you already have: each company's own website — at most 100. `stripe.com`, `www.stripe.com` and `https://stripe.com/pricing` all name the same company; the scheme, `www.` and any path are ignored. Without `query`, each website is resolved to exactly one company Trayo holds, and the resolution is reported back in `resolvedWebsites` so you can see which company each one became. Websites that do not resolve are left out of the search and listed in `unresolvedWebsites`, so the answer can cover fewer companies than you named: read that list before you treat a short page as complete. A `not_found` one: check the spelling, send the company's own domain rather than a parent group's, a reseller's or a marketing microsite's, or describe the company with `filters.industries`, `filters.hq` or `filters.headcount` in a separate search. An `uncertain` one: the domain carries several possible companies and none was searched; show its `alternatives` to the user, or search again with `filters.companyIds` set to the right alternative's `id`. Only when NONE of the websites resolves is the answer a `404 company_not_found` naming them. Other company filters narrow only the resolved companies (AND), and `named_companies_filtered` notes identify companies they exclude. On a people search with `query`, websites instead match the ranked people's current employers directly: `resolvedWebsites` and `unresolvedWebsites` are null, and no matching employer yields an empty page rather than `company_not_found`. A `query_page_filtered` note counts people left out; the page is not refilled. Other company attributes also narrow the current employer (AND). Do not combine with `companyIds`.
    - `title` object: Current employee title. A filters-only people search requires `title.any` or `title.all`, even with `companyIds` or `companyWebsites`; no-title named-company sampling is unsupported. In `mode: direct`, a companies search may also use it: return each company with at least one matching current employee. Pair it with companyIds, companyWebsites or an indexed company filter; when the request names none, take them from the workspace profile (see `headcount`). A title is searched only at the first companies in `sort` order (in `mode: direct`, at most 30: largest first by default, the smallest with `smallest_first`) unless `notes` say `title_matched_first`; `company_fan_out_limit_reached` says more companies matched. Never report it as every company.
      - `any` string[]: Title terms, ANY of which must appear in the person's current title. A multi-word term matches as a phrase, with at most one word between the words: `"vp finance"` matches "VP Finance" and "VP of Finance", not "VP of Engineering, Finance partner". Punctuation and case are ignored: `"founder"` matches "Co-Founder & CEO". A single common word matches across every function (`"automation"` finds manufacturing and procurement automation too) — prefer two-word terms and use `exclude`. An acronym is matched LITERALLY and expands to nothing: `"cto"` does not match "Chief Technology Officer", and the same role is written both ways inside one company, so send both forms — `["cto", "chief technology officer"]`. At named companies (`companyIds`, `companyWebsites`) every term is searched again at each company, so a long list there can run out of time: send the forms the user means, not every variant.
      - `all` string[]: Title terms that must ALL appear. Combine with `any` freely.
      - `exclude` string[]: Title terms that disqualify a person (`["assistant", "intern"]`).
    - `excludeWorkspace` boolean: `target: companies` only. Default false. When true, companies already in your workspace are left out of `data` instead of coming back as `state: alreadyAdded`, so the page fills with companies you do not have. Use this for "find N new companies". "Already in your workspace" is decided exactly as `state` is. It is not a filter on its own: a filters-only search still needs a company filter. On a filters-only search a page can come back shorter than `limit` with `hasMore: true` when most matches were already yours — keep paging with `nextCursor`.
  - `sort` 'largest_first' | 'smallest_first': Filters-only. Page order by company headcount: `largest_first` (the default) or `smallest_first`. `smallest_first` needs `filters.headcount.min` of at least 1, because companies with no recorded headcount would otherwise come first. A search whose title is rare is ordered by company id instead and ignores `sort`; its pages carry a `title_matched_first` note. A search with a `query` is ranked by relevance and takes no `sort`.
  - `perCompany` integer: `target: people`, filters-only. People read per company before paging, most senior title first. Default 4, at most 25. Keeps one large employer from filling every page.
  - `limit` integer: Page size. Default 25, at most 100. A search with a `query` returns at most 50.
  - `cursor` string: The `nextCursor` from the previous page, sent back verbatim WITH THE SAME REQUEST. Filters-only searches page; a search with a `query` returns one ranked page and rejects a cursor.

## Response `200`

The search ran. An empty `data` array is a successful search that matched nothing — check `truncated`, `degraded` and `interpreted` before retrying.

- FindResponseDto
  - `target` 'companies' | 'people', required
  - `data` union, required: Companies or people, per `target`. Filters-only pages are ordered by company headcount (`sort`), and people within a company most senior first — except a search whose title is read first, which is ordered by company id and says so in `notes` (`title_matched_first`, or `title_matches_sampled` for a page checked against a sample of the title's roles). An empty array is a successful search that matched nothing, unless `truncated` is true.
    - object[]
      - `id` string, required: Identifies this row: the Trayo company id when `known` is true, otherwise an id unique to this response only. You do not have to carry it around — to look these companies up again, or to search their people, send `website` back as `filters.companyWebsites`. For a direct record without a website, send its id as filters.companyIds.
      - `name` string, nullable, required
      - `website` string, nullable, required: Primary website host, e.g. `stripe.com`. Send it back as `filters.companyWebsites` to search this company again, with no `query` beside it. Null when Trayo holds no website for it.
      - `url` string, nullable, required: `https://` + `website`, ready to send as `url` to `POST /v1/accounts`. Null when the website is unknown.
      - `logoUrl` string, nullable, required: The company logo URL, derived from `website`, so null when the website is unknown. Usable directly as an `<img src>` and needs no API key; it answers 404 when no logo resolves.
      - `linkedinUrl` string, nullable, required
      - `industry` string, nullable, required: The taxonomy value, as Trayo holds it.
      - `headcount` integer, nullable, required
      - `hq` object, required
        - `city` string, nullable, required
        - `state` string, nullable, required
        - `country` string, nullable, required: ISO-2.
      - `fundingStage` string, nullable, required: A `fundingStages` id when the most recent recorded round maps to one, else the value Trayo holds, verbatim. Null when no round is recorded.
      - `foundedYear` integer, nullable, required
      - `companyType` string, nullable, required: One of `companyTypes`, or null.
      - `description` string, nullable, required: The description Trayo holds, cut at 500 characters.
      - `why` string, nullable, required: Why this company matched the `query`, in Trayo's words. Null on a filters-only search.
      - `known` boolean, required: Whether this row is backed by a profile Trayo holds. Always true on a filters-only search. On a sentence search, false means the search found a company Trayo does not hold: the attributes are as the search reported them, `id` is not one of our own ids, and — because filters are exact — the row would not have survived any `filters`.
      - `state` 'selectable' | 'alreadyAdded', required: `alreadyAdded` means this record is already in your workspace.
    - object[]
      - `id` string, required: Trayo person id on a filters-only search; an id unique to this response on a sentence search.
      - `fullName` string, nullable, required
      - `title` string, nullable, required: Current title, as it is recorded for them.
      - `linkedinUrl` string, nullable, required: Their LinkedIn profile. Send it to `POST /v1/contacts/lookup`, `POST /v1/research/person`, or as `linkedinUrl` to `POST /v1/people`.
      - `linkedinUsername` string, nullable, required
      - `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. Filled on a filters-only search; null on a sentence search.
        - `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.
      - `company` object, nullable, required: Their current employer, with the attributes the company filters read, so a row can be checked against the request. Complete on a filters-only search, and on a sentence search sent with company filters, where it is the employer those filters were checked against. On a sentence search without them usually name-only, because a sentence search returns a company reference without a website.
        - `id` string, nullable, required: Trayo company id, when known.
        - `name` string, nullable, required
        - `website` string, nullable, required: Primary website host.
        - `logoUrl` string, nullable, required: The company logo URL, derived from `website`. Null when the website is unknown.
        - `industry` string, nullable, required
        - `headcount` integer, nullable, required
        - `hq` object, required
          - `city` string, nullable, required
          - `state` string, nullable, required
          - `country` string, nullable, required: ISO-2.
        - `fundingStage` string, nullable, required: A `fundingStages` id when the most recent recorded round maps to one, else the value Trayo holds, verbatim. Null when no round is recorded.
        - `foundedYear` integer, nullable, required
      - `state` 'selectable' | 'alreadyAdded', required: `alreadyAdded` means this record is already in your workspace.
  - `hasMore` boolean, required: A next page exists. People pages fill company by company, so a page can end before every matched company has been reached — keep paging until false. Always false on a sentence search, which returns one ranked page.
  - `nextCursor` string, nullable, required
  - `truncated` boolean, required: The result is or may be incomplete. On a sentence search: more candidates than the page. On a filters-only search: the scan reached its cap, possibly even on a full page, so the pages you can reach are only the companies reached within the cap, not all of the set — `notes` says when that cap was the per-value budget of a multi-value filter, and which values ran out. Either way the answer is to narrow, not to page deeper. In direct people searches, also true when an employer reached perCompany; additional matches may exist there, and raising perCompany can reveal them. Also true when the page was checked against a sample of the title's roles (`title_matches_sampled`).
  - `degraded` boolean, required: Part of this sentence search did not answer, so a thin result may not mean a genuine absence of matches. Always false on a filters-only search.
  - `droppedUnaddressable` integer, required: Company rows this search found and then dropped for carrying neither a website nor a `url`. `POST /v1/accounts` would accept such a row — `url` is optional there — but deduplication keys on the website, and a sentence match with no website at all is overwhelmingly a directory, a job board or an institution rather than a company. They are dropped on that quality ground, so every row left in `data` can be acted on. On a search with a `query` they are dropped BEFORE the page is filled, so the page still reaches its size; on a filters-only search the page has already been read at its size, so a dropped row shortens it and `hasMore` is unaffected. A high number against a thin `data` means the sentence matched directories, job boards and institutions rather than companies — name the kind of company you want. Always 0 for `target: people`, whose rows are addressed by `linkedinUrl`, and in mode: direct, which keeps company records without websites and identifies them by id.
  - `notes` object[], required: Everything about this answer that is not simply "here are the matches": a filter whose values were not all scanned to the end, a bare city name that turned out to mean several places, a sentence search that came back `degraded` because a data source it depends on did not answer. Empty on a search with nothing to report, which is the usual case. Read it before treating a short result as an absence of matches: it is where a short page says it was cut off, or thinned by an outage, rather than that nothing matched.
    - `code` 'filter_values_truncated' | 'city_spans_multiple_places' | 'search_degraded' | 'per_company_limit_reached' | 'title_matched_first' | 'title_matches_sampled' | 'search_time_limit_reached' | 'residual_scan_limit_reached' | 'company_fan_out_limit_reached' | 'named_companies_filtered' | 'led_by_headcount' | 'query_page_filtered', required: What the search could not do exactly. `filter_values_truncated`: the scan budget is shared between the values of one filter and ran out, so the result may omit matches — split the request, one value per call, and merge the pages. `city_spans_multiple_places`: a bare city name in `hq.cities` matched more than one state or country on this page — pin it (`"newark|california"`) if you meant one. `per_company_limit_reached`: a direct people search returned the perCompany cap for at least one employer; additional matching people may exist there. `title_matched_first`: the title was rare, so every current role it matches was read and checked against the company filters — paging to the end returns every match, at most `perCompany` per company — and pages are ordered by company id, not headcount (`sort` does not apply). `title_matches_sampled`: the title matched more current roles than one search reads, so the page was checked against the roles it did read, a sample rather than every match; `truncated` is true and there is no next page. Fewer or more specific title terms read every match. `search_time_limit_reached`: the time limit stopped a people search after some companies; the returned matches are partial, there is no next page, and values lists the remaining company IDs. `residual_scan_limit_reached`: the named filter (founded year, technologies or company type; industries beside `fundingStages`; also country beside `led_by_headcount`, and a city, state or country beside another place or industry filter) is checked company by company, and the search stopped after checking the first companies selected by other filters in `sort` order; add industries, a headcount band or cities (beside `fundingStages`: a headcount band, countries or cities) to check the companies you want. `company_fan_out_limit_reached`: titles were searched only at the first companies that passed the company filters in `sort` order; narrow those filters, or send fewer companies per request. `named_companies_filtered`: company attributes excluded one or more explicitly named companies; the note identifies them, while `applied` shows the filters that were enforced. `led_by_headcount`: no filter could select the companies (a companies search with only a founded year, company type, technologies or country), so they were taken largest first and checked one by one, up to a fixed number: every row matches, but these are the largest matches, not all of them; tell the user. `query_page_filtered`: a people search with a `query` checked each ranked person's current employer against the company filters and left out the people whose employer failed them or could not be identified; the page was not refilled, so it can be short. `mode: "direct"` with `filters.title` and no `query` searches using exact filters instead; it also needs named companies or an indexed company filter such as `industries` or `headcount`. `search_degraded`: this response carries `degraded: true`. One data source this search depends on was unavailable, slow or switched off for this workspace, so this answer may be thinner than usual — read a short result as possibly incomplete rather than as an absence of matches. A repeat of this request is billed as a new search, and some causes persist for a workspace and degrade every repeat, so use what came back or narrow the request rather than resending it.
    - `filter` string: The request field the note is about, e.g. `filters.industries`. Absent when the note is about the response as a whole rather than one field, which is the case for `search_degraded` — group notes by this field only when it is present.
    - `values` string[]: The values of that field the note applies to, as they were sent. Absent whenever `filter` is.
    - `detail` string, required: One sentence naming what was found, for a human reading the response.
  - `applied` object, nullable, required: The `filters` that were enforced on this response, as sent, except that `hq.countries` holds the ISO-2 code each value was read as (`"United States"` comes back as `"US"`). Every row in `data` passed all of them. Null when no filters were sent.
    - `industries` string[]: Exact values from the industry taxonomy — list them with `GET /v1/find/industries?contains=software`. "SaaS" is not a value; "software development" is. Case-insensitive, up to 99. Several values are a union. An uncapped page merges the same rows each value returns alone; when `truncated` is true, read the notes before treating the page as complete. When present, industries drive the indexed scan, unless `fundingStages` is sent: then each company is checked against them. Exact: a company with no value for this attribute does NOT match.
    - `headcount` object: Inclusive employee-count band, e.g. `{"min": 11, "max": 200}`. Either bound may be omitted. Selects a set on its own. In a filters-only search with no company attributes, or beside a title without an indexed company filter, send a saved workspace profile's `buyerProfile.sizeBand` (its max capped at 50,000) beside its mapped industries; with a partial profile, no mapped industry or no profile, send `{"min": 1, "max": 10000}`. Never `{"min": 1}` alone: the largest employers make common titles time out. Beside a title with only a founded year, technology or company type, fit the band to it (founded in recent years: `{"min": 1, "max": 50}`). A funding stage selects a set on its own; send no profile industries or band beside it. Without a title, founded year, technology and company type may go alone. With a country and no industries, pin its main cities or states: the band beside a country alone can time out. Exact: a company with no value for this attribute does NOT match.
      - `min` integer
      - `max` integer
    - `hq` object: Where the company is headquartered, not where any person is based. For where a person is based, use `filters.person` on a people search (`target: people`, no `query`).
      - `countries` string[]: Countries of the headquarters: ISO-2 codes (`["US", "DE"]`) or country names (`["United States", "Germany"]`), any case. A name is read as its ISO-2 code, and `applied` echoes the codes. Applied inside the scan, cheap, any number up to 60. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
      - `regions` string[]: Shorthand for a block of countries: `north_america`, `europe`, `asia_pacific`, `latin_america`, `middle_east`, `africa`. Expands to ISO-2 codes and merges with `countries`. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
      - `cities` string[]: Headquarters city, exact match, case-insensitive (`"san francisco"`). At most 20. Can select a set on its own. A bare city name is a NAME, not a place, and matches it everywhere on earth: `"dublin"` is Ireland and Ohio and California, `"newark"` is New Jersey and California. Pin one by writing `"city|state"`, `"city|state|country"` or `"city||country"` — `"newark|california"`, `"dublin||IE"`, `"richmond|virginia|US"`. The state is its full name and the country an ISO-2 code, each pinned city narrows on its own, and pinned and bare values mix freely. When a bare value turns out to span more than one place, the response says so in `notes`. Exact: a company with no value for this attribute does NOT match.
      - `states` string[]: Headquarters state or province, full name, case-insensitive (`"california"`, `"bavaria"`, not `"CA"`). At most 20. Can select a set on its own. There is no US-region shorthand; spell the states out. Applied alongside `cities` as a separate AND, so `cities: ["newark", "dublin"]` with `states: ["california", "ohio"]` admits all four pairings — write the pins into `cities` to avoid that. Exact: a company with no value for this attribute does NOT match.
    - `person` object: `target: people` without `query` only. Where the person says they are, from their LinkedIn profile location: not a verified address, and not the employer's headquarters (that is `hq`). `countries`, `states` and `cities` are each any-of, and they AND together. A person with no recorded value for a part you sent does NOT match. `states` and `cities` narrow the people at a set of companies: send them beside `companyWebsites`, `companyIds` or an indexed company filter (`industries`, `hq.cities`, `hq.states`, `fundingStages`, `headcount`). Applied before `perCompany`, so each company still returns its most senior people who match.
      - `countries` string[]: Countries the person is in: ISO-2 codes (`["US", "DE"]`) or country names (`["United States", "Germany"]`), any case. A name is read as its ISO-2 code, and `applied` echoes the codes. At most 60.
      - `states` string[]: State or province the person is in, full name, case-insensitive (`"california"`, not `"CA"`). At most 20.
      - `cities` string[]: City the person is in, whole name, case-insensitive (`"san diego"`). A person whose profile names only a metro area ("Greater San Diego Area") has no city and does not match one. A city name can exist in several places: send `countries` or `states` beside it. At most 20.
    - `fundingStages` string[]: The most recent funding round Trayo recorded, as a closed set: `pre_seed`, `seed`, `series_a`, `series_b`, `series_c`, `series_d_plus`, `private_equity`, `debt`, `post_ipo`, `grant`. Only a minority of companies carry funding data at all, so this keeps ONLY companies with a recorded round of that kind — an unfunded or unrecorded company never matches. It is a record, not a stage of life: an old or listed company can carry a stale `seed`. For "early-stage", pair it with `foundedYear` and `headcount`. With a `query`, the sentence path's notion of stage is checked against this record and loses: a company recorded as "Series B" that has since raised a Series C is dropped. Selects a set on its own, with or without a title. Beside it, `industries` are checked company by company: do not add profile industries the user did not ask for.
    - `foundedYear` object: Inclusive founded-year band, e.g. `{"min": 2018}`. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
      - `min` integer
      - `max` integer
    - `technologies` string[]: Technologies detected on the company's public web presence, any of, up to 20. Matched on the name with punctuation and case removed, so `"Amazon Web Services"`, `"aws"` and `"HubSpot"` are all valid. A web-stack scan: good for marketing, analytics and cloud tooling, blind to internal systems such as an ERP. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
    - `companyTypes` string[]: Legal form as the company describes itself, a closed set: `privately held`, `public company`, `nonprofit`, `educational`, `government agency`, `partnership`, `self-employed`, `self-owned`. `public company` includes wholly-owned subsidiaries of listed groups, not only the listed entity. Exact: a company with no value for this attribute does NOT match. Narrows a set. Sent with no `industries`, `hq.cities`, `hq.states`, `fundingStages` or `headcount` and no `query`, a companies search checks companies with known headcount largest first, up to a fixed number, and `notes` say `led_by_headcount`: the largest matches, not all of them. A search with `filters.title` needs one of those five beside it.
    - `companyIds` string[]: **Reach for `companyWebsites` instead** — it names the same companies by something you already have. This is the older form of that filter, kept for callers holding ids: exact companies, by the `id` a companies search returned with `known: true`, at most 200. For `target: companies` it is a lookup of exactly those rows; for `target: people` it names whose people to search. Other company filters narrow only these companies (AND). Without `query`, `named_companies_filtered` notes identify companies the attributes exclude. With `query`, the ranked candidates are intersected with these IDs; this does not search every named company. Do not combine with `companyWebsites`.
    - `companyWebsites` string[]: The same thing as `companyIds`, named by something you already have: each company's own website — at most 100. `stripe.com`, `www.stripe.com` and `https://stripe.com/pricing` all name the same company; the scheme, `www.` and any path are ignored. Without `query`, each website is resolved to exactly one company Trayo holds, and the resolution is reported back in `resolvedWebsites` so you can see which company each one became. Websites that do not resolve are left out of the search and listed in `unresolvedWebsites`, so the answer can cover fewer companies than you named: read that list before you treat a short page as complete. A `not_found` one: check the spelling, send the company's own domain rather than a parent group's, a reseller's or a marketing microsite's, or describe the company with `filters.industries`, `filters.hq` or `filters.headcount` in a separate search. An `uncertain` one: the domain carries several possible companies and none was searched; show its `alternatives` to the user, or search again with `filters.companyIds` set to the right alternative's `id`. Only when NONE of the websites resolves is the answer a `404 company_not_found` naming them. Other company filters narrow only the resolved companies (AND), and `named_companies_filtered` notes identify companies they exclude. On a people search with `query`, websites instead match the ranked people's current employers directly: `resolvedWebsites` and `unresolvedWebsites` are null, and no matching employer yields an empty page rather than `company_not_found`. A `query_page_filtered` note counts people left out; the page is not refilled. Other company attributes also narrow the current employer (AND). Do not combine with `companyIds`.
    - `title` object: Current employee title. A filters-only people search requires `title.any` or `title.all`, even with `companyIds` or `companyWebsites`; no-title named-company sampling is unsupported. In `mode: direct`, a companies search may also use it: return each company with at least one matching current employee. Pair it with companyIds, companyWebsites or an indexed company filter; when the request names none, take them from the workspace profile (see `headcount`). A title is searched only at the first companies in `sort` order (in `mode: direct`, at most 30: largest first by default, the smallest with `smallest_first`) unless `notes` say `title_matched_first`; `company_fan_out_limit_reached` says more companies matched. Never report it as every company.
      - `any` string[]: Title terms, ANY of which must appear in the person's current title. A multi-word term matches as a phrase, with at most one word between the words: `"vp finance"` matches "VP Finance" and "VP of Finance", not "VP of Engineering, Finance partner". Punctuation and case are ignored: `"founder"` matches "Co-Founder & CEO". A single common word matches across every function (`"automation"` finds manufacturing and procurement automation too) — prefer two-word terms and use `exclude`. An acronym is matched LITERALLY and expands to nothing: `"cto"` does not match "Chief Technology Officer", and the same role is written both ways inside one company, so send both forms — `["cto", "chief technology officer"]`. At named companies (`companyIds`, `companyWebsites`) every term is searched again at each company, so a long list there can run out of time: send the forms the user means, not every variant.
      - `all` string[]: Title terms that must ALL appear. Combine with `any` freely.
      - `exclude` string[]: Title terms that disqualify a person (`["assistant", "intern"]`).
    - `excludeWorkspace` boolean: `target: companies` only. Default false. When true, companies already in your workspace are left out of `data` instead of coming back as `state: alreadyAdded`, so the page fills with companies you do not have. Use this for "find N new companies". "Already in your workspace" is decided exactly as `state` is. It is not a filter on its own: a filters-only search still needs a company filter. On a filters-only search a page can come back shorter than `limit` with `hasMore: true` when most matches were already yours — keep paging with `nextCursor`.
  - `resolvedWebsites` object[], nullable, required: What each `filters.companyWebsites` value became, one entry per distinct website, in the order you sent them. Null when no websites were sent, and on a people search with a `query`, which matches each website against the employer of every ranked person instead. A domain can carry more than one company profile — a squatter, a regional page, a subsidiary — so a website resolves only when a profile's name matches the domain and no much larger profile under another name sits beside it; the largest such profile wins. Anything less clear is listed in `unresolvedWebsites` as `uncertain`, with the candidates. Read this before you trust the page: if a `name` here is not the company you meant, the rows under it are not either. To search a different profile, send its id in `filters.companyIds` instead. Lists only the websites that resolved: these are exactly the companies searched.
    - `website` string, required: The website as it was matched: lowercased, without the scheme, `www.` or any path.
    - `id` string, required: The company profile this website resolved to, so two entries can be told apart. Read `name` to check the pick; you name companies to this route by their websites.
    - `name` string, nullable, required: That company, as Trayo names it.
    - `headcount` integer, nullable, required: Its headcount. When the domain carries several profiles whose names match it, the largest wins.
  - `unresolvedWebsites` object[], nullable, required: The `filters.companyWebsites` values that did not resolve to a company and were therefore NOT searched, one entry per distinct website, in the order you sent them: `not_found` (no company on that domain) or `uncertain` (several possible companies, listed in `alternatives`). Empty when every website resolved; null when no websites were sent, and on a people search with a `query`. When this is not empty, `data` covers only the companies in `resolvedWebsites`: a short page here is not a sign that the missing companies have nobody matching. For an `uncertain` website, show the alternatives to the user, or search again with `filters.companyIds` set to the right alternative's `id`.
    - `website` string, required: The website as it was matched: lowercased, without the scheme, `www.` or any path. A value with no usable host is echoed as you sent it.
    - `reason` 'not_found' | 'uncertain', required: Why it was left out. `not_found`: Trayo holds no company on this domain, or the value has no usable host. Check the spelling, send the company's own domain, or describe the company with filters instead. `uncertain`: Trayo holds company profiles on this domain, but none clearly is the company the website belongs to (no name matching the domain, or a much larger profile under another name beside it), so none was searched rather than risk the wrong one. The candidates are in `alternatives`: show them to the user, or, if one is clearly right, search again with `filters.companyIds` set to that alternative's `id`. New reasons may be added; treat an unknown one as not searched.
    - `alternatives` object[]: Only for `reason: "uncertain"`: the profiles this website could name, at most 3, largest first. Absent for other reasons.
      - `id` string, required: The company id. Send it in `filters.companyIds` (instead of `filters.companyWebsites`) to search this exact company.
      - `name` string, nullable, required: That company, as Trayo names it.
      - `headcount` integer, nullable, required: Its headcount, when known.
  - `interpreted` object, nullable, required: Present when a `query` was sent; null on a filters-only search.
    - `query` string, required: How the sentence was rewritten before searching.
    - `filters` object, required: What Trayo read out of the sentence as a constraint, in the request's own `filters` shape. Send it back as `filters` (with or without the `query`) to make the search exact and reproducible. A criterion in your sentence that is not here was NOT enforced; it only steered the rewritten query.
      - `industries` string[]
      - `headcount` object
        - `min` integer
        - `max` integer
      - `hq` object
        - `countries` string[]
        - `cities` string[]
        - `states` string[]
      - `fundingStages` string[]
      - `technologies` string[]

## Other responses

- `400`: Neither `query` nor `filters` was sent, a field is malformed or outside its closed set, a band is inverted, a people-only field was sent for companies, a cursor was sent with a query, or a filters-only search named no usable company filter, or requested a title without an indexed company lead. `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. `find_needs_indexed_filter` (`retry: fix_input`) — This filters-only search needs an indexed company lead. A people search or company search with a title cannot use country, founded year, technologies or company type alone. A company search without a title can use those filters alone, but checks only a bounded set of the largest companies (`led_by_headcount`). Add at least one of `filters.industries` (see `GET /v1/find/industries`), `filters.hq.cities`, `filters.hq.states`, `filters.fundingStages` or a `filters.headcount` band, or send a `query` and let the sentence select the set. With no company criteria in the request, do not ask the user: read `buyerProfile` from `GET /v1/workspace`, map its industries to exact `GET /v1/find/industries` values for `filters.industries`, and send its `sizeBand` (max capped at 50,000) as `filters.headcount` when `buyerProfileSource` is `saved`. With a partial profile, no mapped industry or no profile, send `{"min": 1, "max": 10000}`, never `{"min": 1}` alone. `malformed_cursor` (`retry: fix_input`) — `cursor` was not a value this API issued. Send a previous page's `nextCursor` verbatim, or omit it to start over.
- `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`: Trayo could not identify a company for any `filters.companyWebsites` value, so there was nothing to search. The message names the websites; for an ambiguous one it also names the candidate companies with their ids, to send back as `filters.companyIds`. When only SOME websites fail to resolve, the search runs over the rest and the ones left out are listed in `unresolvedWebsites` instead. `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`.
- `409`: TWO DIFFERENT FAILURES AT ONE STATUS, and `retry` is what tells them apart — they demand OPPOSITE responses. Never branch on the 409 alone. `idempotency_key_reused` (`retry: fix_input`) — This `Idempotency-Key` was already used with a different request body. Repeating this request unchanged will keep failing; use a new key. `idempotency_in_progress` (`retry: retry_later`) — This operation is still in progress. Retry the same request with the same Idempotency-Key.
- `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`: `find_response_failed` (`retry: do_not_retry`) — The search completed and was charged, but its response could not be produced. The same Idempotency-Key preserves this outcome; a new key starts another paid search. Contact support.
- `503`: Read `code` and `retry` to choose the next action. `find_unavailable` (`retry_later`): the search service failed. Wait, then send the same request again. `find_timeout` (`fix_input`): the search exceeded its time limit. Narrow the query or filters before trying again. `find_not_configured` (`do_not_retry`): this search is unavailable. No charge applies. `data_search_busy`: direct search is at capacity or briefly unavailable; wait `details.retryAfterSeconds` before retrying. `data_search_timeout` (`fix_input`): a search by filters, with or without `mode`, reached its time limit. Change what `message` names before retrying; the same search runs out of time again. It is not an empty result. `find_unavailable` (`retry: retry_later`) — The search service failed. Wait, then send the same request again. A successful search with incomplete data returns 200 with `degraded: true`. `find_timeout` (`retry: fix_input`) — The search exceeded its time limit. Narrow the query or filters before trying again. A search that already started can still finish. Do not repeat the identical request. `find_not_configured` (`retry: do_not_retry`) — This type of search is unavailable. Nothing was charged. Retrying or rewording the request will not help. Contact support. The `message` lists any available alternatives, such as an exact company-name lookup or a filters-only search. `data_search_busy` (`retry: retry_later`) — Hosted search capacity (direct Find, post keyword search, job-change search, job posting search) is in use: your workspace already has as many of these searches running as it may, or the service is at capacity. Nothing ran. Wait for details.retryAfterSeconds, then retry, and send fewer at once. `data_search_timeout` (`retry: fix_input`) — A hosted search (Find by filters, with or without `mode: direct`; post keyword search; job-change search; job posting search; post search over dates older than 90 days) ran out of time. Change what `message` names and retry; resending the same search runs out of time again, and a timeout is not an empty result.

---

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