Skip to main content

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.

MCPREST
EndpointPOST https://ads-api.pushnami.com/mcp (Streamable HTTP, stateless)https://ads-api.pushnami.com/v1/...
AuthAuthorization: Bearer pnk_..., or OAuth sign-in with the email address and password the user uses for Pushnami AdsAuthorization: Bearer pnk_... only
Discoverytools/listGET 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 read key unless the task needs changes. Ask for write only 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​

ThingFormatExample
CampaignC + 7–10 digitsC0001234
Creativecampaign ID + - + numberC0001234-001
Sourcewebsite ID, as in the Spend Report source_id columnS0004483
Image / icon24 hex characters, from upload_image66f1c2a9e4b0a1b2c3d4e5f6
MoneyUSD. Bids are per click.0.25
TimeRFC 33392026-10-01T00:00:00Z

4. Tools / endpoints​

MCP toolRESTChanges data
get_api_key_infoGET /v1/meno
get_limitsGET /v1/limitsno
get_balanceGET /v1/balanceno
list_campaignsGET /v1/campaigns?status=&search=&limit=&offset=no
get_campaignGET /v1/campaigns/{id}no
create_campaignPOST /v1/campaignsyes
update_campaignPATCH /v1/campaigns/{id}yes
set_campaign_statusPOST /v1/campaigns/{id}/statusyes
clone_campaignPOST /v1/campaigns/{id}/cloneyes
list_creativesGET /v1/campaigns/{id}/creativesno
get_creativeGET /v1/creatives/{id}no
create_creativesPOST /v1/campaigns/{id}/creativesyes
update_creativePATCH /v1/creatives/{id}yes
set_creative_statusPOST /v1/creatives/{id}/statusyes
clone_creativePOST /v1/creatives/{id}/cloneyes
upload_imagePOST /v1/imagesyes
get_source_settingsGET /v1/campaigns/{id}/sourcesno
set_source_bidsPOST /v1/campaigns/{id}/sources/bidsyes
block_sources, unblock_sources, allow_sources, disallow_sourcesPOST /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​

  1. 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
  2. Read before you write. Call get_campaign / get_creative / get_source_settings first, and report the before and after values.
  3. Don't retry conflict errors 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.
  4. Creatives are reviewed by humans. New and edited creatives become pending_review and go live on their own once approved (usually within 24 hours). Only a paused creative can be activated. Say so instead of trying.
  5. Stay inside the account. A key only sees its own account; a not_found means the ID isn't in this account, not that you should try another.
  6. Batch, within the limits. One set_source_bids / block_sources call takes up to 50 sources; one create_creatives call up to 20 creatives. The rate is 120 requests per minute per key.
  7. Respect the daily limits. Each account has a daily budget per kind of change (get_limits shows it). Check it before a large job, and use dry_run: true on source calls to preview. When you get rate_limited for 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.
  8. Pausing is always allowed. If something looks wrong, pausing the campaign is the safe move.
  9. Go easy on the service. Run at most 4 calls at a time. Scan with list_campaigns and only call get_campaign for the campaigns you need. On rate_limited (429) or unavailable (503), wait the Retry-After header seconds (over MCP, retry_after_seconds in the error) before retrying. Never retry in a tight loop. After an upstream_error on a change, read the object back before retrying, because the change may have gone through. A rate_limited that 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.
  10. Check source changes took effect. If a source call returns not_enforced_source_ids, tell the user, call get_source_settings shortly after, and repeat the change for any source still listed.
  11. Don't overwrite targeting you can't see. If a campaign's audience has unsupported_targeting: true, it has exclusion rules set in the dashboard. Sending audience replaces them. Tell the user and get their confirmation first.
  12. 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-Warning header, or an MCP result has key_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.
  13. Mention a low balance. If get_balance returns status: "low" or "depleted", or any MCP result has an account_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_bid and source bid: $0.10–$25. Minimum is $0.30 when the audience has subscribed_less_than_minutes_ago ≤ 10080, or any of min_age, max_age, gender. default_bid can at most double in one change. Source bid: 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; null turns it off. set_source_bids with optimize: true needs it.
  • disallow_sources is refused if it would remove the last allowed source; suggest pausing instead.
  • audience (replaces the whole audience when sent):
    • country: one of US CA GB AU DE FR IT IE NL JP SG HK IL AE
    • states: US state codes, only with country: "US"
    • platforms: any of DESKTOP MOBILE TABLET OTHER
    • subscribed_more_than_minutes_ago, subscribed_less_than_minutes_ago: integers ≥ 1
    • min_age, max_age: 13–120; gender: M or F
  • 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). With draft: true any 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 in link. See dynamic variables and tracking values.
  • upload_image: kind is icon (1:1) or image (2:1). Send either image_url (a public https link, which we download; prefer this, so you don't have to write the file out in a tool call) or data_base64 + content_type (image/png, image/jpeg or image/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

  1. upload_image with kind: "icon", and again with kind: "image" if you have one.
  2. create_campaign with name, audience, default_bid, daily_spend_limit and status: "paused".
  3. create_creatives with two variants.
  4. Tell the user the creatives are in review. After the user confirms, set_campaign_status to active; it starts serving once a creative is approved and the account has funds.

Tune source bids from performance data

  1. 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.
  2. get_source_settings to see current overrides.
  3. Propose the changes to the user, then set_source_bids in one call per bid level, and block_sources for sources to drop.

Fix rejected creatives

  1. list_creatives with status: ["rejected"].
  2. Read rejection_reasons and rejection_comment for each.
  3. update_creative with corrected fields. It goes back to review.

A/B test a creative

  1. clone_creative from the current winner, changing only title (or only image_id).
  2. Later, pause the loser with set_creative_status after 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.

codeWhat to do
validation_failedFix the fields listed in details and retry
unauthorizedWith 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.
forbiddenThe key is read-only. Ask the user for a write key if they want the change.
not_foundThe ID isn't in this account
conflictNot 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_limitedWait 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_errorFor a read, retry once after a few seconds. For a change, read the object first, since it may have gone through.
unavailableWait Retry-After (or retry_after_seconds) seconds and retry. Pausing still works.
internal_errorStop and give the user the request_id for support@pushnami.com