---
title: "List accounts"
method: GET
path: "/v1/accounts"
tags: ["Accounts"]
---

# List accounts

`GET /v1/accounts`

## Request

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

**Required permissions:** `accounts:read`.

A page of accounts, most recently created first. PAGINATES: follow `nextCursor` until `hasMore` is false.

## Query parameters

- `q` string
- `updatedSince` string
- `cursor` string
- `limit` integer

## Response `200`

- AccountListDto
  - `data` object[], required: Accounts, most recently created first.
    - `id` string, uuid, required
    - `name` string, required
    - `url` string, uri, nullable, required: The company's website as an absolute HTTP or HTTPS URL, or `null` when this workspace does not know one. `null` is expected rather than exceptional: an account created from a company NAME alone — by `POST /v1/people` with a `company` carrying no `domain`, or by a Trayo import — has no website until one is supplied. Set it with `PATCH /v1/accounts/{id}` (`trayo_update_account` over MCP). `logoUrl` is `null` for exactly these accounts, because it is derived from this field.
    - `logoUrl` string, nullable, required: The account's logo URL. Derived from `url`, so it is present for every account whose `url` is a usable domain and `null` otherwise. Cannot be set through this API. The URL returns image bytes (200) or redirects to them (302), so it is usable directly as an `<img src>`; it answers 404 when no logo resolves for the domain. Fetching it needs no API key.
    - `linkedinHandle` string, nullable, required
    - `starred` boolean, required
    - `scores` object, required: Trayo's own ranking of this account, published so you do not have to rebuild it by counting events. Both members are null on an account Trayo has not scored yet. This route does not sort by them — page through and sort client-side on `scores.timing.score` or `scores.activity.total`.
      - `timing` object, nullable, required: Null until Trayo has computed it. `calculatedAt` records when this score was last updated.
        - `score` integer, required: 0–100, NORMALIZED: this account's `raw` as a share of the highest `raw` in your workspace, rounded — the definition the product uses for its timing score. 100 is your hottest account right now, which means it moves when OTHER accounts move. Rank on this; threshold on `raw`.
        - `raw` number, required: The un-normalized score. How much your signals have fired at this account RECENTLY: each matched signal contributes its weight (1–3), decayed with a 30-day half-life, over a 90-day window, stacked and capped at 100. Stable for this account regardless of the rest of the workspace, so an absolute threshold on it holds still.
        - `calculatedAt` string, date-time, required
      - `activity` object, nullable, required: Null until Trayo has computed it. Recomputed after discovery runs; nothing in this API triggers it.
        - `total` number, required: Weighted sum of event significance and relevance with recency decay and a diversity bonus, over `lookbackDays`. Scale depends on the workspace's scoring configuration — compare accounts within a workspace, not across workspaces.
        - `eventCount` integer, required: Events that contributed.
        - `avgSignificance` number, nullable, required
        - `avgRelevance` number, nullable, required
        - `recencyBonus` number, required
        - `signalDiversityBonus` number, required
        - `lookbackDays` integer, required
        - `summary` string, nullable, required: A generated prose summary of what the contributing events add up to for this account — the "why", not just the number. Null until generated; regenerated when new events arrive.
        - `summaryGeneratedAt` string, date-time, nullable, required
        - `calculatedAt` string, date-time, required
    - `pointOfView` object, nullable, required: Trayo's reading of what this account is doing and why it matters to what your workspace sells — the "why" behind the account, where `scores.activity.summary` is the "why" behind the number. Every prediction cites the events it rests on, so you can check the reading against the record instead of taking it on trust. Null until the first discovery run produces events and the point of view is generated; regenerated when new events arrive. Also null when the stored copy predates the current shape — an out-of-date reading is withheld rather than served in pieces. Reading this route never generates one.
      - `predictions` object[], required: At most three, strongest first. Empty when the events do not support a direction — an honest answer, not a failure.
        - `point` string, required: One sentence: where the company appears to be heading, and the need that follows from it. Deliberately hedged ("likely", "appears to be") — it is an outside-in reading of public events, not a confirmed internal plan.
        - `evidence` object[], required: The events the prediction rests on, at least one and at most three. A prediction with no evidence is never written, so this is never empty.
          - `eventId` string, uuid, required: A public event id — read the whole event with `GET /v1/events/{id}`, or find it in `GET /v1/events?accountId=...`. The title, date and summary beside it are copies taken when the point of view was written, so fetch the event when you need its current form. That fetch can 404: the point of view is not rewritten when an event it cites is dismissed afterwards.
          - `eventTitle` string, required
          - `eventDate` string, date, required: The date the event happened, `YYYY-MM-DD` — not a timestamp.
          - `eventSummary` string, nullable, required
          - `connection` string, required: One line on how THIS event supports the prediction — the causal or strategic link, not a restatement of the title.
      - `whyNow` object[], required: At most three. Always empty when `predictions` is empty — timing means nothing without a direction it is timely for.
        - `signalKey` string, required: The signal whose recent firing makes the timing matter. Matches a `signalKey` on `GET /v1/signals` and on the cited events.
        - `point` string, required: One line on why the timing matters now.
        - `events` object[], required: The last few events that matched this signal, most recent first.
          - `eventId` string, uuid, required: A public event id — read the whole event with `GET /v1/events/{id}`, or find it in `GET /v1/events?accountId=...`. The title, date and summary beside it are copies taken when the point of view was written, so fetch the event when you need its current form. That fetch can 404: the point of view is not rewritten when an event it cites is dismissed afterwards.
          - `eventTitle` string, required
          - `eventDate` string, date, required: The date the event happened, `YYYY-MM-DD` — not a timestamp.
      - `generatedAt` string, date-time, required: When this point of view was written.
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
  - `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.
- `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)
