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

# Managing campaigns

> Increase or decrease budget, update geographic tiers, toggle auto-renew, and stop wallet campaigns via the API.

Once a campaign is running, the API lets you adjust its daily budget, reallocate geographic tiers, toggle auto-renew, and stop delivery. All management endpoints require the `campaigns:write` scope and work on **wallet-funded campaigns only**.

## Which campaigns can you manage?

Campaign list and detail responses include a `generation` field:

| Generation | Meaning | Manageable via API? |
| - | - | - |
| `1` | Stripe / one-time / subscription / legacy | No |
| `2` | Soundlink managed | No |
| `3` | Wallet-funded | Yes — budget, tiers, auto-renew, stop |

Calling a management endpoint on a non-generation-`3` campaign returns `400 invalid_request` (for example, "Stopping is only supported for wallet campaigns.").

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS 'https://api.getsoundlink.com/v1/campaigns/CAMPAIGN_ID' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET'
# → check data.generation === 3 before managing
```

<Note>
  TypeScript: `soundlink.campaigns.increaseBudget` / `decreaseBudget` / `tiers` / `stop` —
  see [TypeScript SDK](/docs/typescript-sdk#create-and-manage-wallet-campaigns).
</Note>

<Note>
  Stop, budget, and auto-renew endpoints require the same [`Idempotency-Key`
  header](/docs/creating-campaigns#idempotency) as campaign creation: unique string up to 255
  characters, honored for 24 hours, same key + same body replays the original response.
</Note>

## Increase budget

`POST /v1/campaigns/{campaignId}/budget/increase`

Adds budget using credits already in your wallet — there is no new card payment.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X POST 'https://api.getsoundlink.com/v1/campaigns/CAMPAIGN_ID/budget/increase' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET' \
  -H 'Idempotency-Key: budget-inc-2026-07-21-01' \
  -H 'Content-Type: application/json' \
  -d '{ "amount": 100, "mode": "current_and_renewals" }'
```

| Mode | Effect |
| - | - |
| `current_cycle` | Spreads `amount` across the remaining days of this cycle; debits the wallet |
| `next_renewal` | Sets a higher daily budget starting next cycle; no wallet debit now |
| `current_and_renewals` (default) | Both: raises the current cycle **and** keeps the higher daily budget on future renewals |

* `amount` is the total USD debit for the current cycle. With `mode: next_renewal` you may send `amount: 0` together with `targetDailyBudget` to set an absolute next-cycle daily budget.
* The wallet is debited whenever the current cycle is increased. If the balance is too low, you get `402 insufficient_credit` with `details.available` and `details.required`.

The response returns the wallet balance after the debit:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "campaignId": "campaign-uuid",
    "mode": "current_and_renewals",
    "amount": 100,
    "walletBalance": 250,
    "walletNextCycleDailyBudget": 35
  },
  "meta": { "requestId": "..." }
}
```

## Decrease budget

`POST /v1/campaigns/{campaignId}/budget/decrease`

Lowers the campaign's daily budget to `targetDailyBudget` and refunds unused budget to your wallet.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X POST 'https://api.getsoundlink.com/v1/campaigns/CAMPAIGN_ID/budget/decrease' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET' \
  -H 'Idempotency-Key: budget-dec-2026-07-21-01' \
  -H 'Content-Type: application/json' \
  -d '{ "targetDailyBudget": 15, "mode": "current_and_renewals" }'
```

Rules (violations return `400 invalid_request`):

* `targetDailyBudget` must be at least **\$10/day** and **lower** than the current daily budget
* More than **3 days** must remain in the current billing cycle
* Modes: `current_cycle` or `current_and_renewals` (default) — `next_renewal` is **not** supported for decreases

A success response returns `accepted: true`. The refund of unused budget is applied to your wallet **asynchronously** after acceptance — it may take a moment to appear in your balance.

## Update geographic tiers

`GET /v1/campaigns/{campaignId}/tiers` (requires `campaigns:read`) returns the current allocation:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "lastUpdate": "2026-07-18T10:00:00Z",
    "tiers": [
      { "tierId": 1, "tierName": "US + UK", "isEnabled": true, "allocationPercent": 60 },
      { "tierId": 2, "tierName": "DACH", "isEnabled": true, "allocationPercent": 40 }
    ]
  },
  "meta": { "requestId": "..." }
}
```

`PATCH /v1/campaigns/{campaignId}/tiers` enables/disables tiers and reallocates budget shares:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X PATCH 'https://api.getsoundlink.com/v1/campaigns/CAMPAIGN_ID/tiers' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "items": [
      { "targetingTierId": 1, "isEnabled": true, "newAllocationPercent": 100 },
      { "targetingTierId": 2, "isEnabled": false, "newAllocationPercent": 0 }
    ]
  }'
```

Request rules:

* Include **all** of the campaign's tiers in `items` — not just the ones you change
* `newAllocationPercent` values must sum to exactly **100**
* At least one tier must have `isEnabled: true`

<Warning>
  After a successful update, further tier changes on that campaign are blocked for **72 hours**.
  During the cooldown, `PATCH` returns `409 tier_update_cooldown`. Use `lastUpdate` from the `GET`
  response to know when the last change happened.
</Warning>

## Toggle auto-renew

`PUT /v1/campaigns/{campaignId}/auto-renew`

Turns auto-renew on or off. Wallet campaigns only. Allowed when status is `active` or `renew_failed`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X PUT 'https://api.getsoundlink.com/v1/campaigns/CAMPAIGN_ID/auto-renew' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET' \
  -H 'Idempotency-Key: autorenew-2026-07-21-01' \
  -H 'Content-Type: application/json' \
  -d '{ "autoRenew": true }'
```

Expected (`200`):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": { "campaignId": "campaign-uuid", "autoRenew": true },
  "meta": { "requestId": "..." }
}
```

A matching stored value succeeds with no change.

## Read wallet balance

`GET /v1/wallet` · scope `wallet:read`

Returns the organization wallet. `available` is what `POST /v1/campaigns` checks before reserving. `reserved` is the sum of active reservations. Existing keys do not include `wallet:read` until that scope is granted.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS 'https://api.getsoundlink.com/v1/wallet' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET'
```

Expected (`200`):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": {
    "balance": 500,
    "available": 350,
    "reserved": 150,
    "currency": "USD",
    "updatedAt": "2026-04-01T10:00:00Z"
  },
  "meta": { "requestId": "..." }
}
```

See [GET /v1/wallet](/docs/api-reference/get-v1-wallet) for the full response.

## Stop a campaign

`POST /v1/campaigns/{campaignId}/stop`

Stops delivery and refunds the unspent budget to your wallet.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -sS -X POST 'https://api.getsoundlink.com/v1/campaigns/CAMPAIGN_ID/stop' \
  -H 'Authorization: Bearer sk_YOUR_PREFIX_YOUR_SECRET' \
  -H 'Idempotency-Key: stop-2026-07-21-01'
```

Expected (`200`):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "data": { "campaignId": "campaign-uuid", "status": "stopped" },
  "meta": { "requestId": "..." }
}
```

These statuses accept stop and return `stopped`: `active`, `inactive`, `restarting`, `restart_failed`, `renew_failed`, and `renew_awaiting_charge`.

`stopped` and `ended` succeed without changing status. The response `status` stays `stopped` or `ended`, and a second stop does not refund again.

`creating` and `renewing` return `400 invalid_request`. Retry after the campaign leaves that status.

Stopped campaigns cannot be restarted via the API — create a new campaign instead.

## Common management errors

| HTTP | Code | When |
| - | - | - |
| 400 | `invalid_request` | Non-wallet campaign, invalid body, decrease rule violated, or stop while `creating` or `renewing` |
| 400 | `invalid_tier_status` | Tier items invalid (missing tiers, sum ≠ 100, none enabled) |
| 402 | `insufficient_credit` | Wallet balance too low for the budget increase |
| 404 | `campaign_not_found` | Unknown campaign or a campaign in another organization |
| 409 | `tier_update_cooldown` | Tier update within 72 hours of the previous one |
| 409 | `idempotency_key_conflict` | `Idempotency-Key` reused with a different body, or still processing |

See [Errors](/docs/errors) for the full list.

## Next

[Creating campaigns](/docs/creating-campaigns) · [Campaign metrics](/docs/understanding-metrics) · [Credit wallet](/docs/learn/wallet)


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