# Move Campaign Leads

> Move leads from a campaign that is not active into another campaign.

Requires an API key with the `write` scope.

Moves up to 5,000 leads from this campaign into another campaign in your workspace. The leads leave this campaign (their pending sends are cancelled and their progress here is dropped) and start the other campaign's sequence from its first step; if that campaign is active, it starts emailing them on its schedule. A lead that is already in the other campaign stays there and leaves this one. A lead whose address or domain is on the [block list](/api-reference/block-list/list-entries) is not added and stays here. Ids that are not in this campaign are ignored. The Python and TypeScript samples need the next SDK release: the published packages (PyPI `foxreach` 0.6.0, npm `foxreach` 0.5.0) do not have `move_leads` yet. This campaign must not be `active`: pause it first. The move runs in one transaction inside the call; send larger lists in batches. Each move is recorded in the [audit log](/api-reference/audit-log/list-audit-log) as `audit.campaign.leads_removed` on this campaign, with detail `moved`.


## Path Parameters

<ParamField path="campaign_id" type="string" required>The campaign the leads are in now.</ParamField>

## Request Body

<ParamField body="toCampaignId" type="string" required>The campaign to move the leads to. Must be another campaign in the same workspace.</ParamField>

<ParamField body="leadIds" type="string[]" required>1 to 5,000 lead ids.</ParamField>

## Response

<ResponseField name="data" type="object">
  <Expandable title="Result">
    <ResponseField name="moved" type="integer">Leads added to the other campaign and taken out of this one.</ResponseField>
    <ResponseField name="alreadyInTarget" type="integer">Leads that were already in the other campaign; they were taken out of this one.</ResponseField>
    <ResponseField name="blocked" type="integer">Leads on the block list; they stay in this campaign.</ResponseField>
    <ResponseField name="notInSource" type="integer">Ids that were not in this campaign; nothing was done for them.</ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
```bash cURL
curl -X POST "https://api.foxreach.io/api/v1/campaigns/cmcamp1/leads/move" \
  -H "X-API-Key: otr_your_key" \
  -H "Content-Type: application/json" \
  -d '{"toCampaignId": "cmcamp2", "leadIds": ["cmlead1", "cmlead2"]}'
```

```python Python SDK (beta)
from foxreach import FoxReach

client = FoxReach(api_key="otr_your_key")
result = client.campaigns.move_leads("cmcamp1", "cmcamp2", ["cmlead1", "cmlead2"])
```

```typescript TypeScript SDK (beta)
import { FoxReach } from "foxreach";

const client = new FoxReach({ apiKey: "otr_your_key" });
const result = await client.campaigns.moveLeads("cmcamp1", "cmcamp2", ["cmlead1", "cmlead2"]);
```
</RequestExample>

<ResponseExample>
```json
{
  "data": { "moved": 1, "alreadyInTarget": 1, "blocked": 0, "notInSource": 0 }
}
```
</ResponseExample>

## Errors

| Status | Description |
|--------|-------------|
| `401` | Missing, invalid, revoked or expired API key |
| `403` | API key does not have the required scope: `write` |
| `400` | This campaign is active |
| `404` | This campaign or the target campaign is not in this workspace |
| `422` | Empty list, more than 5,000 ids, or `toCampaignId` is this campaign |

## Rate Limit

- **100 requests per minute, shared by every `bulk` call in your workspace.** Fixed 60-second window.
- Every response includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (unix epoch seconds) and `X-RateLimit-Bucket` (`bulk`).
- A `429` response includes a `Retry-After` header (seconds until the bucket resets).

For client-side patterns (`Retry-After`, exponential backoff, monitoring `X-RateLimit-Remaining`), see [Rate Limiting](/rate-limiting).
