# Apple Ads Operator Playbooks

> Practical Apple Ads workflows for safe setup, read-only checks, reporting, and guarded mutations

Use these playbooks when you are operating Apple Ads from `asc` in a terminal,
CI job, or agent run. The goal is to start read-only, confirm the ad-account context,
and only mutate resources after the target IDs and payload files are reviewed.

The primary examples use the direct Platform API v1 resource commands under
`asc ads` with `--ad-account`. The deprecated Campaign Management API v5 tree
is isolated in the migration and legacy sections below.

## Setup and Credential Safety

Apple Ads credentials are separate from App Store Connect API credentials.
`asc auth login` does not configure `asc ads`.

Create a named Apple Ads profile and store the default ad account when you know it:

```bash  theme={null}
asc ads auth login \
  --name "Marketing" \
  --client-id "SEARCHADS_CLIENT_ID" \
  --team-id "SEARCHADS_TEAM_ID" \
  --key-id "KEY_ID" \
  --private-key "$HOME/.asc/apple-ads-private-key.pem" \
  --ad-account "987654" \
  --network
```

For CI, prefer secrets and environment variables over checked-in config:

```bash  theme={null}
export ASC_ADS_CLIENT_ID="SEARCHADS_CLIENT_ID"
export ASC_ADS_TEAM_ID="SEARCHADS_TEAM_ID"
export ASC_ADS_KEY_ID="KEY_ID"
export ASC_ADS_PRIVATE_KEY_PATH="$HOME/.asc/apple-ads-private-key.pem"
export ASC_ADS_AD_ACCOUNT_ID="987654"
export ASC_ADS_BYPASS_KEYCHAIN=1

asc ads apps search --ad-account "$ASC_ADS_AD_ACCOUNT_ID" --query "Example" --output json
```

Use a read-only API command for CI validation so the run resolves the exported
`ASC_ADS_*` credentials and org context instead of only listing stored profiles.

Use `ASC_ADS_PRIVATE_KEY_B64` when your CI secret store handles single-line
values more reliably than PEM blocks. Keep `ASC_ADS_ACCESS_TOKEN` for cases
where another trusted system already minted a short-lived token.

## Platform API v1 migration

For new automation, start with the Apple Ads Platform API v1 command tree:

```bash  theme={null}
asc ads me view --output json
asc ads acls list --output json
asc ads apps search --ad-account "987654" --query "Example" --output json
```

The legacy Campaign Management API v5 commands under `asc ads v5` still run in
4.4.0 and print a deprecation warning on stderr. Apple retires v5 on January
26, 2027. A v5 command keeps its existing `--org` context; a v1 command uses
the separate `--ad-account` context. Do not pass an organization ID where a v1
ad account ID is required, or assume that the two IDs identify the same thing.

V1 query, report, and mutation bodies use the Platform API schemas. Put the
appropriate v1 object in `--file`; the CLI does not convert a v5 selector,
reporting request, numeric ID, or response envelope. V1 report pagination stays
in the request JSON, so do not add the legacy `--paginate` flag to a v1 report
command:

```bash  theme={null}
asc ads reports apps campaigns \
  --ad-account "987654" \
  --file report.json \
  --output json
```

Platform v1 exposes one `negative-keywords` resource for both campaign and ad
group scope. Move the scope into the v1 query or mutation body rather than
looking for separate campaign-negative-keywords and ad-group-negative-keywords
commands. The v5 `asc ads v5 reports preset` helper remains a warning-producing
compatibility path; replace it with an explicit v1 report leaf and reviewed
request file.

Seven v5 leaves have no one-command v1 replacement in 4.4.0: product-page
countries, product-page devices, targeting-keyword bulk delete, campaign
negative-keyword bulk delete, ad-group negative-keyword bulk delete, and
impression-share report list and view. Related v1 commands are not drop-in
replacements for those contracts. In particular, `geo search` is not the v5
country/device mapping, `insights impression-share` is not the custom report
list/view API, and v1 has no bulk-delete operation.

For raw calls, keep `asc ads v5 api request` for v5 paths and its `--org` context.
It remains runnable with a warning and is not rewritten. Use
`asc ads api request --path v1/... --ad-account ...` for a raw v1 call.

## Account Access Inspection

Start outside an ad-account context. These commands do not require
`--ad-account`:

```bash  theme={null}
asc ads acls list --output json
```

Use the ACL response to confirm the ad account ID, then pin it explicitly for
the rest of the session:

```bash  theme={null}
export ASC_ADS_AD_ACCOUNT_ID="987654"
asc ads apps search --ad-account "$ASC_ADS_AD_ACCOUNT_ID" --query "Example" --limit 1 --output json
```

When operating multiple Apple Ads accounts, avoid relying on the ambient
default. Pass both profile and ad account:

```bash  theme={null}
asc ads apps search \
  --ads-profile "Marketing" \
  --ad-account "987654" \
  --query "Example" \
  --output json
```

## Safe Read-Only Smoke Test

Use this sequence before an automation run or after rotating credentials:

```bash  theme={null}
ASC_BYPASS_KEYCHAIN=1 asc ads acls list --output json
ASC_BYPASS_KEYCHAIN=1 asc ads apps search --ad-account "987654" --query "Example" --limit 1 --output json
```

Expected result: every command exits successfully, and the app search returns
either a `result` envelope or an empty result. Treat auth failures, missing account
errors, or unexpected account names as stop conditions.

When using stored Ads profiles, add profile validation before the API smoke
test:

```bash  theme={null}
asc ads auth status --validate --output json
```

## Campaign and Ad Group Inventory

Query campaigns first, then inspect ad groups under one confirmed campaign ID:

```bash  theme={null}
asc ads campaigns find \
  --ad-account "987654" \
  --file campaign-query.json \
  --output json

asc ads ad-groups find \
  --ad-account "987654" \
  --file ad-group-query.json \
  --output json
```

For a full v1 query, keep pagination in the request body:

```bash  theme={null}
asc ads campaigns find --ad-account "987654" --file campaign-query.json --output json
asc ads ad-groups find --ad-account "987654" --file ad-group-query.json --output json
```

Do not use report payloads as inventory replacements. Reports answer
performance questions; list and find endpoints answer "what exists right now?"
questions.

## Report Workflow

Use Platform API v1 for new report automation. Its body shape differs from v5:

```json platform-report.json
{
  "pagination": {"offset": 0, "pageSize": 20},
  "filters": [
    {"field": "campaignId", "operator": "EQUALS", "value": ["444555666"]}
  ],
  "groupBy": ["countryOrRegion"],
  "timeRange": {
    "start": "2025-01-01",
    "end": "2025-01-31",
    "timeZone": "ORTZ",
    "granularity": "DAILY"
  }
}
```

```bash  theme={null}
asc ads reports apps campaigns \
  --ad-account "987654" \
  --file platform-report.json \
  --output json
```

The v1 report response keeps Apple's raw `result` and `pagination` fields.
Change `offset` and `pageSize` in the file to fetch another page. Run
`asc ads reports apps campaigns --help` for body and option guidance; report
entities do not all accept the same `groupBy` values.

### Legacy v5 report compatibility

The request below is the deprecated v5 shape. Keep it only while existing
automation migrates:

```json reporting-request.json
{
  "startTime": "2026-05-01",
  "endTime": "2026-05-31",
  "returnRowTotals": true,
  "returnGrandTotals": true,
  "selector": {
    "pagination": {
      "offset": 0,
      "limit": 100
    },
    "orderBy": [
      {
        "field": "impressions",
        "sortOrder": "DESCENDING"
      }
    ]
  }
}
```

Run the campaign report with an explicit org and output format:

```bash  theme={null}
asc ads v5 reports campaigns \
  --org "123456" \
  --file reporting-request.json \
  --output json
```

For ad-group, keyword, search-term, or ad-level reports, first verify the parent
campaign ID with `asc ads v5 campaigns list`. Report pagination lives inside the
Apple Ads request body, so update the file's selector pagination rather than
adding `--paginate`.

## Raw API Safety

Use `asc ads api request` for Platform API v1 debugging, newly added Apple
fields, or support captures. Prefer first-class commands for routine work.

Read-only raw request:

```bash  theme={null}
asc ads api request \
  --method POST \
  --path v1/campaigns/query \
  --ad-account "987654" \
  --file campaign-query.json \
  --output json
```

Raw v1 requests only accept Platform API v1 paths or
`https://api.ads.apple.com/v1/...` URLs. `DELETE` requests and risky mutation
payloads require `--confirm`; multipart asset upload must use
`asc ads assets upload`.

For legacy troubleshooting only, keep `asc ads v5 api request` with its v5 path
and `--org` context:

```bash  theme={null}
asc ads v5 api request \
  --method POST \
  --path v5/campaigns/find \
  --org "123456" \
  --file selector.json \
  --output json
```

Raw requests only accept Apple Ads v5 paths or Apple Ads API URLs. `DELETE`
requests require `--confirm`; do not pass it until the command line includes
the exact target path you intend to delete.

## Mutation Safety Checklist

Before running create, update, delete, or bulk commands:

1. Run the matching `--help` command and confirm required flags.
2. Resolve account access with `asc ads acls list --output json`.
3. Run a read-only resource check:
   `asc ads apps search --ad-account "987654" --query "Example" --limit 1 --output json`.
4. Pass `--ads-profile` and `--ad-account` explicitly when more than one account is possible.
5. Store request bodies in reviewed JSON files; avoid shell-escaped inline JSON.
6. Create paused or clearly named test resources when validating workflow shape.
7. Print or log the target IDs before using `--confirm`.
8. Prefer deleting temporary parent campaigns only after confirming Apple allows the cleanup.

Example guarded delete:

```bash  theme={null}
asc ads campaigns view --ad-account "987654" --campaign "campaign-id" --output json
asc ads campaigns delete --ad-account "987654" --campaign "campaign-id" --confirm
```

## Troubleshooting Auth and Source Precedence

Use the auth doctor first:

```bash  theme={null}
asc ads auth doctor --output json
asc ads auth status --verbose --output json
```

Common fixes:

* Missing ad account: pass `--ad-account`, export `ASC_ADS_AD_ACCOUNT_ID`, or store `--ad-account` during login.
* Wrong profile: pass `--ads-profile "Marketing"` or export `ASC_ADS_PROFILE`.
* Keychain prompts in CI: export `ASC_ADS_BYPASS_KEYCHAIN=1`.
* Mixed sources: set `ASC_ADS_STRICT_AUTH=1` to fail when both profile and env token/key sources are present.
* Token-only runs: `ASC_ADS_ACCESS_TOKEN` still needs `--ad-account`, `ASC_ADS_AD_ACCOUNT_ID`, or a stored profile account for scoped v1 commands.

When debugging API responses, use an explicit output format and redact tokens,
client IDs, team IDs, key IDs, org IDs, and account names before sharing logs.
