# List leads

`GET https://api.versionseven.ai/v1/leads`

Leads in your organization, newest first. Filter by campaign, revenue range, employee count, industry, title or a search term, and page through the results with `limit` and `offset`.

- Required scope: `leads:read`

## Request

**cURL**

```bash
curl "https://api.versionseven.ai/v1/leads" \
  -H "Authorization: Bearer $VICTORIA_API_KEY"
```

**Node.js**

```javascript
const response = await fetch("https://api.versionseven.ai/v1/leads", {
  headers: {
    Authorization: `Bearer ${process.env.VICTORIA_API_KEY}`,
  },
});

const data = await response.json();
```

**Python**

```python
import os

import requests

response = requests.get(
    "https://api.versionseven.ai/v1/leads",
    headers={
        "Authorization": f"Bearer {os.environ['VICTORIA_API_KEY']}",
    },
)
data = response.json()
```

## Headers

- `Authorization` (string, required): `Bearer` followed by a space and your API key, for example `Bearer vk_…`.

## Query parameters

- `campaign_id` (string · uuid or null, optional): Filter by campaign ID
- `min_revenue` (integer or null, optional, ≥ 0): Minimum annual revenue filter
- `max_revenue` (integer or null, optional, ≥ 0): Maximum annual revenue filter
- `min_employees` (integer or null, optional, ≥ 0): Minimum employee count filter
- `max_employees` (integer or null, optional, ≥ 0): Maximum employee count filter
- `industry` (string or null, optional, at most 200 characters): Filter by industry (case-insensitive match)
- `title` (string or null, optional, at most 200 characters): Filter by job title (case-insensitive partial match)
- `search` (string or null, optional, at most 200 characters): Up to 5 words, separated by spaces. Every word must match at least one of the lead's email, first name, last name or company.
- `page` (integer, optional, ≥ 1, default `1`): Page number (1-indexed). Prefer `offset`, which every list route supports
- `limit` (integer, optional, ≥ 1, ≤ 100, default `20`): Results per page (max 100)
- `offset` (integer or null, optional, ≥ 0): Rows to skip before this page. Takes precedence over `page` when both are sent.

## Response

### `200`

```json
{
  "total": 1,
  "limit": 1,
  "offset": 1,
  "has_more": true,
  "leads": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "created_at": "2024-01-15T10:30:00+00:00",
      "annual_revenue": 5000000,
      "company": "Acme Corp",
      "company_website": "https://acmecorp.com",
      "custom_fields": {},
      "delivered_at": "2026-09-01T09:00:00+00:00",
      "email": "sarah.johnson@acmecorp.com",
      "employees": 150,
      "first_name": "Sarah",
      "industry": "Technology",
      "last_name": "Johnson",
      "linkedin_url": "https://linkedin.com/in/sarahjohnson",
      "phone_number": "+1-555-123-4567",
      "qualification_score": 85.5,
      "source": "lead_database",
      "title": "VP of Sales"
    }
  ],
  "count": 1,
  "page": 1,
  "success": true
}
```

- `total` (integer, required): Total rows matching the query, across all pages
- `limit` (integer, required): Page size that was applied
- `offset` (integer, required): Rows skipped before this page
- `has_more` (boolean, required): Whether a further page exists
- `leads` (array of objects, required): Array of leads
  - `id` (string, required, example `"a1b2c3d4-e5f6-7890-abcd-ef1234567890"`): Lead UUID
  - `created_at` (string · date-time, required, example `"2024-01-15T10:30:00+00:00"`): When the lead was created
  - `annual_revenue` (integer or null, optional, example `5000000`): Company annual revenue in dollars
  - `company` (string or null, optional, example `"Acme Corp"`): Company name
  - `company_website` (string or null, optional, example `"https://acmecorp.com"`): Lead's company website URL
  - `custom_fields` (object or null, optional): Custom fields as key-value pairs
  - `delivered_at` (string · date-time or null, optional, example `"2026-09-01T09:00:00+00:00"`): When a lead-database result was delivered to your workspace; null for leads from other sources
  - `email` (string or null, optional, example `"sarah.johnson@acmecorp.com"`): Lead's email address
  - `employees` (integer or null, optional, example `150`): Number of employees at the company
  - `first_name` (string or null, optional, example `"Sarah"`): Lead's first name
  - `industry` (string or null, optional, example `"Technology"`): Industry sector
  - `last_name` (string or null, optional, example `"Johnson"`): Lead's last name
  - `linkedin_url` (string or null, optional, example `"https://linkedin.com/in/sarahjohnson"`): Lead's LinkedIn profile URL
  - `phone_number` (string or null, optional, example `"+1-555-123-4567"`): Phone number
  - `qualification_score` (number or null, optional, example `85.5`): Lead qualification score (0-100)
  - `source` (string or null, optional, example `"lead_database"`): Where the lead came from: `lead_database` for a licensed lead-database result, `csv` for an upload, `manual` for one entered by hand, `linkedin_search` for a LinkedIn search; null for leads recorded before sources were tracked
  - `title` (string or null, optional, example `"VP of Sales"`): Job title
- `count` (integer, required): Leads in this page
- `page` (integer, required): Current page number. Prefer `offset`, which every list route returns
- `success` (boolean, optional, default `true`)

## Errors

Errors share one JSON body: `success`, `error`, `message`, optional `details`, and `request_id`. For example:

```json
{
  "success": false,
  "error": "UNAUTHORIZED",
  "message": "Invalid or inactive API key",
  "request_id": "734d11a1-54d4-414c-9e8d-4e1ada977db4"
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | The request didn't match the endpoint's schema: a missing or malformed field, a bad query parameter, or a value out of range. `details.errors` lists each failing field with its `field`, `message` and `type`. |
| 401 | `UNAUTHORIZED` | The `Authorization` header is missing or malformed, or the API key is unknown or has been deactivated. |
| 401 | `API_KEY_EXPIRED` | The API key is past its expiry date. Create a new key in the Victoria AI app. |
| 403 | `INSUFFICIENT_SCOPE` | The API key doesn't have the scope this endpoint requires, such as `leads:write`. |
| 403 | `ORGANIZATION_DEACTIVATED` | The organization that owns this API key has been deactivated. |
| 429 | `RATE_LIMITED` | Too many requests for this API key: more than 100 a minute to one endpoint, or 600 a minute in total. Retry after the number of seconds in the `Retry-After` header. See [Rate limits](https://docs.versionseven.ai/guides/rate-limits). |
| 500 | `INTERNAL_ERROR` | Something failed on our side. The response never includes internal details; quote its `request_id` when you contact support. |
| 503 | `UPSTREAM_TIMEOUT` | A service the API depends on timed out. The request is safe to retry. |
| 503 | `AUTH_UNAVAILABLE` | The API key couldn't be checked because the authentication service was unavailable. The request is safe to retry. |

## Response headers

| Header | Description |
| --- | --- |
| `X-Request-ID` | Correlation ID for the request, also returned as `request_id` in error bodies. Send your own `X-Request-ID`, up to 128 letters, digits, `.`, `_`, `:` or `-`, and it's used instead. |
| `RateLimit-Limit` | Requests allowed to this endpoint per minute. |
| `RateLimit-Remaining` | Requests you can still send to this endpoint right now. |
| `RateLimit-Reset` | Seconds until the endpoint's full limit is available again. |
| `Retry-After` | On a `429`: seconds to wait before retrying. |
