> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ancha.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These docs describe the Ancha CLI, npm package @ancha-labs/cli version 0.1.4, and the hosted workspace at app.ancha.io. Coding agents should start at https://docs.ancha.io/coding-agents. Run the CLI as `ancha` after `npm install -g @ancha-labs/cli`, or as `npx -y @ancha-labs/cli@0.1.4` without installing. Pass --json for machine-readable output, or set ANCHA_JSON=1. Every command ends with a `next:` line naming the next command. Exit codes: 0 pass, 1 a check failed or a comparison regressed, 2 usage, config or authentication error, 3 infrastructure error, 4 unimplemented endpoint with --strict.

# Google Ads

> A stateful replica of the Google Ads API v26 over REST and gRPC: accounts, GAQL search, and mutations for budgets, search campaigns, ad groups, responsive search ads and keywords.

The Google Ads replica implements API v26 over both REST and gRPC, on the same data. It covers GAQL search and mutations for search campaigns with manual CPC. Its key is `google-ads`. It was tested with the official Python client, `google-ads` 34.0.0, over gRPC.

## Connect

| Variable | Value |
| - | - |
| `GOOGLE_ADS_API_URL` | REST base URL. Paths start with `/v26/`. |
| `GOOGLE_ADS_ACCESS_TOKEN` | Fake OAuth access token. Send `Authorization: Bearer <token>`. |
| `GOOGLE_ADS_GRPC_ENDPOINT` | Local gRPC endpoint for the official clients. |
| `GOOGLE_ADS_TLS_ROOT_CERT_BASE64` | Base64 PEM certificate the gRPC client must trust for the local endpoint. |

Send `login-customer-id: 1000000000`, the manager account, to reach the client account. No developer token is needed.

```js Node (REST) theme={null}
const response = await fetch(process.env.GOOGLE_ADS_API_URL + '/v26/customers/1000000001/googleAds:search', {method:'POST',headers:{Authorization:'Bearer '+process.env.GOOGLE_ADS_ACCESS_TOKEN,'login-customer-id':'1000000000','Content-Type':'application/json'},body:JSON.stringify({query:'SELECT campaign.id, campaign.name, campaign.status FROM campaign'})});
const campaigns = await response.json();
```

```python Python (gRPC) theme={null}
import base64, os, tempfile
# Set before the gRPC client initializes; certificate is for local mock transport only.
root = tempfile.NamedTemporaryFile(suffix='.pem')
root.write(base64.b64decode(os.environ['GOOGLE_ADS_TLS_ROOT_CERT_BASE64']))
root.flush()
os.environ['GRPC_DEFAULT_SSL_ROOTS_FILE_PATH'] = root.name
from google.ads.googleads.client import GoogleAdsClient
from google.oauth2.credentials import Credentials
client = GoogleAdsClient(credentials=Credentials(token=os.environ['GOOGLE_ADS_ACCESS_TOKEN']), endpoint=os.environ['GOOGLE_ADS_GRPC_ENDPOINT'], login_customer_id='1000000000', version='v26', use_proto_plus=True)
rows = client.get_service('GoogleAdsService').search(customer_id='1000000001', query='SELECT campaign.id, campaign.name, campaign.status FROM campaign')
for row in rows: print(row.campaign.name)
# Tested with google-ads==34.0.0. Keep root alive while the client is in use.
```

These are the snippets `ancha connect google-ads --lang ts` and `--lang py` print.

## Endpoints

| Method | Path |
| - | - |
| `GET` | `/v26/customers:listAccessibleCustomers` |
| `POST` | `/v26/customers/:customerId/googleAds:search` |
| `POST` | `/v26/customers/:customerId/googleAds:searchStream` |
| `POST` | `/v26/customers/:customerId/campaignBudgets:mutate` |
| `POST` | `/v26/customers/:customerId/campaigns:mutate` |
| `POST` | `/v26/customers/:customerId/adGroups:mutate` |
| `POST` | `/v26/customers/:customerId/adGroupAds:mutate` |
| `POST` | `/v26/customers/:customerId/adGroupCriteria:mutate` |
| `POST` | `/v26/customers/:customerId/googleAds:mutate` |

Each REST route has the matching gRPC method, such as `GoogleAdsService.Search` and `CampaignService.MutateCampaigns`. `googleAds:mutate` exists only to report that cross-resource mutations and temporary IDs are not implemented. Unknown REST routes return Google's error shape with `x-ancha-gap: 1`; unknown gRPC methods return `UNIMPLEMENTED`.

## Behavior

* **GAQL.** `SELECT ... FROM` one of `customer`, `customer_client`, `campaign_budget`, `campaign`, `ad_group`, `ad_group_ad`, `ad_group_criterion`. `WHERE` predicates joined with AND (`=`, `!=`, `<`, `>`, `<=`, `>=`, `IN`, `BETWEEN`), `segments.date DURING` `TODAY`, `YESTERDAY`, `LAST_7_DAYS`, `LAST_14_DAYS` or `LAST_30_DAYS`, `ORDER BY` and `LIMIT`. No OR, LIKE, functions or REGEXP.
* **Search pages** hold up to 10,000 rows; `pageSize` is rejected, as in v26.
* **Mutations** cover budgets, SEARCH campaigns with manual CPC, ad groups, responsive search ads and keywords (EXACT, PHRASE, BROAD). They are atomic by default and support `partialFailure`, `validateOnly` and `responseContentType`.
* **Review.** New ads stay `REVIEW_IN_PROGRESS`. Nothing serves, bills or delivers.
* **Metrics** (`impressions`, `clicks`, `cost_micros`, `conversions`) come only from the historical starter rows.

## Starter data

The `google-ads-starter` dataset belongs to Acme.

| Record | ID | Detail |
| - | - | - |
| Manager account | `1000000000` | Acme manager. The token is granted this account. |
| Client account | `1000000001` | Acme US, USD, America/Los\_Angeles, under the manager. |
| Account | `1000000002` | Acme Europe (restricted), EUR. Not reachable with the token. |
| Budget | `2001` | "Enterprise search daily budget", 150 USD a day (`150000000` micros). |
| Campaign | `3001` | "Ridgeway Robotics · enterprise intent", SEARCH, manual CPC, enabled. |
| Ad group | `4001` | "Robotics operations buyers", CPC bid 6.50 USD. |
| Ad | `4001~5001` | A responsive search ad for `https://acme.example/ridgeway`, approved. |
| Keyword | `4001~6001` | "robotics operations software", phrase match. |

Reports cover 2026-10-24 and 2026-10-25 for campaign `3001`.

## Checks

Google Ads has no state checks. Use trace checks, for example `{ called: "POST /v26/customers/1000000001/campaigns:mutate" }`. Trace paths for gRPC calls use the full RPC name.

## Limits

* Worlds with Google Ads cannot run in playgrounds, and `ancha run` rejects them with container execution (`CONFIG_INVALID`). Use them with `ancha run` as a local process or in a sandbox.
* Search campaigns with manual CPC only: no Performance Max, display, video, smart bidding, audiences, assets, conversion uploads or recommendations.
* No auction, serving or spend simulation.


## Related topics

- [Worlds](https://docs.ancha.io/concepts/worlds.md)
- [Tools](https://docs.ancha.io/tools/overview.md)
- [Introduction](https://docs.ancha.io/index.md)
- [Troubleshooting](https://docs.ancha.io/troubleshooting.md)
- [Test file (scene.yaml)](https://docs.ancha.io/reference/test-file.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.