A simple, modern Python REST client for the Google Ad Manager API.
pyadmanager is a developer-friendly way to read data out of Google Ad Manager: typed, autocomplete-able filter builders, automatic pagination and retries, and one GAMClient entry point — no code generation, no WSDL, no hand-built query strings.
For comparison, Google offers two official clients:
googleads— the legacy SOAP API client. Powerful, but heavyweight and tied to SOAP's older, more ceremonious request/response model.google-ads-admanager— the official REST API (v1) client, code-generated from the API's protobuf definitions. Complete, but as with most generated clients, it prioritizes full API coverage over an ergonomic, idiomatic-Python feel.
pyadmanager targets that same REST API but is hand-written for ergonomics: it trades full write-API coverage (it's read-only today — see Roadmap) for a small, typed surface that's easy to read, autocomplete, and reason about. If you just need to pull line items, run a report, or look up custom targeting keys through the Ad Manager REST API (v1), it does that without you needing to hand-roll requests calls or re-derive GAM's filter-query grammar (field = "value" for strings, bare true/false for booleans, RFC-3339 strings for dates) yourself.
pyadmanager wraps that REST API in a thin, typed client:
- One
GAMCliententry point, one resource client per GAM resource (.line_item,.report,.order, ...), each built lazily and cached. - Typed
listfilter keyword arguments per resource so your editor autocompletes valid filter fields andLiteralenum values (e.g.LineItemType), andpyrightcatches typos before you hit the API. - Pagination, retries (with exponential backoff on 429/500/502/503/504), and GAM's filter-string quoting rules handled for you.
- Read-only today. Every resource client currently only implements
list/get, even for resources where the REST API supports writes.create/update/deletesupport is on the roadmap — if you need write access right now, this isn't yet the library for you.
It's aimed at ad-ops engineers and backend developers who need to read Ad Manager data (line items, orders, reports, inventory) into Python — for dashboards, ETL pipelines, or one-off scripts — without pulling in the full googleads SOAP stack.
- Typed, per-resource clients —
line_item,order,placement,ad_unit,custom_targeting,role,user,network,private_auction,private_auction_deal,programmatic_buyer, andreport. - Correct GAM filter-string building —
GAMRestFilterscentralizes the quoting rules (quoted strings/dates, bare numbers/booleans) so you never hand-write a malformedfilterquery param. - Automatic pagination —
listmethods page throughnextPageTokenand return the fully collected list. - Built-in retry with backoff — transient errors (429/500/502/503/504, connection/timeout errors) are retried automatically.
- Async report support —
run_report()kicks off a GAM report job and returns aReportJobyou can poll and pull rows from, optionally parsed straight into apolars.DataFrame. - Cross-resource id resolution — pass a bare
order_id/key_id/parent_ad_unit_idint and it's resolved into the correct GAM resource path for you.
Requires Python 3.13+.
pip install pyadmanagerTo use ReportJob.fetch_rows_as_dataframe() / parse_report_rows(), install the optional polars extra:
pip install "pyadmanager[polars]"Without the extra, everything else works normally — parse_report_rows just raises a clear ImportError if you call it without polars installed.
from pyadmanager import GAMClient
client = GAMClient.from_service_account_file(
network_code="123456789",
filename="creds.json",
readonly=True, # requests the read-only OAuth scope
)
# List every non-archived line item for a given order
line_items = client.line_item.list(order_id=98765, archived=False)
for item in line_items:
print(item["displayName"], item["status"])from pyadmanager import GAMClient
client = GAMClient.from_service_account_file(
network_code="123456789",
filename="creds.json",
)
# Fetch a single line item by numeric id
line_item = client.line_item.get(line_item_id=112233)
print(line_item["name"], line_item["lineItemType"])
# List active custom targeting keys
keys = client.custom_targeting.list_keys(status="ACTIVE")
for key in keys:
print(key["displayName"])from datetime import datetime, timedelta, timezone
from pyadmanager import GAMClient
from pyadmanager.filters import GAMRestFilters
client = GAMClient.from_service_account_file(
network_code="123456789",
filename="creds.json",
)
# Filter using a (value, filter_type) tuple to build CONTAINS/GT_EQ-style clauses
one_week_ago = datetime.now(timezone.utc) - timedelta(days=7)
delivering_line_items = client.line_item.list(
display_name=("Q3", "CONTAINS"), # displayName = "*Q3*"
status=["DELIVERING", "READY"], # (status = "DELIVERING" OR status = "READY")
start_time=(one_week_ago, "GT_EQ"), # startTime >= "2026-..."
page_size=500, # tune the page size (default 1000)
)
# Run a saved report and pull results straight into a polars DataFrame
# (requires: pip install "pyadmanager[polars]")
job = client.report.run_report(report_id=445566)
df = job.fetch_rows_as_dataframe() # polls until GAM finishes computing rows
print(df.head())
# Handle report failures explicitly instead of letting fetch_rows_as_dataframe raise
try:
job.wait_till_complete(sleep=5.0)
except ValueError as exc:
print("report job failed:", exc)
else:
rows = job.fetch_rows() # raw pages, if you'd rather parse them yourself| Class / Method | Inputs | Output | Description |
|---|---|---|---|
GAMClient.from_service_account_file(network_code, filename, readonly=False, **kwargs) |
GAM network code, path to a service-account JSON key, optional readonly flag |
GAMClient |
Builds credentials from a key file on disk and returns a ready-to-use client. |
GAMClient.from_service_account_info(network_code, info, readonly=False, **kwargs) |
GAM network code, service-account key as a dict |
GAMClient |
Same as above, for key material loaded from a secrets manager rather than a file. |
GAMClient.<resource> |
— | resource client (e.g. LineItemClient) |
Lazily built, cached per-resource client sharing the parent's network code and session. |
<Resource>Client.list(**filters, page_size=1000) |
typed filter kwargs per resource | list[dict] |
Pages through every matching result via nextPageToken. |
<Resource>Client.get(id) |
numeric resource id | dict |
Fetches a single resource by id. |
ReportClient.run_report(report_id) |
numeric report id | ReportJob |
Starts async generation of a saved report's rows. |
ReportJob.wait_till_complete(sleep=2.5) |
poll interval (seconds) | None |
Blocks until the report job is done; raises ValueError on job failure. |
ReportJob.fetch_rows(sleep=2.5) |
poll interval (seconds) | list[dict] |
Polls to completion if needed, then returns raw report result pages. |
ReportJob.fetch_rows_as_dataframe(sleep=2.5) |
poll interval (seconds) | polars.DataFrame |
Same as fetch_rows, parsed into a DataFrame (requires the polars extra). |
GAMRestFilters.text_filter/number_filter/boolean_filter/date_filter/id_based_filter |
field name, value (or (value, FILTER_TYPE) tuple) |
str |
Low-level building blocks for GAM's filter-query grammar — used internally by every resource's list method. |
- Auth scope —
from_service_account_file/from_service_account_infodefault to the fullhttps://www.googleapis.com/auth/admanagerscope; passreadonly=Trueforhttps://www.googleapis.com/auth/admanager.readonly, or an explicitscopes=[...]kwarg to override either default. - Extra credential kwargs — any additional keyword arguments passed to the
from_service_account_*constructors are forwarded directly togoogle.oauth2.service_account.Credentials. - Page size — every
listmethod acceptspage_size(default1000) to tune how many results are fetched per underlying HTTP request; pagination across pages happens automatically regardless of this value. - Report poll interval —
ReportJob.wait_till_complete/fetch_rows/fetch_rows_as_dataframeaccept asleepargument (default2.5seconds) controlling how often the async report job's status is polled. - Logging — the library logs via the standard
loggingmodule (logging.getLogger("pyadmanager...")); enableDEBUGlogging to see outgoing request URLs/params and report-job poll iterations, orINFOfor lifecycle events like credential loading and report job completion.
- Write support (
create/update/delete) for resources where the underlying REST API supports it (e.g.orders,lineItems,placements,adUnits). The library is read-only today; this is the main planned expansion of scope. - New resource coverage, following the existing
services/<resource>.pypattern as GAM's REST API surface grows.
Have a resource or write operation you need sooner? Open an issue — it helps prioritize the roadmap.
Contributions are welcome! To get set up locally:
uv sync --extra polars # install all deps, including the optional polars extra
uv run pytest # run the test suite
uv run ruff check . # lint
uv run pyright src tests # type checkPlease open an issue before starting on a larger change, especially write support (see Roadmap) so the approach can be agreed on before you invest the time. Bug reports and PRs for new read-only resources are especially appreciated — see services/ for the pattern each resource client follows.
MIT — see LICENSE for details.