# List the Leads table

The Leads table as the user sees it in mySDR: the leads from the signals on Signals, plus the people an agent saved. `total` is the number next to the Leads tab when no filter is set. Each row says where it came from, which signals found it and where its outreach stands. The Leads and Companies tables are what the user sees in mySDR. Your agent decides what goes in them: search results do not land there by themselves. The user keeps the overview there; your agent reads the same tables here and fills them with POST.

A person who is both a signal lead and saved by an agent is one row (the signal lead), with the state the agent set on it: its next action, address, fields and campaigns, and `needs_decision` while Leads says new. `saved_ids` gives the id of the agent's own row. `table` counts the whole table, whatever the filter: by source and, when it can be read, by outreach state. `capped` is true when there are more than 5,000 signal leads; the numbers are then a lower bound.

## Authentication

Send `Authorization: Bearer gtk_…`. Create the key under **Connect assistant → API keys**. Every `gtk_` key can call these routes, also while the Signals API is closed. A missing, invalid or revoked key is **401**.

### Query parameters

- **`q`** `string` — Only rows whose name, title, company, place, signal or one of your own field values contains this text (up to 100 characters).
- **`source`** `string` — Only rows from the signals on Signals, or only what an agent saved. One of: `signal`, `assistant`.
- **`limit`** `integer` — How many rows to return, from 1 to 100. Defaults to 50.
- **`offset`** `integer` — Skip this many rows. Pass `next_offset` of the previous page.
- **`outreach`** `string` — Only rows in this outreach state; `any` = with outreach, `none` = without. One of: `scheduled`, `ongoing`, `replied`, `done`, `any`, `none`.
- **`lifecycle`** `string` — Only rows in this state of the Status column the user sees (`lifecycle` on each row); `open` = `new`, `scheduled`, `contacted`, `replied`, `interested` and `meeting`. Several separated by commas. One of: `new`, `scheduled`, `contacted`, `replied`, `interested`, `meeting`, `later`, `no_reply`, `not_interested`, `archived`, `open`.
- **`status`** `string` — Only rows with this status; several separated by commas. A snooze that ended counts as `new`. One of: `new`, `needs_decision`, `snoozed`, `archived`.
- **`lead_status`** `string` — Only rows with this lead status; `none` = without one. Several separated by commas. One of: `interested`, `meeting`, `later`, `not_interested`, `none`.
- **`due`** `string` — By next action: `today` (today and everything overdue), `overdue`, `week` (the next 7 days and overdue), `any` (has one), `none`. One of: `today`, `overdue`, `week`, `any`, `none`.
- **`campaign`** `string` — Only rows in this campaign (a campaign id).
- **`stage`** `string` — Only rows in these stages (keys from GET /stages); `none` = without a stage. Several separated by commas.
- **`owner`** `string` — Only rows of these owners: `me`, a colleague's email address or user id (GET /team), or `none`. Several separated by commas.
- **`do_not_contact`** `boolean` — `true` = only rows on the exclusions (marked Do not contact in mySDR), `false` = only rows that may be contacted.
- **`agent_signal`** `string` — Only rows your own signals caught (POST /agent-signals/{key}/hits): their keys, separated by commas. A key the organization does not have is a 400.
- **`field.{key}`** `string` — Only rows whose own field `{key}` has this value; several separated by commas (any of them). Text without case, a number by its value, true/false or yes/no for a yes/no field. E.g. `field.ats=recruitee,teamtailor`.
- **`field.{key}.gte`** `number` — Only rows whose field `{key}` is a number of at least this. E.g. `field.fte.gte=200`.
- **`field.{key}.lte`** `number` — Only rows whose field `{key}` is a number of at most this.
- **`field.{key}.exists`** `boolean` — `true` = the field has a value, `false` = it has none. Up to 10 field filters, all must match.

## Response

- **`object`** `string` · **required** — Always `list`. One of: `list`.
- **`total`** `integer` · **required** — Rows that match this request. Without a filter: the number next to the Leads tab.
- **`data`** `object[]` · **required** — Rows, newest first. `id` (a signal lead: its number in mySDR; a saved person: the `sav_…` or `per_…` that PATCH and DELETE take), `source` (`signal` or `assistant`), `name`, `title`, `headline`, `company`, `company_linkedin_url`, `company_domain`, `location`, `country_code`, `linkedin_url`, `signals` (the names in the Signals column, your own signals included), `agent_signals` (your own signals that caught it, newest first: `key`, `name`, `first_at`, `last_at`, `summary` (what you last saw) and `source_url`; empty = none), `note`, `added_at`, `last_signal_at`, `outreach` (`status`, `label`, `attention`, `last_activity_at`, `next_step_at`, `campaign_ids`, or null), `lifecycle` (the Status column the user sees, derived from `status`, `lead_status` and `outreach`, read-only; the first that fits: `archived`, `not_interested`, `meeting`, `interested`, `later` (they said later, or a snooze), `replied`, `no_reply` (outreach done without a reply), `contacted`, `scheduled`, else `new`), the state (`status`, `status_reason`, `snooze_until`, `lead_status`, `next_action` (`at`, `kind`, `note`, `email` when you planned one, without its `text` in a list, or null), `out_of_office_until`, `fields`, `campaign_ids`, `stage` (a stage key, or null) and `owner` (`id`, `name`, `email`, or null), plus `email` and `email_status`), `saved_ids` (the ids of the agent's own row: PATCH takes them; empty for a signal lead nobody saved), `do_not_contact` (the exclusions on this person, by their address or their employer's domain or name, each with `type`, `reason` and `message`; empty = may be contacted; a bounced address only blocks email and is not listed) and `dashboard_url`.
- **`next_offset`** `integer | null` · **required** — The offset of the next page, or null.
- **`table`** `object` · **required** — `leads`, `from_signals`, `from_assistant`, `outreach` (a count per state, or null), `lifecycle` (a count per state of the Status column), `status` (a count per status), `due_today` (rows whose next action is today or overdue), `do_not_contact` (rows on the exclusions) and `capped`.
- **`dashboard_url`** `string` · **required** — The Leads table in mySDR.

## Credits

Free. This call never uses credits.

## Errors

- **400** `invalid_request` — An unknown `source`, `outreach`, `lifecycle`, `status`, `lead_status` or `due`, a `campaign` that is not an id, a `stage` that is not a key, an `owner` who is not in the organization, an `agent_signal` the organization does not have, a field filter that is not valid (`param` names it), a `do_not_contact` that is not true or false, a `limit` outside 1 to 100, or a negative `offset`.
- **401** `authentication_error` — The Authorization header is missing, or the key is unknown or revoked.
- **403** `permission_error` — API access is not part of your plan, or your account is suspended.
- **429** `rate_limit_error` — More than 60 requests per minute, or more than 3 at the same time, on one key. Wait for the Retry-After header.
- **503** `service_unavailable` — The API is temporarily unavailable. Retry later.
- **500** `internal_error` — Something went wrong on our side.

## Example

### cURL

```bash
curl "https://mysdr.app/api/v1/tables/leads?limit=25" \
  -H "Authorization: Bearer $MYSDR_API_KEY"
```

### JavaScript

```javascript
const response = await fetch('https://mysdr.app/api/v1/tables/leads?limit=25', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.MYSDR_API_KEY}`
  }
});
const body = await response.json();
```

### Python

```python
import json
import os
import urllib.request

request = urllib.request.Request(
    "https://mysdr.app/api/v1/tables/leads?limit=25",
    headers={
    "Authorization": f"Bearer {os.environ['MYSDR_API_KEY']}"
    },
    method="GET",
)
with urllib.request.urlopen(request) as response:
    body = json.load(response)
```

### Example response

```json
{
  "object": "list",
  "total": 2,
  "data": [
    {
      "id": "sav_7c1f2a40-5b0e-4c3e-9a71-2f7d3e0c9b11",
      "object": "table_lead",
      "source": "assistant",
      "name": "Jane Example",
      "title": "CFO",
      "company": "Acme Analytics",
      "country_code": "BE",
      "linkedin_url": "https://www.linkedin.com/in/jane-example",
      "signals": [
        "Companies that raised funding · BE"
      ],
      "note": "CFO at a company that raised a Series A last week.",
      "added_at": "2026-10-23T09:30:00.000Z",
      "outreach": null,
      "lifecycle": "new"
    },
    {
      "id": "48213",
      "object": "table_lead",
      "source": "signal",
      "name": "Tom Sample",
      "title": "Head of Finance",
      "company": "Beta Labs",
      "country_code": "NL",
      "signals": [
        "New finance leaders · Benelux"
      ],
      "added_at": "2026-10-21T07:12:00.000Z",
      "outreach": {
        "status": "ongoing",
        "label": "Invite sent",
        "attention": null,
        "last_activity_at": "2026-10-22T10:00:00.000Z",
        "next_step_at": "2026-10-24T09:00:00.000Z",
        "campaign_ids": []
      },
      "lifecycle": "contacted"
    }
  ],
  "next_offset": null,
  "table": {
    "leads": 2,
    "from_signals": 1,
    "from_assistant": 1,
    "outreach": {
      "scheduled": 0,
      "ongoing": 1,
      "replied": 0,
      "done": 0
    },
    "capped": false
  },
  "dashboard_url": "https://mysdr.app/en/gtm/leads"
}
```
