> ## Documentation Index
> Fetch the complete documentation index at: https://lupitor-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List Leads

> Retrieve leads for a campaign with optional filtering

<Note>
  **Endpoint Alias**: You can also use `/api/v1/records` instead of `/api/v1/leads`. Both endpoints are functionally identical - use whichever naming convention fits your integration.
</Note>

## Authentication

<ParamField header="x-api-key" type="string" required>
  Your API key with `read` or `write` scope
</ParamField>

## Query Parameters

<ParamField query="campaignId" type="string" required>
  The ID of the campaign to list leads for
</ParamField>

<ParamField query="status" type="string">
  Filter leads by status

  **Options:**

  * `raw` - Never been called
  * `attempting` - Currently being called
  * `exhausted` - Max attempts reached
  * `concluded` - Successfully completed
</ParamField>

<ParamField query="limit" type="number">
  Maximum number of leads to return per request. Default: `1000`, Max: `8000`
</ParamField>

<ParamField query="cursor" type="string">
  Pagination cursor from a previous response. Use this to fetch the next page of results.
</ParamField>

## Response

<ResponseField name="success" type="boolean">
  Whether the request was successful
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="properties">
    <ResponseField name="leads" type="array">
      Array of lead objects

      <Expandable title="Lead Object">
        <ResponseField name="_id" type="string">
          Unique lead ID
        </ResponseField>

        <ResponseField name="campaignId" type="string">
          Campaign this lead belongs to
        </ResponseField>

        <ResponseField name="phoneNumber" type="string">
          Lead's phone number
        </ResponseField>

        <ResponseField name="externalId" type="string">
          Your CRM's ID (if provided)
        </ResponseField>

        <ResponseField name="metadata" type="object">
          Custom data about the lead
        </ResponseField>

        <ResponseField name="status" type="string">
          Current lead status
        </ResponseField>

        <ResponseField name="scheduledFor" type="string">
          When to call this lead (ISO 8601)
        </ResponseField>

        <ResponseField name="priority" type="number">
          Lead priority (0-100)
        </ResponseField>

        <ResponseField name="attempts" type="number">
          Number of call attempts made
        </ResponseField>

        <ResponseField name="_creationTime" type="number">
          Unix timestamp of creation
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="count" type="number">
      Number of leads returned in this page
    </ResponseField>

    <ResponseField name="cursor" type="string">
      Pagination cursor. Pass this to the next request to get more results.
    </ResponseField>

    <ResponseField name="hasMore" type="boolean">
      Whether there are more leads to fetch
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="error" type="string | null">
  Error message if request failed
</ResponseField>

<RequestExample>
  ```bash cURL - First Page theme={null}
  curl "https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads?campaignId=YOUR_CAMPAIGN_ID&limit=1000" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```bash cURL - Next Page (with cursor) theme={null}
  curl "https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads?campaignId=YOUR_CAMPAIGN_ID&limit=1000&cursor=CURSOR_FROM_PREVIOUS" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```bash cURL - Filtered by Status theme={null}
  curl "https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads?campaignId=YOUR_CAMPAIGN_ID&status=raw" \
    -H "x-api-key: YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  async function getAllLeads(campaignId, status) {
    const allLeads = [];
    let cursor = null;

    do {
      const params = new URLSearchParams({ campaignId, limit: '1000' });
      if (status) params.append('status', status);
      if (cursor) params.append('cursor', cursor);

      const response = await fetch(
        `https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads?${params}`,
        { headers: { 'x-api-key': 'YOUR_API_KEY' } }
      );

      const data = await response.json();
      allLeads.push(...data.data.leads);
      cursor = data.data.hasMore ? data.data.cursor : null;
    } while (cursor);

    return allLeads;
  }
  ```

  ```python Python theme={null}
  import requests

  def get_all_leads(campaign_id, status=None):
      all_leads = []
      cursor = None

      while True:
          params = {'campaignId': campaign_id, 'limit': 1000}
          if status:
              params['status'] = status
          if cursor:
              params['cursor'] = cursor

          response = requests.get(
              'https://YOUR_DEPLOYMENT.lupitor.com/api/v1/leads',
              params=params,
              headers={'x-api-key': 'YOUR_API_KEY'}
          )

          data = response.json()['data']
          all_leads.extend(data['leads'])

          if not data['hasMore']:
              break
          cursor = data['cursor']

      return all_leads
  ```
</RequestExample>

<ResponseExample>
  ```json Success Response theme={null}
  {
    "success": true,
    "data": {
      "leads": [
        {
          "_id": "jh7abc123xyz",
          "campaignId": "YOUR_CAMPAIGN_ID",
          "phoneNumber": "+15555551234",
          "externalId": "CRM_12345",
          "metadata": {
            "name": "John Doe",
            "company": "Acme Corp"
          },
          "status": "raw",
          "scheduledFor": "2024-12-25T09:00:00Z",
          "priority": 10,
          "attempts": 0,
          "_creationTime": 1703520000000
        },
        {
          "_id": "jh7abc456def",
          "campaignId": "YOUR_CAMPAIGN_ID",
          "phoneNumber": "+15555555678",
          "externalId": "CRM_67890",
          "metadata": {
            "name": "Jane Smith",
            "company": "Tech Inc"
          },
          "status": "raw",
          "priority": 5,
          "attempts": 0,
          "_creationTime": 1703520100000
        }
      ],
      "count": 2,
      "cursor": "eyJwb3NpdGlvbiI6Mn0=",
      "hasMore": true
    },
    "error": null
  }
  ```

  ```json Error Response theme={null}
  {
    "success": false,
    "data": null,
    "error": "campaignId query parameter is required"
  }
  ```
</ResponseExample>

## Lead Status Lifecycle

```mermaid theme={null}
graph LR
    A[raw] --> B[attempting]
    B --> C[exhausted]
    B --> D[concluded]
    C --> B
```

<Info>
  **Status Meanings:**

  * **raw**: Lead has never been called
  * **attempting**: Lead is currently in the calling queue
  * **exhausted**: Max call attempts reached without success
  * **concluded**: Call completed successfully
</Info>

## Common Errors

| Error                                    | Cause                       | Solution                            |
| ---------------------------------------- | --------------------------- | ----------------------------------- |
| `campaignId query parameter is required` | Missing campaignId          | Add campaignId to query string      |
| `Invalid or inactive API key`            | Wrong API key               | Check your API key                  |
| `Campaign not found or access denied`    | Wrong campaign or no access | Verify campaignId and API key scope |

## Usage Tips

<Tip>
  **Filtering**: Use the `status` parameter to focus on specific subsets:

  * `status=raw` - See leads that haven't been called yet
  * `status=attempting` - Monitor active calls
  * `status=concluded` - Review completed calls
</Tip>

## Pagination

This endpoint supports cursor-based pagination for handling large datasets efficiently.

<Info>
  **Limits:**

  * Default: 1,000 leads per request
  * Maximum: 8,000 leads per request

  For campaigns with more than 8,000 leads, use the `cursor` parameter to fetch additional pages.
</Info>

<Tip>
  **Best Practice:** When fetching all leads for large campaigns, loop through pages using the `cursor` until `hasMore` is `false`.
</Tip>
