Campaign Management API
Use the Campaign Management API to manage your Pushnami Ads account from your own scripts, tools or AI agents. It covers the changes advertisers make most often in the dashboard:
- Campaigns: create, clone, rename, pause, activate and archive. Change bids, daily spend limits, target CPA, audience and schedule.
- Creatives: create, edit, clone, pause, activate and archive push creatives. Upload images and icons.
- Sources: set bids for many sources at once, and block, unblock, allow or disallow sources.
The same actions are available as an MCP server, so you can connect Claude, Cursor or another MCP-capable agent to your account. If you want an agent to set itself up, point it at the Agent Guide.
- Billing: adding funds, payment methods, auto-funding and invoices stay in the dashboard. You can check your balance, but not change it.
- Reporting: for spend and performance data, use the Spend Report API.
Quick Start
1. Create an API key. In the dashboard, open API Access in the side menu and select Create Key. Give it a name, choose Read only or Read & write, and confirm your password. The key is shown only once, so copy it then.
You can also create a key from the command line with your Pushnami Ads username and password:
curl -u 'you@example.com:your-password' \
-H 'Content-Type: application/json' \
-d '{"name": "my first key", "scope": "write"}' \
https://ads-api.pushnami.com/v1/api-keys
{
"key": "pnk_3f9c1a0b2d4e6f8a1b2c3d4e_Xk2...",
"key_id": "3f9c1a0b2d4e6f8a1b2c3d4e",
"name": "my first key",
"scope": "write",
"expires_at": "2027-09-24T15:02:11.000Z"
}
Copy key from the response. It isn't shown again. The key lasts 365 days unless you add "expires_in_days": 30 or 90.
2. Call the API with the key as a Bearer token.
curl -H 'Authorization: Bearer pnk_...' \
https://ads-api.pushnami.com/v1/campaigns
API Keys
| Property | Details |
|---|---|
| Format | pnk_ followed by a key ID and a secret |
| Scope | read (view only) or write (view and change). Defaults to read. |
| Lifetime | 30, 90 or 365 days, chosen when you create the key. The default is 365. |
| Limit | 10 active keys per account |
| Sending it | Authorization: Bearer pnk_... (or X-API-Key: pnk_...) |
A key acts as your account and nothing else. It can never see or change another advertiser's campaigns.
Expiry and Renewal
Renew a key on the API Access page in the dashboard by entering your password again. The key string stays the same, so nothing that uses it needs to change. You can renew a key before or after it expires, but not after it is revoked. Renewing an expired key counts toward the limit of 10 active keys.
Every response to a request made with a key tells you when the key expires:
| Header | When | Value |
|---|---|---|
Pushnami-Api-Key-Expires-At | Always | The expiry time, for example 2027-09-24T15:02:11.000Z |
Pushnami-Api-Key-Expiry-Warning | In the last 14 days | For example Key expires in 5 days. Renew it on the API Access page. |
Over MCP, tool results carry the same warning as key_expiry_warning in the last 14 days.
We also email the account's alert address (or its contact address if it has none) 14 days and 3 days before a key expires.
A key also stops working when your Pushnami Ads login is disabled, when you are removed from the account, or when your password is reset.
Using keys safely
- Give each script or agent its own key, so you can revoke one without breaking the others.
- Use a
readkey for anything that only needs to look, such as a monitoring agent or a dashboard. - Keep keys out of source control and chat messages. Store them in a secrets manager or environment variable.
- If a key leaks, revoke it right away (see Manage Keys).
Manage Keys
The API Access page in the dashboard lists your keys with when each was created, last used and expires, and lets you revoke any of them.
You can do the same over the API. Listing returns only metadata, never the key itself.
curl -H 'Authorization: Bearer pnk_...' https://ads-api.pushnami.com/v1/api-keys
Revoke a key with any write key, or with the key itself:
curl -X DELETE -H 'Authorization: Bearer pnk_...' \
https://ads-api.pushnami.com/v1/api-keys/3f9c1a0b2d4e6f8a1b2c3d4e
If you've lost the key, revoke it with your login instead:
curl -X DELETE -u 'you@example.com:your-password' \
https://ads-api.pushnami.com/v1/api-keys/3f9c1a0b2d4e6f8a1b2c3d4e
You can't create a key from a login that needs an extra sign-in step, such as MFA or a required password change. Finish that step in the dashboard first. If your organization requires MFA, contact your account representative.
Conventions
- Base URL:
https://ads-api.pushnami.com - IDs: campaigns look like
C0001234, creatives likeC0001234-001. Sources use the same website IDs as thesource_idcolumn in the Spend Report API. - Money: USD. Bids are per click.
- Dates: RFC 3339, for example
2026-10-01T00:00:00Z. - Requests: JSON bodies. Unknown fields are rejected, so a typo produces an error instead of being ignored.
- Lists: use
limit(1–100, default 25) andoffset. Responses includepagination.total. - Machine-readable spec:
/v1/openapi.json(OpenAPI 3.1). - Usage analytics: as in the dashboard, we record which operations each key calls, whether they succeed, how long they take, how many sources or creatives they touch, and your client's user agent (for MCP, also the client's name and version). We use this to improve the API. We don't record the values you send.
Endpoints
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /v1/me | read | The account and scope of your key |
| GET | /v1/limits | read | Today's usage of your daily limits |
| GET | /v1/balance | read | Your balance and how long it will last |
| GET | /v1/campaigns | read | List campaigns |
| POST | /v1/campaigns | write | Create a campaign |
| GET | /v1/campaigns/{campaign_id} | read | Get a campaign, including default bid and delivery status |
| PATCH | /v1/campaigns/{campaign_id} | write | Change bid, budget, target CPA, audience, schedule or name |
| POST | /v1/campaigns/{campaign_id}/status | write | Pause, activate or archive |
| POST | /v1/campaigns/{campaign_id}/clone | write | Clone a campaign |
| GET | /v1/campaigns/{campaign_id}/creatives | read | List a campaign's creatives |
| POST | /v1/campaigns/{campaign_id}/creatives | write | Add up to 20 creatives |
| GET | /v1/creatives/{creative_id} | read | Get a creative |
| PATCH | /v1/creatives/{creative_id} | write | Edit a creative |
| POST | /v1/creatives/{creative_id}/status | write | Pause, activate or archive |
| POST | /v1/creatives/{creative_id}/clone | write | Clone a creative |
| POST | /v1/images | write | Upload an image or icon |
| GET | /v1/campaigns/{campaign_id}/sources | read | Source targeting mode, blocked/allowed sources, bid overrides |
| POST | /v1/campaigns/{campaign_id}/sources/bids | write | Set bids for up to 50 sources |
| POST | /v1/campaigns/{campaign_id}/sources/block | write | Block sources |
| POST | /v1/campaigns/{campaign_id}/sources/unblock | write | Unblock sources |
| POST | /v1/campaigns/{campaign_id}/sources/allow | write | Allow sources (allow-list campaigns) |
| POST | /v1/campaigns/{campaign_id}/sources/disallow | write | Remove allowed sources |
Campaigns
List Campaigns
curl -H 'Authorization: Bearer pnk_...' \
'https://ads-api.pushnami.com/v1/campaigns?status=active,paused&search=summer&limit=50'
status accepts active, paused, archived and rejected. The default is active,paused.
Get a Campaign
curl -H 'Authorization: Bearer pnk_...' https://ads-api.pushnami.com/v1/campaigns/C0001234
{
"id": "C0001234",
"name": "Summer Sweepstakes",
"status": "active",
"delivery_status": "daily_limit_reached",
"created_at": "2026-08-01T14:00:00.000Z",
"updated_at": "2026-09-20T09:12:44.000Z",
"default_bid": 0.25,
"daily_spend_limit": 500,
"target_cpa": 12,
"source_optimization": true,
"source_mode": "block",
"active_creative_count": 4,
"audience": { "country": "US", "platforms": ["MOBILE"], "subscribed_more_than_minutes_ago": 1440 },
"schedule": { "start_date": "2026-08-01T00:00:00.000Z", "end_date": null, "active_hours": [] }
}
status is what you set. delivery_status explains whether the campaign is serving right now:
| delivery_status | Meaning |
|---|---|
delivering | Serving |
delivering_with_rejected_creatives | Serving, but some creatives were rejected |
paused / archived | Paused or archived |
waiting_for_funds | The account needs funds |
waiting_for_creatives | No active creatives, for example none yet, or all of them pending, draft or paused |
all_creatives_rejected | Every creative was rejected |
scheduled | The start date is in the future |
ending_soon | The end date is within 48 hours |
near_daily_limit / daily_limit_reached | 85% or 100% of today's spend limit is used |
rejected | The campaign was rejected by review |
If the delivery status can't be read at that moment, delivery_status and active_creative_count are left out of the response. Read the campaign again shortly.
Create a Campaign
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns \
-d '{
"name": "Fall Promo - Mobile US",
"default_bid": 0.20,
"daily_spend_limit": 250,
"audience": { "country": "US", "platforms": ["MOBILE", "TABLET"] },
"start_date": "2026-10-01T05:00:00Z",
"status": "paused"
}'
| Field | Required | Notes |
|---|---|---|
name | Yes | Up to 100 characters |
audience | Yes | See Audience. {} targets everyone. |
default_bid | Yes | USD per click, $0.10 to $25. See Minimum bids. |
daily_spend_limit | Yes | At least $100 |
target_cpa | No | At least $1. Turns on source optimization. |
start_date | No | Defaults to now |
end_date | No | Omit for no end date |
status | No | active (default) or paused |
A new campaign starts delivering once it has an approved creative and your account has funds. Add creatives next with Create Creatives.
Update a Campaign
Send only the fields you want to change.
curl -X PATCH -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns/C0001234 \
-d '{ "default_bid": 0.30, "daily_spend_limit": 400 }'
| Field | Notes |
|---|---|
name | Rename |
default_bid | New default bid, at most double the current one |
daily_spend_limit | A new limit, at least $100 and at most double the current one. Removing the limit is only possible in the dashboard. |
target_cpa | A number turns source optimization on or changes the target (at most double the current one). null turns it off. |
audience | Replaces the whole audience |
start_date, end_date | end_date: null removes the end date. Day-parting (active hours) set in the dashboard is kept. |
Changes are applied one field at a time. If one fails, the fields before it are already saved and the error lists them in details.applied. Read the campaign again before retrying.
Pause, Activate or Archive
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns/C0001234/status -d '{ "status": "paused" }'
status is active, paused or archived. Pausing stops delivery within seconds.
An archived campaign can't be reactivated or edited. Pause it instead if you might need it again.
Clone a Campaign
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns/C0001234/clone \
-d '{ "name": "Fall Promo - Desktop", "include_creatives": true }'
name is required. The clone copies the audience, schedule, bid, daily limit, target CPA and source targeting. Send daily_spend_limit to give the clone a different limit (required if the original has none). With include_creatives (the default), active, paused and pending creatives are copied and sent to review. Clones start paused unless you send "status": "active".
The API refuses to clone a campaign that was switched to an allow-list with no sources yet and still enforces its old block-list, because the copy would lose those blocks. Add allowed sources or switch it back to a block-list first.
Audience
The audience uses the same options as the dashboard's audience form:
| Field | Values |
|---|---|
country | One of US, CA, GB, AU, DE, FR, IT, IE, NL, JP, SG, HK, IL, AE |
states | US state codes, for example ["TX", "CA"]. Needs country: "US". |
platforms | Any of DESKTOP, MOBILE, TABLET, OTHER |
subscribed_more_than_minutes_ago | Only subscribers older than this many minutes |
subscribed_less_than_minutes_ago | Only subscribers newer than this many minutes (10080 = 7 days) |
min_age, max_age | 13–120 |
gender | M or F. Omit to target both. |
When you read a campaign, audience.unsupported_targeting: true means it has targeting these fields can't show, such as a rule that excludes a country, state, platform or gender. That targeting is set in the dashboard and isn't included in the other fields. Sending audience replaces the whole audience, including those rules.
Minimum Bids
- $0.10 by default
- $0.30 when the audience targets subscribers newer than 7 days, or uses age or gender
Creatives
New and edited creatives go through the usual review process, which usually takes up to 24 hours. They start serving on their own once approved.
Upload Images
Upload the icon and (optionally) the large image first. PNG, JPEG and GIF are accepted, up to 700 KB.
From a link. This is the simplest option and the best one for AI assistants. Send a public https URL and we download it:
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/images \
-d '{"kind": "icon", "image_url": "https://cdn.example.com/icon.png"}'
The link must be publicly reachable over https on the standard port and must download within 10 seconds. Up to 3 redirects are followed. The file type is read from the file itself. Links to private networks, IP addresses or localhost are refused.
From a file. Send the file as base64 with its content_type. This example uses jq to build the body and pipes it to curl, because a large file can be too long for a single shell argument:
base64 < icon.png | tr -d '\n' \
| jq -Rs '{kind: "icon", content_type: "image/png", data_base64: .}' \
| curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/images -d @-
{ "icon_id": "66f1c2a9e4b0a1b2c3d4e5f6", "url": "https://api.pushnami.com/api/push/icon/id/66f1c2a9e4b0a1b2c3d4e5f6" }
Use "kind": "image" for the large image; the response then has image_id. See Push Notification Specifications for sizes.
Create Creatives
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns/C0001234/creatives \
-d '{
"creatives": [
{
"title": "Your fall deal is here",
"message": "Save 30% this week only. Tap to claim.",
"link": "https://example.com/offer?cid=<<conversion_id>>&src={{=data.adsourceid}}",
"icon_id": "66f1c2a9e4b0a1b2c3d4e5f6",
"image_id": "66f1c2b7e4b0a1b2c3d4e5f7",
"button1": "Claim Offer"
}
]
}'
| Field | Required to submit | Notes |
|---|---|---|
title | Yes | Up to 100 characters; 34 or fewer recommended |
message | Yes | Up to 200 characters; 65 or fewer recommended |
link | Yes | http(s)://, up to 2,000 characters. Supports tracking values. |
icon_id | Yes | From Upload Images |
image_id | No | Large image |
button1, button2 | No | Up to 25 characters each |
Send up to 20 creatives per request. Add "draft": true to save them as drafts instead of submitting; drafts only need one field.
Titles, messages and links can use dynamic variables, such as {{=data.dcity||'Your City'}} or {{#def.date('MMMM')}}. The API accepts the documented forms only: a data. field, a quoted fallback joined with ||, or def.date with an optional format.
Edit a Creative
curl -X PATCH -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/creatives/C0001234-001 -d '{ "title": "Last chance: 30% off" }'
- Pending, rejected or draft creatives are updated in place and (re)submitted for review. For a draft, add
"submit_for_review": trueto submit it. - Active or paused creatives get a new version. The original keeps running while the new version is reviewed, and the new version replaces it once approved. The response has
new_version_created: trueand the new creative's ID. If an edit is already in review, that edit is updated instead of a second one being made.
To clear image_id, button1 or button2, send null.
Pause, Activate or Archive a Creative
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/creatives/C0001234-001/status -d '{ "status": "paused" }'
- You can pause an
activecreative and activate apausedone. - Only a paused creative can be activated. Pending, rejected and draft creatives go live when review approves them.
- Archiving is permanent.
Clone a Creative
For an A/B test, clone a creative and change one field.
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/creatives/C0001234-001/clone \
-d '{ "title": "Only 3 days left", "campaign_id": "C0001250" }'
campaign_id is optional and defaults to the original's campaign. The clone is submitted for review unless you add "draft": true.
Creative Statuses
active, paused, pending_review, rejected, draft, archived. Rejected creatives include rejection_reasons and rejection_comment, so you can fix and resubmit them.
Sources
Get Source Settings
curl -H 'Authorization: Bearer pnk_...' https://ads-api.pushnami.com/v1/campaigns/C0001234/sources
{
"campaign_id": "C0001234",
"mode": "block",
"blocked_source_ids": ["S0004483", "S0007710"],
"blocked_source_count": 2,
"allowed_source_ids": [],
"allowed_source_count": 0,
"linked_source_list_count": 1,
"bid_overrides": [
{ "source_id": "S0001102", "bid": 0.35, "optimized": false }
],
"bid_override_count": 1,
"truncated": false
}
bid_overrides and bid_override_count are null when the bids can't be read at that moment. That doesn't mean there are no overrides.
mode is block (serve everywhere except blocked sources) or allow (serve only on allowed sources). Change the mode and linked source lists in the dashboard. For per-source performance, use the Spend Report API with source_id.
Set Source Bids
Set bids for up to 50 sources in one request. A bid can be at most 3× the campaign's default bid.
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns/C0001234/sources/bids \
-d '{ "source_ids": ["S0001102", "S0001188"], "bid": 0.35 }'
"bid": 0.35sets an override"bid": nullgoes back to the campaign's default bid"optimize": truehands the sources to source optimization (the campaign needs atarget_cpa)
The response lists updated_source_ids and any unknown_source_ids. Source bids can't be changed on an archived campaign.
Block, Unblock, Allow, Disallow
curl -X POST -H 'Authorization: Bearer pnk_...' -H 'Content-Type: application/json' \
https://ads-api.pushnami.com/v1/campaigns/C0001234/sources/block \
-d '{ "source_ids": ["S0004483"] }'
| Endpoint | Campaign mode | Effect |
|---|---|---|
/sources/block | block | Stop buying traffic from these sources |
/sources/unblock | block | Start buying from them again |
/sources/allow | allow | Add sources to the allow-list |
/sources/disallow | allow | Remove sources from the allow-list |
The response lists updated_source_ids and any unknown_source_ids. If the change was saved but some sources aren't in force yet, it also has not_enforced_source_ids and a warning. Check Get Source Settings shortly, and repeat the change if they are still listed. Sources can't be changed on an archived campaign.
Removing the last allowed source would put the campaign back in block mode with nothing blocked, so it would serve on every source. The API refuses that request with 409 conflict. Pause the campaign instead, or make the change in the dashboard.
Balance
GET /v1/balance (the get_balance tool over MCP) shows your available balance and how long it will last at your current pace. It's read-only: add funds in the dashboard (card) or by wire (prepay).
curl https://ads-api.pushnami.com/v1/balance -H "Authorization: Bearer $PUSHNAMI_ADS_KEY"
{
"account_id": "62c668500000000000000000",
"payment_type": "prepay",
"available_balance": 5000,
"currency": "USD",
"auto_funding": null,
"average_daily_spend": 1000,
"days_remaining": 5,
"estimated_depletion_date": "2026-09-29T14:00:00.000Z",
"refill_by_date": "2026-09-26T14:00:00.000Z",
"status": "low",
"message": "At the current pace your balance of $5,000.00 lasts about 5 more day(s). Send a wire by Sep 26 so it arrives before the balance runs out.",
"as_of": "2026-09-24T14:00:00.000Z"
}
| Field | Meaning |
|---|---|
payment_type | card (you add funds by card, optionally with auto-funding) or prepay (you prepay by wire) |
auto_funding | Card accounts only: whether auto-funding is on, the balance it refills below, and how much it adds. Card details are never returned. |
average_daily_spend | Average over the last 7 full days |
days_remaining, estimated_depletion_date | At the current pace. null when nothing is delivering or there's no recent spend. |
refill_by_date | Prepay only: the last day to send a wire so it arrives (about 3 days) before the balance runs out |
status | ok, low, depleted, or unknown when spend couldn't be read |
message | What to do, in plain language |
When status is low:
- Prepay: the balance lasts 7 days or less at the current pace. At 3 days or less, the message says to wire today.
- Card without auto-funding: the balance is under $500, or lasts 3 days or less.
- Card with auto-funding: never
low. If the balance runs out anyway,statusisdepleted, which usually means a top-up failed. Check your payment method.
The balance is refreshed at most every 10 minutes. Pass ?fresh=true to force a new read, at most once a minute. For a daily low-balance alert, poll this once or twice a day and alert on status.
Errors
Every error has this shape:
{
"error": {
"code": "validation_failed",
"message": "Invalid input for update_campaign.",
"details": [{ "path": "daily_spend_limit", "message": "must be >= 100" }]
}
}
| Status | code | Meaning |
|---|---|---|
| 400 | bad_request, validation_failed | Fix the request. details lists each field. |
| 401 | unauthorized | Missing, invalid, expired or revoked key |
| 403 | forbidden | A read key tried to make a change, or API access for the account is suspended |
| 404 | not_found | Not in your account |
| 409 | conflict | Not allowed in the current state or by a safeguard, for example activating an archived campaign or more than doubling a bid. See Conflicts. |
| 429 | rate_limited | Too many requests, a daily limit was reached, or an overlapping change is still running. Wait for the Retry-After seconds. |
| 502 | upstream_error | A Pushnami service didn't respond, or the request took longer than 25 seconds (2 minutes to create or clone a campaign). It may still have completed, so check before retrying. |
| 503 | unavailable | The API is busy or changes are briefly paused. Wait for Retry-After and retry. Pausing still works. |
| 500 | internal_error | Our fault. Include request_id when you contact support. |
Limits and Safeguards
These limits cap how much a faulty script or AI assistant can change. Make large or unusual changes in the dashboard, where a person makes them on purpose.
Pausing is never limited. You can always pause a campaign or creative through the API, even when other changes are turned off.
Per Request
| Limit | Value |
|---|---|
| Sources per request (block, unblock, allow, disallow, bids) | 50, the same as one page in the dashboard |
| Creatives per request | 20 |
| Creatives copied by Clone Campaign | The 20 newest |
Per Day
Each account has a daily budget for each kind of change. It's shared by all the account's API keys and resets at 00:00 UTC. Changes you make in the dashboard don't count.
| Kind of change | Daily limit |
|---|---|
| Sources blocked, unblocked, allowed or disallowed | 1,000 sources |
| Source bids set, reset or optimized | 2,500 sources |
| Campaign updates (bids, budget, target CPA, audience, schedule, name) | 100 |
| Campaigns created or cloned | 20 |
| Creatives created or cloned | 100 |
| Creative edits | 200 |
| Campaigns and creatives activated | 50 |
| Campaigns and creatives archived | 25 |
| Image uploads | 200 |
Check what's left with GET /v1/limits (the get_limits tool over MCP):
{
"resets_at": "2026-09-25T00:00:00.000Z",
"writes_enabled": true,
"daily": {
"source_targeting_changes": { "limit": 1000, "used": 150, "remaining": 850 },
"archives": { "limit": 25, "used": 0, "remaining": 25 }
},
"per_request": { "source_ids": 50, "creatives": 20 },
"always_allowed": ["pausing campaigns and creatives"]
}
When a limit is reached, the API answers 429 rate_limited. The response says which limit was hit and has a Retry-After header with the seconds until the reset. For more, make the change in the dashboard or ask your account representative.
Money Settings
- API-created campaigns must have a daily spend limit. The API can change the limit but not remove it; only the dashboard can.
- No big jumps in one change. A campaign's default bid, daily spend limit and target CPA can at most double in a single change. To go higher, raise it in steps or use the dashboard. This catches typos like
2.50for0.25. - Source bids can be at most 3× the campaign's default bid.
Source Targeting
/sources/disallowwon't remove the last allowed source from an allow-list campaign. See Removing the last allowed source.- Add
"dry_run": trueto any source request (block, unblock, allow, disallow, bids) to see which sources would change, with no changes made and none of your daily budget used.
Overlapping Changes
Some changes run one at a time. If you send a second one while the first is still running, the second gets 429 rate_limited with a short Retry-After. Wait, read the object again, then retry. This applies to:
- updating the same campaign
- activating or archiving the same campaign or creative
- editing the same creative
- changing the sources of the same campaign (block, unblock, allow, disallow)
- creating campaigns in the same account
- creating creatives in the same campaign
Pausing is never held up this way.
Conflicts
409 conflict means the change isn't allowed right now. Don't retry it unchanged. Besides the cases above, the API returns it when:
- you activate a campaign or creative while an earlier pause of it may still be finishing. Retry the activation in a minute.
- you change sources or set source bids on an archived campaign
- a creative is archived while an edit to it is running. The edit doesn't take effect.
- you clone a campaign that was switched to an empty allow-list (see Clone a Campaign)
Request Rate
- Per account: each request uses part of a per-minute allowance shared by all the account's keys. Reads like
list_campaignsuse little;get_campaignand changes use more. Normal use, even a busy script, stays well inside it. - At once: up to 4 requests can run at the same time per account.
- Per key: 120 requests per minute.
- Logins: creating and revoking keys with a username and password is limited to 5 attempts per 15 minutes per username, and 10 per IP address.
When you go over, you get 429 rate_limited. If the API as a whole is busy, or a service it depends on is having trouble, you get 503 unavailable. Both come with a Retry-After header. Wait that many seconds, then retry.
To stay fast:
- Use
GET /v1/campaignsto scan many campaigns, andGET /v1/campaigns/{campaign_id}only for the ones you need in detail.default_bidanddelivery_statuson a campaign can be up to 30 seconds old. - Use the bulk endpoints (per-request limits) instead of one request per item.
GET /v1/campaigns/{campaign_id}/sourcesreturns up to 500 bid overrides and 1,000 blocked or allowed sources. It includes the full counts andtruncated: truewhen there are more.
Code Examples
Python
import os
import requests
API = "https://ads-api.pushnami.com"
HEADERS = {"Authorization": f"Bearer {os.environ['PUSHNAMI_ADS_API_KEY']}"}
# Pause every active campaign whose name contains "Test".
# Collect every page first: pausing changes the filtered list.
params = {"status": "active", "search": "Test", "limit": 100, "offset": 0}
ids = []
while True:
page = requests.get(f"{API}/v1/campaigns", headers=HEADERS, params=params)
page.raise_for_status()
body = page.json()
ids += [campaign["id"] for campaign in body["data"]]
params["offset"] += params["limit"]
if not body["data"] or params["offset"] >= body["pagination"]["total"]:
break
for campaign_id in ids:
requests.post(f"{API}/v1/campaigns/{campaign_id}/status",
headers=HEADERS, json={"status": "paused"}).raise_for_status()
JavaScript
const API = 'https://ads-api.pushnami.com';
const headers = {
Authorization: `Bearer ${process.env.PUSHNAMI_ADS_API_KEY}`,
'Content-Type': 'application/json',
};
// Raise the bid on two sources
const res = await fetch(`${API}/v1/campaigns/C0001234/sources/bids`, {
method: 'POST',
headers,
body: JSON.stringify({ source_ids: ['S0001102', 'S0001188'], bid: 0.35 }),
});
console.log(await res.json());
Support
For API access or issues, contact your Pushnami account representative or email support@pushnami.com.