Agent Guide
This page is written for AI agents. If you are a person, give your agent this page's URL (https://help.pushnami.com/developers/agents) and it will know how to connect and what it may do. Docs for people are in Campaign Management API and MCP Server.
1. What you can do
You can manage one Pushnami Ads advertiser account: push notification campaigns, their creatives, and per-source (website) targeting and bids. You can also read the account balance (get_balance).
You cannot: add funds, change payment methods, read invoices, or pull reports. For spend and performance data, use the Spend Report API (GET https://adnetpn.com/ads/spend-report), which uses the same IDs. It signs in with the user's dashboard username and password (HTTP Basic), not the API key, so ask the user to run the report or share its output.
2. Connect
Prefer MCP if your runtime supports it. Otherwise call the REST API directly.
| MCP | REST | |
|---|---|---|
| Endpoint | POST https://ads-api.pushnami.com/mcp (Streamable HTTP, stateless) | https://ads-api.pushnami.com/v1/... |
| Auth | Authorization: Bearer pnk_..., or OAuth sign-in with the email address and password the user uses for Pushnami Ads | Authorization: Bearer pnk_... only |
| Discovery | tools/list | GET https://ads-api.pushnami.com/v1/openapi.json |
Getting a key. Unless you're connected through OAuth, you need an API key that starts with pnk_. Ask the user to create one on the API Access page of the Pushnami Ads dashboard (https://ads.pushnami.com/developers/api) and give it to you through an environment variable or secret store. If they prefer the command line:
curl -u 'USERNAME:PASSWORD' -H 'Content-Type: application/json' \
-d '{"name": "agent", "scope": "read"}' https://ads-api.pushnami.com/v1/api-keys
If the user connected you through OAuth sign-in in the MCP client, using the email address and password they use for Pushnami Ads, you don't need a key. That connection works only on MCP, and it can make changes.
- Never ask the user to paste their password into the conversation. Ask them to use the dashboard or run the command themselves.
- Ask for a
readkey unless the task needs changes. Ask forwriteonly when it does. - Never print, log or repeat the key.
First call. Call get_api_key_info (MCP) or GET /v1/me to confirm the account and whether you can write.
3. IDs and units
| Thing | Format | Example |
|---|---|---|
| Campaign | C + 7–10 digits | C0001234 |
| Creative | campaign ID + - + number | C0001234-001 |
| Source | website ID, as in the Spend Report source_id column | S0004483 |
| Image / icon | 24 hex characters, from upload_image | 66f1c2a9e4b0a1b2c3d4e5f6 |
| Money | USD. Bids are per click. | 0.25 |
| Time | RFC 3339 | 2026-10-01T00:00:00Z |
4. Tools / endpoints
| MCP tool | REST | Changes data |
|---|---|---|
get_api_key_info | GET /v1/me | no |
get_limits | GET /v1/limits | no |
get_balance | GET /v1/balance | no |
list_campaigns | GET /v1/campaigns?status=&search=&limit=&offset= | no |
get_campaign | GET /v1/campaigns/{id} | no |
create_campaign | POST /v1/campaigns | yes |
update_campaign | PATCH /v1/campaigns/{id} | yes |
set_campaign_status | POST /v1/campaigns/{id}/status | yes |
clone_campaign | POST /v1/campaigns/{id}/clone | yes |
list_creatives | GET /v1/campaigns/{id}/creatives | no |
get_creative | GET /v1/creatives/{id} | no |
create_creatives | POST /v1/campaigns/{id}/creatives | yes |
update_creative | PATCH /v1/creatives/{id} | yes |
set_creative_status | POST /v1/creatives/{id}/status | yes |
clone_creative | POST /v1/creatives/{id}/clone | yes |
upload_image | POST /v1/images | yes |
get_source_settings | GET /v1/campaigns/{id}/sources | no |
set_source_bids | POST /v1/campaigns/{id}/sources/bids | yes |
block_sources, unblock_sources, allow_sources, disallow_sources | POST /v1/campaigns/{id}/sources/{action} | yes |
MCP tool arguments and REST bodies use the same field names. Every input schema rejects unknown fields.
5. Rules you must follow
- Confirm before these actions, stating exactly what will change:
- archiving a campaign or creative (permanent)
- activating a campaign or creative
- raising any bid, daily spend limit or target CPA by more than 25%, or removing a daily spend limit
- any change to more than 10 campaigns or 100 sources at once
- Read before you write. Call
get_campaign/get_creative/get_source_settingsfirst, and report the before and after values. - Don't retry
conflicterrors unchanged. They mean the action isn't allowed in the current state. Read the message and explain it to the user. One exception: if the message says an earlier pause is still finishing, retry the activation once after a minute. - Creatives are reviewed by humans. New and edited creatives become
pending_reviewand go live on their own once approved (usually within 24 hours). Only a paused creative can be activated. Say so instead of trying. - Stay inside the account. A key only sees its own account; a
not_foundmeans the ID isn't in this account, not that you should try another. - Batch, within the limits. One
set_source_bids/block_sourcescall takes up to 50 sources; onecreate_creativescall up to 20 creatives. The rate is 120 requests per minute per key. - Respect the daily limits. Each account has a daily budget per kind of change (
get_limitsshows it). Check it before a large job, and usedry_run: trueon source calls to preview. When you getrate_limitedfor a daily limit, stop and tell the user. Don't retry it in a loop, and don't work around it by splitting the job across keys. - Pausing is always allowed. If something looks wrong, pausing the campaign is the safe move.
- Go easy on the service. Run at most 4 calls at a time. Scan with
list_campaignsand only callget_campaignfor the campaigns you need. Onrate_limited(429) orunavailable(503), wait theRetry-Afterheader seconds (over MCP,retry_after_secondsin the error) before retrying. Never retry in a tight loop. After anupstream_erroron a change, read the object back before retrying, because the change may have gone through. Arate_limitedthat says another change to the same campaign or creative (or another create) is in progress means one of your own calls is still running. Wait for it, read the object again, then decide whether to retry. - Check source changes took effect. If a source call returns
not_enforced_source_ids, tell the user, callget_source_settingsshortly after, and repeat the change for any source still listed. - Don't overwrite targeting you can't see. If a campaign's
audiencehasunsupported_targeting: true, it has exclusion rules set in the dashboard. Sendingaudiencereplaces them. Tell the user and get their confirmation first. - Pass on key expiry warnings. This applies only when you use an API key; an OAuth connection has no key to expire. If a REST response has a
Pushnami-Api-Key-Expiry-Warningheader, or an MCP result haskey_expiry_warning, tell the user once that the key expires soon and that they renew it on the API Access page of the dashboard. The key keeps the same value after renewal. Never try to renew the key yourself, and never ask for the user's password to do it. - Mention a low balance. If
get_balancereturnsstatus: "low"or"depleted", or any MCP result has anaccount_notice, tell the user in one line before creating, activating or raising the budget of anything. Don't stop other work over it, and don't repeat it every turn. You can't add funds: the user does that in the dashboard (card) or by wire (prepay).
6. Validation rules (so your first call succeeds)
default_bidand sourcebid: $0.10–$25. Minimum is $0.30 when the audience hassubscribed_less_than_minutes_ago≤ 10080, or any ofmin_age,max_age,gender.default_bidcan at most double in one change. Sourcebid: at most 3× the campaign's default bid.daily_spend_limit: ≥ 100. Required when creating a campaign. It can't be removed through the API, and it can at most double in one change.target_cpa: ≥ 1, at most double the current value in one change. Setting it turns on source optimization;nullturns it off.set_source_bidswithoptimize: trueneeds it.disallow_sourcesis refused if it would remove the last allowed source; suggest pausing instead.audience(replaces the whole audience when sent):country: one ofUS CA GB AU DE FR IT IE NL JP SG HK IL AEstates: US state codes, only withcountry: "US"platforms: any ofDESKTOP MOBILE TABLET OTHERsubscribed_more_than_minutes_ago,subscribed_less_than_minutes_ago: integers ≥ 1min_age,max_age: 13–120;gender:MorF
- Creative, to submit for review:
title(≤ 100, aim for ≤ 34),message(≤ 200, aim for ≤ 65),link(http(s)://, ≤ 2000),icon_id. Optional:image_id,button1,button2(≤ 25 each). Withdraft: trueany one field is enough. - Dynamic text: only
{{=data.FIELD}},{{=data.FIELD||'Fallback'}}(chain more fields with||), and{{#def.date('FORMAT')}}. Link macros like<<conversion_id>>are allowed inlink. See dynamic variables and tracking values. upload_image:kindisicon(1:1) orimage(2:1). Send eitherimage_url(a publichttpslink, which we download; prefer this, so you don't have to write the file out in a tool call) ordata_base64+content_type(image/png,image/jpegorimage/gif). Max 700 KB. If the user only has a local file and you can't read it, ask them for a link or to upload it in the dashboard.
7. Recipes
Launch a campaign with two creatives
upload_imagewithkind: "icon", and again withkind: "image"if you have one.create_campaignwithname,audience,default_bid,daily_spend_limitandstatus: "paused".create_creativeswith two variants.- Tell the user the creatives are in review. After the user confirms,
set_campaign_statustoactive; it starts serving once a creative is approved and the account has funds.
Tune source bids from performance data
- Get per-source spend and clicks from the Spend Report API (
columns=source_id,total_spend,clicks&campaign_id=...). The user runs it with their login, or gives you the output. get_source_settingsto see current overrides.- Propose the changes to the user, then
set_source_bidsin one call per bid level, andblock_sourcesfor sources to drop.
Fix rejected creatives
list_creativeswithstatus: ["rejected"].- Read
rejection_reasonsandrejection_commentfor each. update_creativewith corrected fields. It goes back to review.
A/B test a creative
clone_creativefrom the current winner, changing onlytitle(or onlyimage_id).- Later, pause the loser with
set_creative_statusafter the user agrees.
Why isn't my campaign delivering?
get_campaign and read delivery_status: waiting_for_funds (the user must add funds in the dashboard), waiting_for_creatives (no active creatives), all_creatives_rejected, scheduled, daily_limit_reached, paused, archived, rejected. If delivery_status is missing, it couldn't be read. Call get_campaign again shortly rather than guessing. In get_source_settings, bid_overrides: null means the bids couldn't be read, not that there are none.
8. Errors
Errors are {"error": {"code", "message", "details?"}} over REST, and a tool result with isError: true and the same JSON over MCP. Over MCP the wait for rate_limited and unavailable is in retry_after_seconds, since there is no Retry-After header.
| code | What to do |
|---|---|
validation_failed | Fix the fields listed in details and retry |
unauthorized | With an API key: the key is invalid, expired or revoked. Ask the user to renew it on the API Access page (the key value stays the same), or to create a new key if it was revoked. With an OAuth connection: the sign-in ended. Ask the user to reconnect in their MCP client and sign in again. |
forbidden | The key is read-only. Ask the user for a write key if they want the change. |
not_found | The ID isn't in this account |
conflict | Not allowed in the current state. Explain the message; don't retry unchanged (except "an earlier pause is still finishing": retry once after a minute). |
rate_limited | Wait Retry-After (or retry_after_seconds) seconds. For a daily limit, stop and tell the user. For "another change is in progress", read the object again before retrying. |
upstream_error | For a read, retry once after a few seconds. For a change, read the object first, since it may have gone through. |
unavailable | Wait Retry-After (or retry_after_seconds) seconds and retry. Pausing still works. |
internal_error | Stop and give the user the request_id for support@pushnami.com |