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

# List Campaign Leads

> Page through leads for a required campaign ID.

Requires `leads:read` and the **`campaignId` query parameter**. Optional `status` filters by the lead's current status; `full=true` requests the expanded lead shape, including contact information.

## Filtering by activity

A lead's `status` changes as its sequence moves on, so a lead that accepted your connection request usually no longer has the status `CONNECTION_ACCEPTED`. Use `activity` (lead had **every** listed event) and `excludeActivity` (lead had **none** of them) to filter by what has happened. Values: `CONNECTION_SENT`, `CONNECTION_ACCEPTED`, `MESSAGE_SENT`, `REPLY_RECEIVED`.

| You want | Query |
| - | - |
| Everyone you sent a request to | `activity=CONNECTION_SENT` |
| Everyone who accepted | `activity=CONNECTION_ACCEPTED` |
| Everyone who replied | `activity=REPLY_RECEIVED` |
| Accepted but not replied | `activity=CONNECTION_ACCEPTED&excludeActivity=REPLY_RECEIVED` |
| Accepted and messaged, no reply yet | `activity=CONNECTION_ACCEPTED,MESSAGE_SENT&excludeActivity=REPLY_RECEIVED` |

Values can be comma-separated or repeated (`activity=A&activity=B`). Each lead in the response still shows its current `status`. Unknown values, an empty list, or the same value in both `activity` and `excludeActivity` return `400`.

`page` defaults to 1 and is limited to 1–100. `limit` defaults to 50 and is limited to 1–100. Use the returned pagination metadata rather than assuming an unlimited workspace-wide lead list.

```bash theme={null}
curl 'https://api.sendpilot.ai/v1/leads?campaignId=CAMPAIGN_ID&page=1&limit=50&full=true' \
  -H "X-API-Key: $SENDPILOT_API_KEY"

# Accepted but not replied
curl 'https://api.sendpilot.ai/v1/leads?campaignId=CAMPAIGN_ID&activity=CONNECTION_ACCEPTED&excludeActivity=REPLY_RECEIVED' \
  -H "X-API-Key: $SENDPILOT_API_KEY"
```


## OpenAPI

````yaml GET /v1/leads
openapi: 3.0.0
info:
  title: SendPilot External API
  description: >

    # SendPilot External API v1


    Public API for integrating with SendPilot programmatically.


    ## Authentication


    Requests authenticate with a workspace API key in the `X-API-Key` header:


    ```

    X-API-Key: sp_live_your_api_key_here

    ```


    `Authorization: Bearer sp_live_...` (or `sp_test_...`) is also accepted.
    Prefer one header; `X-API-Key` takes precedence. Clerk OAuth tokens for MCP
    connection URLs are not public REST API keys.


    A key only reaches what its scopes and creator's current workspace
    permissions allow. Each operation lists the scopes it needs in
    `x-sp-scopes`. Use `GET /v1/me` to inspect the effective permissions and
    subscription state.


    ## Rate Limits


    - **Per API Key**: 300 requests/minute

    - **Per Workspace**: 50,000 requests/day


    Responses that reach the rate limiter include these headers; early
    authentication failures may not:

    - `X-RateLimit-Limit`: Max requests per minute

    - `X-RateLimit-Remaining`: Remaining requests in current window

    - `X-RateLimit-Reset`: Epoch timestamp when limit resets


    ## LinkedIn Sender Limits


    When using messaging endpoints, these headers show account limits:

    - `X-LinkedIn-Messages-Remaining`: Messages left today

    - `X-LinkedIn-Connections-Remaining`: Connection requests left today

    - `X-LinkedIn-Quota-Reset-At`: When daily limits reset


    ## Retries


    Send an `Idempotency-Key` header on POST and PATCH requests. A retry with
    the same key within 24 hours returns the first result (with
    `Idempotent-Replayed: true`) instead of acting twice. The same key with a
    different request answers 422 `IDEMPOTENCY_KEY_REUSED`; a retry while the
    first request still runs answers 409 `IDEMPOTENCY_IN_PROGRESS`.


    Once execution is attempted, its response (including errors) is retained. If
    a process stops or result storage fails after the attempt, the durable
    record blocks another execution for the replay window and returns 409
    `IDEMPOTENCY_IN_PROGRESS` or `IDEMPOTENCY_REPLAY_UNAVAILABLE`. Check the
    operation's status before using a new key: an uncertain outcome may already
    have acted. Requests refused before execution is claimed, such as
    authentication or rate-limit refusals, can be retried with the same key.


    ## Webhooks


    Configure webhooks in the SendPilot app to receive real-time events:

    - `lead.tag.updated`

    - `lead.updated`

    - `message.sent`

    - `reply.received`

    - `connection_request.sent`

    - `connection_request.accepted`

    - `campaign.started`

    - `campaign.paused`

    - `campaign.resumed`

    - `campaign.finished`


    ## Error Codes


    Errors return a JSON body. Use the machine-readable `code` when supplied;
    validation messages can be arrays, and some legacy errors omit `code`. A
    typical error is:

    ```json

    {
      "statusCode": 400,
      "error": "Bad Request",
      "code": "SPECIFIC_ERROR_CODE",
      "message": "Human-readable description"
    }

    ```
  version: '1.0'
  contact:
    name: SendPilot Support
    url: https://sendpilot.ai
    email: support@sendpilot.ai
servers:
  - url: https://api.sendpilot.ai
security: []
tags: []
paths:
  /v1/leads:
    get:
      tags:
        - External API - Leads
      summary: List leads
      description: List leads with optional filtering by campaign and status
      operationId: leads.list
      parameters:
        - name: campaignId
          required: true
          in: query
          description: Campaign ID (required)
          schema:
            example: cmobr5lei0000u1h02ezf5itt
            type: string
        - name: status
          required: false
          in: query
          description: >-
            Lead's current status. To find who accepted, was messaged or
            replied, use `activity`.
          schema:
            $ref: '#/components/schemas/LeadStatus'
        - name: activity
          required: false
          in: query
          description: >-
            Only leads that had every listed event, whatever their current
            status. Comma-separated, e.g.
            `activity=CONNECTION_ACCEPTED&excludeActivity=REPLY_RECEIVED` =
            accepted but not replied.
          schema:
            example: CONNECTION_ACCEPTED
            type: array
            items:
              type: string
              enum:
                - CONNECTION_SENT
                - CONNECTION_ACCEPTED
                - MESSAGE_SENT
                - REPLY_RECEIVED
        - name: excludeActivity
          required: false
          in: query
          description: Only leads that had none of the listed events. Comma-separated.
          schema:
            example: REPLY_RECEIVED
            type: array
            items:
              type: string
              enum:
                - CONNECTION_SENT
                - CONNECTION_ACCEPTED
                - MESSAGE_SENT
                - REPLY_RECEIVED
        - name: full
          required: false
          in: query
          description: 'Return full lead data including all dynamic fields (default: false)'
          schema:
            default: false
            example: true
            type: boolean
        - name: page
          required: false
          in: query
          description: Page number for pagination (1-indexed)
          schema:
            minimum: 1
            maximum: 100
            default: 1
            type: number
        - name: limit
          required: false
          in: query
          description: Maximum number of items to return
          schema:
            minimum: 1
            maximum: 100
            default: 50
            type: number
      responses:
        '200':
          description: List of leads
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeadListResponseDto'
      security:
        - api-key: []
components:
  schemas:
    LeadStatus:
      type: string
      description: Lead status
      enum:
        - PENDING
        - PROCESSING
        - MESSAGE_SENT
        - CONNECTION_SENT
        - CONNECTION_ACCEPTED
        - CONNECTION_ALREADY_SENT
        - REPLY_RECEIVED
        - FOLLOWUP_SENT
        - BLOCKED
        - PROFILE_UNREACHABLE
        - RATE_LIMITED
        - FAILED
        - SUCCESS
        - UNSUBSCRIBED
        - IRRELEVANT
        - SKIPPED
        - DONE
        - MEETING_BOOKED
        - OPPORTUNITY
        - LIKED_POST
        - MESSAGE_SCHEDULED
        - CONNECTION_SCHEDULED
        - LIKE_POST_SCHEDULED
        - VIEW_PROFILE_SCHEDULED
        - CONNECTED
        - CONNECTION_WITHDRAWN
        - WITHDRAWAL_SCHEDULED
        - WITHDRAWAL_NOT_POSSIBLE
        - NOT_CONNECTED
        - ICP_MATCH
        - ICP_NOT_MATCH
        - PROFILE_VIEWED
        - STARTED
        - STOPPED
        - WAITING
    LeadListResponseDto:
      type: object
      properties:
        leads:
          description: List of leads
          type: array
          items:
            $ref: '#/components/schemas/LeadSummaryDto'
        pagination:
          description: Pagination info
          allOf:
            - $ref: '#/components/schemas/PaginationDto'
      required:
        - leads
        - pagination
    LeadSummaryDto:
      type: object
      properties:
        id:
          type: string
          description: Lead ID
        linkedinUrl:
          type: string
          description: LinkedIn URL
        firstName:
          type: string
          description: First name
        lastName:
          type: string
          description: Last name
        company:
          type: string
          description: Company
        title:
          type: string
          description: Title
        status:
          $ref: '#/components/schemas/LeadStatus'
        customLeadStatus:
          type: string
          description: Custom lead status
        campaignId:
          type: string
          description: Campaign ID the lead belongs to
        senderId:
          type: string
          description: >-
            LinkedIn sender assigned to this lead (use as senderId for
            /v1/inbox/send); null until the campaign assigns one
          nullable: true
        createdAt:
          format: date-time
          type: string
          description: When the lead was added
      required:
        - id
        - linkedinUrl
        - status
        - customLeadStatus
        - campaignId
        - createdAt
    PaginationDto:
      type: object
      properties:
        page:
          type: number
          description: Current page number
          example: 1
        limit:
          type: number
          description: Items per page
          example: 20
        total:
          type: number
          description: Total number of items
          example: 150
        totalPages:
          type: number
          description: Total number of pages
          example: 8
      required:
        - page
        - limit
        - total
        - totalPages
  securitySchemes:
    api-key:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'API key for authentication (prefix: sp_live_ or sp_test_)'

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.