---
title: "List events"
method: GET
path: "/v1/events"
tags: ["Events"]
---

# List events

`GET /v1/events`

## Request

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

**Required permissions:** `events:read`.

What discovery found. Defaults to `state=live` — dismissed events are excluded unless you ask for them explicitly. PAGINATES: follow `nextCursor` until `hasMore` is false.

After a discovery run reports `status: completed`, filter by `?discoveryRunId={runId}` to read every event that stands for that run: everything the run wrote, at any `eventDate` — a job change carries the month the job STARTED, which can predate the lookback — plus every event matching its account × signal × lookback scope, including ones written by earlier runs. Paged to the end, a settled run therefore yields at least `eventsNew` events — one page is capped and need not, so follow `nextCursor` before comparing to that counter. Before it settles the same filter returns what the run has written so far; poll `GET /v1/discoveries/{runId}` for the rest. `candidatesScreened` and `eventsNew` remain the execution's frozen counters, not the size of this page.

## Query parameters

- `discoveryRunId` string, uuid
- `accountId` string, uuid
- `signalKey` string
- `signalKeys` string[]
- `since` string
- `discoveredSince` string
- `state` string
- `expand` string
- `cursor` string
- `limit` integer

## Response `200`

- EventListDto
  - `data` object[], required: Events, most recently created first.
    - `id` string, uuid, required
    - `accountId` string, uuid, required
    - `accountName` string, required: The account this event is about, named. On every event, expanded or not, so a digest can say which company without a second call — there is no route that turns an `accountId` back into a company.
    - `accountUrl` string, uri, nullable, required: That account's website as an absolute HTTP or HTTPS URL, or `null` when the workspace does not know one — same field, and same rule, as `url` on `GET /v1/accounts`.
    - `title` string, required
    - `summary` string, nullable, required
    - `sourceObservation` object, nullable: Source summary with its quotations, acquired headline, or null when no retained receipt exists; separate from seller relevance.
      - `basis` 'source_quotes' | 'source_headline' | 'court_docket', required
      - `text` string, required
      - `quotes` string[], required
      - `scopeNote` string, required
    - `whyItMatters` string, nullable, required: Why this event matters to YOUR workspace — one short paragraph relating the event to your solutions and the signals it matched, written by discovery when it wrote the event. `summary` says what happened; this says why you should care. It is the same text the Trayo app shows under "Why it matters" and that webhook payloads carry as `whyItMatters`. `null` when discovery did not produce one — older events, and events whose signal type skips that stage. Never an empty string.
    - `offerings` object[], nullable, required: Which of YOUR solutions this event relates to — the offerings from your workspace profile that discovery tied to the event when it wrote `whyItMatters`. The same array webhook payloads carry as `offerings`. `null`, never `[]`, when discovery tied none to it: older events, events whose signal type skips that stage, and workspaces with no offerings on their profile (`PATCH /v1/workspace`).
      - `name` string, required: The name your workspace gave the offering.
      - `description` string, required: Why that offering is relevant to this event, in one line.
      - `link` string, required: Where to read more about the offering. `""` when none was set.
    - `url` string, nullable, required
    - `publication` string, nullable, required
    - `eventDate` string, date-time, required
    - `signalType` string, required
    - `signalKeys` string[], required: Which of YOUR signals matched this event, by `signalKey` — the slug you named the signal, the same value `POST /v1/discoveries` takes in its `signalKeys` and `?signalKey=` filters on here. On EVERY event, compact reads included, sorted and without repeats so two reads of one event agree. **This is the per-event attribution `signalType` does not give you.** `signalType` is the kind of source a signal reads — `news`, `jobs`, `job_change` — and your signals usually share a handful of those between them; `signalKeys` names the signals themselves. So a run over many signals stays attributable event by event, and a run per signal is not needed to tell which one fired. Usually one key. Several when several of your signals matched the same event, which Trayo records on the one event rather than duplicating it. `[]` only where no signal is linked at all: an event from before these links were recorded, or one whose signal has since been deleted. `expand=signals` carries the same keys with each match's `type`, `confidence` and `evidence`; this field is the keys alone, and it costs no expansion.
    - `jobChange` object, nullable, required: The move behind a `job_change` event — the same object webhook payloads carry as `jobChange`. `null` on every event whose `signalType` is not `job_change`. On a `job_change` event the object is always present, and its own fields read `null` where discovery could not classify the move.
      - `type` string, nullable, required: How the person moved — for example `new_hire` or `promotion`.
      - `date` string, nullable, required: When the move happened, as discovery read it off the profile.
      - `previousRole` object, nullable, required: Where they came from. `null` when discovery found no prior role.
        - `title` string, nullable, required
        - `company` string, nullable, required
        - `since` string, nullable, required
    - `location` object, nullable, required: Where the job posting behind a `jobs` event says the role is, as the posting records it. State a role's location from here, never from its title: a role that asks for a language is often based in another country. A job listed in several places shows the place of the listing that stands for it; `jobPosting.locations` holds every place. A part reads `null` when the posting leaves it empty. The object is `null` when the posting records no place and does not say it is remote, and on every event whose `signalType` is not `jobs`.
      - `city` string, nullable, required
      - `region` string, nullable, required: The state, province or similar region.
      - `country` string, nullable, required
      - `remote` boolean, required: Whether the posting says the role is remote. When true, the city, region and country are where the posting is listed, not where the person would work.
    - `jobPosting` object, nullable, required: The job behind a `jobs` event, over all its listings. Its `eventDate` is the earliest date the job was posted: the date a listing says it was posted when that is not after the day it was first recorded, else that day. A job first posted before the window you asked for is an older job listed again, not a new opening. `null` on every event whose `signalType` is not `jobs`, and on jobs events discovery wrote before it grouped listings (2026-10).
      - `listings` integer, required: How many listings of this one job discovery found. A job board lists one role once per city, and several boards list it again: they are one job and one event. Count roles by events, never by listings.
      - `locations` object[], required: Every place those listings name, each once, `location` first. `[]` when none records a place.
        - `city` string, nullable, required
        - `region` string, nullable, required: The state, province or similar region.
        - `country` string, nullable, required
        - `remote` boolean, required: Whether the posting says the role is remote. When true, the city, region and country are where the posting is listed, not where the person would work.
      - `active` boolean, nullable, required: Whether any listing was still open when discovery found the job. `false`: every listing had closed, so report it as a past opening, never as current hiring. `null` when the listings did not say.
      - `firstSeen` string, nullable, required: The day the first listing was recorded, YYYY-MM-DD. `null` when not recorded.
      - `lastSeen` string, nullable, required: The day the latest listing was recorded, YYYY-MM-DD. `null` when not recorded.
    - `state` 'live' | 'dismissed', required: The only two states this API publishes. An event that has not reached one of them is not addressable yet — it 404s. `dismissed` means Trayo withdrew an event it had produced. To dismiss one yourself send `DELETE /v1/events/{id}`; the event then reads back in neither state.
    - `dismissalReason` string, nullable, required: Why Trayo withdrew this event — a short machine-readable tag such as `stale_job_change`, `no_signal_match` or `within_batch_duplicate:{eventId}`. The vocabulary is Trayo's own and grows over time, so read it as a label, not an enum. Only ever set on an event whose `state` is `dismissed`, and only when a reason was recorded. `null` on every `live` event.
    - `createdAt` string, date-time, required
    - `people` object[]: Present only when `expand` was sent on the list route. Always present on `GET /v1/events/{id}`. **It is EMPTY until three preconditions hold, and an empty array here does not mean nobody was relevant.** Attaching people to an EVENT is a stage of its own. It runs only when (1) the account is ready — the same readiness `blockedSignals` reports on a discovery run, which the account reaches on its own shortly after it is created — (2) the workspace has a stakeholder definition, and (3) a discovery run you start yourself was started with `people: "add"`. The default, `people: "none"`, adds no people. A `job_change` event still names the person who changed jobs; add them with `POST /v1/people` if you want them. (2) and (3) are yours: `PATCH /v1/workspace`, and `people: "add"` on the run. All must be in place BEFORE the discovery run that produces the event — none is applied retroactively — so a workspace that sets its definition after its first runs gets `[]` on the events those runs produced and people on the ones after. Set it first. The people listed here are already in your workspace: `id` is a person id you can pass to list and contact tools without adding them again. Importing an account adds no people; to add people at an account yourself, search with `POST /v1/find` and add the ones you choose with `POST /v1/people/batch`. `signals` expands normally.
      - `id` string, uuid, required
      - `fullName` string, required
      - `title` string, nullable, required
      - `linkedinUrl` string, nullable, required: Their LinkedIn profile URL, when Trayo has it. Send it to `POST /v1/contacts/lookup`, `POST /v1/research/person`, or as `linkedinUrl` to `POST /v1/people`.
      - `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.
      - `reasoning` string, required: Why this person is relevant to this event.
    - `signals` object[]: How well each of your signals matched, and from what reasoning. Same expansion rule as `people`. Send `expand` for that per-match detail, not to learn WHICH signals fired — every event names those in `signalKeys`, expanded or not. One entry per stored match, ordered by `signalKey` like `signalKeys`, but NOT deduplicated the way that field is: a signal you have refined can hold two matches on one event, one per version of it. Such repeats carry IDENTICAL `confidence` and `evidence`, because the detail is looked up by `signalKey` — so a repeated key tells you nothing extra. Expect repeats and read `signalKeys` for the distinct set.
      - `signalKey` string, required
      - `type` string, required
      - `confidence` 'low' | 'medium' | 'high', nullable, required: How sure the match was, on the three-step scale the matcher writes: `low`, `medium`, `high`. `null` when the run did not record one for this signal — older events, and signals attached by a stage that does not score its matches.
      - `evidence` string, nullable, required: Why this signal matched THIS event, in the matcher's own words — the sentence or two it reasoned from. `whyItMatters` argues the event to your workspace; this shows the work behind one signal. `null` when the run recorded no reasoning for this signal. Never an empty string.
  - `hasMore` boolean, required: Another page exists. When `false` you have reached the end — stop requesting pages.
  - `nextCursor` string, nullable, required: Pass verbatim as the next request's `cursor` to fetch the next page. Null exactly when `hasMore` is false.

## 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. `invalid_date` (`retry: fix_input`) — A date filter (`updatedSince`, `since`, `detectedSince`) did not parse. Send a plain date (`2026-06-07`, read as midnight UTC) or a full ISO-8601 timestamp (`2026-06-07T14:30:00Z`). Relative words such as `yesterday` or `last-quarter` are not accepted. `param` names the field.
- `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.
- `404`: `discoveryRunId` does not resolve in your workspace. `discovery_run_not_found` (`retry: do_not_retry`) — No such run in your workspace.
- `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.

---

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