---
title: "Add up to 500 leads to a campaign"
method: POST
path: "/api/v1/leads/bulk"
tags: ["Leads"]
---

# Add up to 500 leads to a campaign

`POST /api/v1/leads/bulk`

## Request

- Base URL: `https://www.zen-mode.io`
- URL: `https://www.zen-mode.io/api/v1/leads/bulk`
- Auth: HTTP bearer

One campaign, up to 500 items, one result per item in input order. Each item is judged exactly as POST /api/v1/leads would judge it on its own, plus `duplicate_in_request` for a person who appears earlier in the same request. The request is 200 whenever it is valid, even if every item was refused. A retry is safe: an item that already landed comes back `duplicate`. The approval check runs once for the whole request and only if at least one item is about to be added. Limits: 100 requests/minute per key (shared with every v1 route) plus 2,000 items/minute per key; both are per server instance.

## Request body

- object
  - `campaign_id` integer, required
  - `items` object[], required
    - `linkedin_url` string, required
    - `name` string, nullable
    - `title` string, nullable
    - `company` string, nullable
    - `location` string, nullable
    - `messages` object
      - `connection_request` string
      - `follow_up_1` string
      - `follow_up_2` string
      - `follow_up_3` string

## Response `200`

Processed. `summary` counts outcomes; `results[i].http_status` is what POST /api/v1/leads would have returned for that item alone; `delivery` describes the campaign once for the whole request.

- object
  - `campaign_id` integer
  - `summary` object
    - `received` integer
    - `created` integer
    - `duplicate` integer
    - `duplicate_in_request` integer
    - `suppressed` integer
    - `excluded` integer
    - `refused` integer
    - `invalid` integer
  - `results` object[]
    - `index` integer, required
    - `outcome` 'created' | 'duplicate' | 'duplicate_in_request' | 'suppressed' | 'excluded' | 'refused' | 'invalid', required
    - `http_status` integer, required
    - `code` string
    - `error` string
    - `hint` string
    - `lead` union
      - Lead
        - `id` integer
        - `campaign_id` integer
        - `name` string, nullable
        - `title` string, nullable
        - `company` string, nullable
        - `location` string, nullable
        - `linkedin_url` string
        - `status` 'pending' | 'warming_up' | 'sent' | 'connected' | 'replied' | 'meeting_booked' | 'not_interested' | 'failed' | 'no_response' | 'stale_connection' | 'disconnected' | 'connection_removed' | 'messaging_closed' | 'calendar_clicked' | 'invitation_withdrawn'
        - `follow_up_number` integer
        - `calendar_clicked` boolean
        - `connection_sent_at` string, date-time, nullable
        - `connection_accepted_at` string, date-time, nullable
        - `replied_at` string, date-time, nullable
        - `created_at` string, date-time, nullable
        - `updated_at` string, date-time, nullable
      - object
        - `id` integer
        - `campaign_id` integer
        - `status` string
        - `removed` boolean
    - `identity` object
      - `linkedin_slug` string, nullable
      - `sales_lead_id` string, nullable
    - `scope` 'all_campaigns' | 'this_campaign', nullable
    - `matched_on` 'company' | 'url' | 'title'
    - `linkedin_url` string
    - `first_index` integer
  - `delivery` object
    - `state` 'paused_by_this_push' | 'campaign_not_active' | 'awaiting_approval' | 'unknown' | 'no_desktop_seen' | 'desktop_idle' | 'queued_running'
    - `reasons` string[]
    - `campaign` object
      - `status` string, nullable
      - `approval_state` string, nullable
      - `linkedin_account_id` integer, nullable
    - `desktop` object
      - `state` 'active' | 'went_quiet' | 'never_seen', nullable
      - `last_seen_at` string, date-time, nullable
      - `age_seconds` integer, nullable
      - `app_version` string, nullable
      - `account_running` boolean, nullable
      - `running_campaign_id` integer, nullable
      - `works_all_active_campaigns_on_account` boolean, nullable
    - `checked_at` string, date-time

## Other responses

- `400`: Malformed JSON, `invalid_campaign_id`, `items_required` (missing or empty) or `too_many_items` (more than 500).
- `401`: Missing, malformed or invalid Authorization header, or an unknown API key.
- `403`: The key is valid but the account's plan does not include API access.
- `404`: Campaign not found for this account.
- `429`: `rate_limit_exceeded` (100 requests/minute) or `item_rate_limit_exceeded` (2,000 items/minute; nothing in the request was processed). Both carry `Retry-After`.
- `500`: Unhandled server error. Some items may have been added; retrying is safe.
- `503`: `approval_check_failed`: the campaign's approval state could not be confirmed, so nothing in the request was added. Retry the whole request with backoff.

---

[API](https://skmtc.dev/zen-mode/apis/zenmode-api.md) · [All operations](https://skmtc.dev/zen-mode/apis/zenmode-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zen-mode/apis/zenmode-api/revisions/2769372f1e3f?raw)
