---
title: "Find LinkedIn posts by topic or author"
method: POST
path: "/v1/posts/search"
tags: ["People"]
---

# Find LinkedIn posts by topic or author

`POST /v1/posts/search`

## Request

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

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

Searches public LinkedIn posts from the last `windowDays` (default 30, maximum 30), or between inclusive `publishedFrom` and `publishedTo` dates within the last 365 days, for a `topic` by meaning and keyword, or by LinkedIn profile `author`. Omit topic to list an author's posts. Results are limited to posts Trayo holds, which go back 365 days; an earlier `publishedFrom` is refused with `validation_failed`. An empty result does not prove none were published. Posts arrive with a delay of about 10 days, so the most recent days may be incomplete. Returns up to `limit` posts per page (default 25, maximum 50), each with `postId`, `url`, the author, the author's company where known, and `commentCount`. Send a post's `url` or `postId` to POST /v1/posts/engagers to see who commented.

Follow `nextCursor` as `cursor` with identical search filters until `hasMore` is false. Dates stay fixed across pages. Author-only pages traverse current stored posts newest first; edits or inserts can change a traversal. Topic pages freeze a bounded ranked candidate pool in private temporary storage. Check `coverage`: exhausting that pool does not prove all stored matches were found. An expired or unavailable snapshot never silently restarts a search. Nothing is added to your workspace. Allow more than 120 seconds for the client timeout.

## Request body

- PostSearchBodyDto
  - `topic` string: What the posts should be about. Omit when searching all posts by an author.
  - `author` string: Exact LinkedIn profile URL or handle of the post author. Use alone for their latest posts, or with topic. A person's name as topic does not filter authors. A valid handle format does not verify the person's identity.
  - `windowDays` integer: How many days back to search when publishedFrom and publishedTo are omitted, 1 to 30; default 30.
  - `publishedFrom` string: First publication date to include, YYYY-MM-DD. Send with publishedTo; overrides windowDays.
  - `publishedTo` string: Last publication date to include, YYYY-MM-DD. Send with publishedFrom; overrides windowDays.
  - `limit` integer: Maximum posts per page. Follow nextCursor with the same search filters until hasMore is false; page size may change.
  - `cursor` string: The previous nextCursor, verbatim with the same topic, author and date/window inputs. Effective dates stay fixed across pages. Author pages are a live traversal; topic pages use a bounded ranked snapshot.

## Response `200`

- PostSearchResponseDto
  - `windowDays` integer, nullable, required: The relative window searched, or null for explicit publication dates.
  - `since` string, required: The earliest publication date searched, YYYY-MM-DD.
  - `until` string, required: The last publication date searched, YYYY-MM-DD.
  - `hasMore` boolean, required: More rows remain in this traversal. For topic searches, false means the bounded ranked candidate pool ended, not that all matching stored posts were found.
  - `nextCursor` string, nullable, required: Send back as cursor with the same search filters. Null at the end. Do not compute or modify it.
  - `coverage` object, required: Author-only pages traverse current stored matches and can change across edits. Topic pages freeze a bounded ranked pool until expiry; neither guarantees full LinkedIn coverage.
    - `mode` 'author_live' | 'ranked_snapshot', required
    - `scope` 'stored_matches' | 'bounded_ranked_candidates', required
    - `candidateLimit` integer, nullable, required
    - `snapshotExpiresAt` string, nullable, required
    - `droppedLowRelevance` integer, nullable, required: Topic searches only: ranked candidates left out for low relevance to the topic. A short page with a high count means the window held few posts on the topic, not that the search failed. Null for author-only searches.
    - `keywordOnly` boolean: True when only keyword matches were ranked: matching posts by meaning ran out of time, which happens most on dates more than 90 days ago. The posts returned contain the topic's words; posts that discuss it in other words may be missing. Say so when you report the results. False on author-only searches.
    - `semanticPartial` boolean: True when matching posts by meaning covered only part of the dates: of the posts collected most recently for them, only the newest were matched by meaning and the rest by their words only. Posts that discuss the topic in other words may be missing. Say so when you report the results. False on author-only searches.
    - `note` string, nullable: What limited this search, in a sentence to pass on, or null when nothing did.
  - `posts` object[], required: Topic matches rank best first; author-only posts sort newest first. Empty means no stored matches for the requested identity, dates and topic; it does not verify the identity or prove that no posts were published. Nothing is saved to your workspace.
    - `postId` string, required: The LinkedIn post id. A string because it does not fit a JSON number. Send it, or `url`, as `post` to POST /v1/posts/engagers.
    - `url` string, nullable, required: The post on LinkedIn.
    - `publishedAt` string, required: Publication date, YYYY-MM-DD.
    - `text` string, required: The post text, cut at 600 characters.
    - `author` object, required
      - `name` string, nullable, required
      - `fullName` string, nullable, required: The person's name. The same value as `name`, under the name every person-returning route uses.
      - `headline` string, nullable, required
      - `title` string, nullable, required: The author's current job title, where we know it.
      - `linkedinUrl` string, nullable, required: The author's LinkedIn profile URL.
      - `linkedinUsername` string, nullable, required: The handle from that profile URL.
      - `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.
    - `company` object, nullable, required: The author's current company, or null when it is not known.
      - `companyId` string, nullable, required: Company dataset ID, where known. Reuse it as `companyId` in POST /v1/accounts/lookalikes. Send it as `company.companyId` to company research, stakeholder search or 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 LinkedIn profile, and looking it up by name will usually miss; search for it with a find companies `query` instead.
      - `name` string, nullable, required
      - `website` string, nullable, required
      - `linkedinUrl` string, nullable, required: The company's LinkedIn page, when Trayo holds a record for it.
      - `employees` number, nullable, required: Employee count, where known.
      - `country` string, nullable, required
    - `commentCount` integer, required: Comments we hold for this post.

## Other responses

- `400`: `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. `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.
- `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.
- `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.
- `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`: `post_search_not_configured` (`retry: do_not_retry`) — Post search is unavailable. Retrying will not help. Contact support. `post_search_failed` (`retry: retry_later`) — The post search or engager read failed or exceeded its time budget. Nothing was saved; retry the same request shortly. `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)
