Skip to content

Repository files navigation

Apple Ads CLI by Attainment

CI Python License: MIT

Composable Apple Search Ads CLI for the Apple Ads API, built for operators, developers, and AI agents that need safe campaign inventory, keyword reporting, and dry-run bid management.

Built by Attainment Labs

Why this exists

Apple Ads work often starts with a simple question:

Which campaigns, ad groups, and keywords are live right now?

The hard part is making the answer safe enough for automation. This CLI wraps asa-api-client with stable JSON, explicit pagination, credential-safe diagnostics, and dry-run-first write commands so a human or agent can inspect the account before making a live change.

Common use cases:

  • Apple Search Ads account inventory
  • App Store Ads keyword reporting
  • ASA API diagnostics
  • Campaign and ad group discovery
  • Keyword bid previews
  • Safe keyword bid updates after approval
  • Agent-friendly Apple Ads workflows

The installed command is:

apple-ads

This project is not affiliated with, endorsed by, or sponsored by Apple Inc.

Safety model

  • Stable --json output for success and error cases.
  • Invalid arguments return JSON when --json is present.
  • Inventory commands expose pagination metadata.
  • Inventory commands fail closed on truncation unless --allow-partial is explicitly used.
  • Bid updates are dry-run by default.
  • Bid apply requires --apply.
  • Bid apply verifies live readback after the update and exits nonzero on mismatch.
  • The CLI never prints full credentials.
  • Local profile IDs are masked by default in doctor and profiles list.
  • Use --reveal only when you need exact local values.

Install

From source:

git clone https://github.com/attainmentlabs/apple-ads-cli.git
cd apple-ads-cli
python3 -m pip install -e .

Requires Python 3.13 or newer.

Local development:

make install-local

More install options are in docs/install.md, including pipx and uv.

Auth

The CLI uses the environment variables expected by asa-api-client:

export ASA_CLIENT_ID="..."
export ASA_TEAM_ID="..."
export ASA_KEY_ID="..."
export ASA_PRIVATE_KEY_PATH="/path/to/AuthKey.p8"
export ASA_ORG_ID="..."

You can also point a profile at a local env file. The profile stores only paths and IDs, not secret values:

apple-ads --json profiles set \
  --name my-app \
  --env-file ~/.config/apple-ads/my-app.env \
  --org 1234567890 \
  --default \
  --write

Preview profile writes by omitting --write.

Quick start

apple-ads --json doctor
apple-ads --version
apple-ads --json doctor --live
apple-ads --json campaigns list
apple-ads --json ad-groups list --campaign CAMPAIGN_ID
apple-ads --json keywords list --campaign CAMPAIGN_ID --ad-group AD_GROUP_ID
apple-ads --json keywords live --serving-only
apple-ads --json reports keywords --campaign CAMPAIGN_ID --latest-complete --nonzero-only
apple-ads --json request get campaigns --param limit=10

Command reference

Command Purpose Live write
doctor Check config, auth variables, client import, and optional live reachability. No
profiles list List local non-secret profiles with IDs masked by default. No
profiles set Preview or write a local non-secret profile. Local file only with --write
campaigns list List campaigns with pagination metadata. No
ad-groups list List ad groups in one campaign. No
keywords list List keywords in one campaign and ad group. No
keywords live List active keywords and distinguish currently serving keywords. No
keywords set-bid Preview or apply one keyword bid update. Yes, only with --apply
reports keywords Read keyword performance metrics. No
request get Run a raw read-only GET request relative to the Apple Ads API version. No

More examples live in examples.

The raw request get command is a repair hatch for read-only Apple Ads API endpoints that do not have a high-level command yet. It does not expose POST, PUT, PATCH, or DELETE.

Project docs

JSON contract

Successful commands return a top-level ok: true field:

{
  "ok": true,
  "rows": []
}

Errors return a top-level ok: false field:

{
  "ok": false,
  "error": {
    "code": "invalid_args",
    "message": "argument --campaign: must be a positive integer"
  }
}

Inventory commands include pagination details:

{
  "ok": true,
  "partial": false,
  "pagination": {
    "total_results": 1001,
    "returned": 1001,
    "page_count": 2,
    "truncated": false
  },
  "rows": []
}

If the page walk is incomplete, the command returns nonzero partial_read unless --allow-partial is used.

Dry-run bid update

Preview first:

apple-ads --json keywords set-bid \
  --campaign CAMPAIGN_ID \
  --ad-group AD_GROUP_ID \
  --keyword KEYWORD_ID \
  --bid 1.25

Apply only after reviewing the exact preview:

apple-ads --json keywords set-bid \
  --campaign CAMPAIGN_ID \
  --ad-group AD_GROUP_ID \
  --keyword KEYWORD_ID \
  --bid 1.25 \
  --apply

GitHub discovery setup

Recommended repository description:

Apple Search Ads CLI for ASA API reporting, keyword inventory, and dry-run bid management.

Recommended GitHub topics:

apple-search-ads apple-ads asa-api app-store-ads app-store-marketing cli python marketing-automation keyword-research bid-management

Development

make check

Manual checks:

python3 -m unittest discover -s tests
PYTHONPYCACHEPREFIX=/private/tmp/apple-ads-cli-pycache python3 -m compileall apple_ads_cli
HOME=/private/tmp/apple-ads-cli-check-home python3 -m apple_ads_cli.cli --json doctor

Before public release, also run a private-data scan for product names, credential paths, account IDs, campaign IDs, ad group IDs, keyword IDs, local user paths, and forbidden Unicode dash characters.

python3 scripts/public_safety_scan.py

Status

Alpha. Built around asa-api-client and intended for careful Apple Ads operator and agent workflows.

Releases

Packages

Used by

Contributors

Languages