pyadmanager
A simple, modern Python REST client for the Google Ad Manager API.
Overview / Why pyadmanager?
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
list_*filter 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.
Features
- 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 —
list_*methods 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.
Installation
Requires Python 3.13+.
pip install pyadmanager
To 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.
Quick Start (The 80/20 Example)
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_line_items(order_id=98765, archived=False)
for item in line_items:
print(item["displayName"], item["status"])
Detailed Usage & Code Examples
Basic Example
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(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"])
Advanced Example
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_line_items(
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
API Overview / Key Reference
| 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_<resource>(**filters, page_size=1000) |
typed filter kwargs per resource | list[dict] |
Pages through every matching result via nextPageToken. |
<Resource>Client.get_<resource>(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. |
Configuration & Customization
- 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
list_*method 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.
Roadmap
- 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.
Contributing
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 check
Please 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.
License
MIT — see LICENSE for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyadmanager-0.1.0.tar.gz.
File metadata
- Download URL: pyadmanager-0.1.0.tar.gz
- Upload date:
- Size: 21.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19f7aef6e6b0ed08ddc0cd489f0cb3563740c40f16ca4fffdc064ac3579dbde8
|
|
| MD5 |
5e824ede2b3337180b9a325d3bc3a988
|
|
| BLAKE2b-256 |
93abb7ea3168301ee9b72c5f6b92a00312ed3573856727bffe974b0f93f3ddef
|
Provenance
The following attestation bundles were made for pyadmanager-0.1.0.tar.gz:
Publisher:
release.yaml on shani-suthar/pyadmanager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyadmanager-0.1.0.tar.gz -
Subject digest:
19f7aef6e6b0ed08ddc0cd489f0cb3563740c40f16ca4fffdc064ac3579dbde8 - Sigstore transparency entry: 2387453292
- Sigstore integration time:
-
Permalink:
shani-suthar/pyadmanager@839fd5fcd07279beefee29e3e1b9bc89aa4a4b07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shani-suthar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@839fd5fcd07279beefee29e3e1b9bc89aa4a4b07 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyadmanager-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyadmanager-0.1.0-py3-none-any.whl
- Upload date:
- Size: 33.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d7f4cb3e7d41bdbd7b68813367585a716c283434a2463eead4d45011415ba8c4
|
|
| MD5 |
60dd2e678035e5d87e4999009e7f4f0d
|
|
| BLAKE2b-256 |
15a2121f38d28254321c778dcd6fe32d2dc44f6120123182cce7d8271fdb6c86
|
Provenance
The following attestation bundles were made for pyadmanager-0.1.0-py3-none-any.whl:
Publisher:
release.yaml on shani-suthar/pyadmanager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyadmanager-0.1.0-py3-none-any.whl -
Subject digest:
d7f4cb3e7d41bdbd7b68813367585a716c283434a2463eead4d45011415ba8c4 - Sigstore transparency entry: 2387453296
- Sigstore integration time:
-
Permalink:
shani-suthar/pyadmanager@839fd5fcd07279beefee29e3e1b9bc89aa4a4b07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/shani-suthar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@839fd5fcd07279beefee29e3e1b9bc89aa4a4b07 -
Trigger Event:
push
-
Statement type: