Academyby Dasow

Stop 1 of 10 · Set once

Tokens, scopes and the apply rule

Three platform credentials at the smallest scope that works, kept outside the repository, and the one rule every system in this course obeys.

Watch first · 0:57

What changes when you own the system

Nour now has twenty accounts, five people and Rafik, a developer on contract two days a week. The bottleneck moved. The work that eats the agency is no longer thinking, it is the same nine steps repeated twenty times, and the only way to buy that back is to write it down as code the agency owns.

Everything here is code you write and run, and every stop ends with a file and a test. Stop one is what has to be right first: what the credentials can do, where they live, and what the systems may never do without a person.

One scope per job

Ask for the smallest scope that lets the job finish, and confirm the current scope names in the platform documentation before you request access. They change, and a wrong request costs a fortnight of review.

Platform What the systems read Where to confirm the scope
Google Ads Campaign, ad group and search term rows developers.google.com/google-ads/api
Meta Ad, ad set and campaign insights developers.facebook.com/docs/marketing-api
GA4 Sessions, conversions and channel rows developers.google.com/analytics/devguides/reporting/data/v1

Two credential roles, not one. The reader is what nineteen of your twenty jobs load. The writer exists on one host, is loaded by one file, and is rotated on a date you write down. On Meta that is the difference between read access and management access.

Build the config loader and its test
I run a paid media agency. This repository holds our internal systems and reads Google Ads, Meta and GA4 for twenty client accounts.

Write config.py. It loads credentials from environment variables only, never from a file in the repository. It exposes reader() and writer(). reader() returns the read-only credential set and raises if any write-capable variable is present in the same process. writer() raises unless the environment variable AGENCY_ROLE equals apply, so only the apply host can construct it.

Also write .env.example with the variable names and no values, add .env to .gitignore, and write test_config.py covering: reader works with only read variables set, reader raises when a write variable is present, and writer raises when AGENCY_ROLE is unset. Run the tests and show me the output.

Nothing applies without a signature

Say it once, in the Project and in the code. Every system here reads, joins, scores, drafts and proposes. Exactly one file applies a change to an ad account, and it refuses unless a human signature exists for the exact bytes of the proposal. Stop five builds that file, and every stop before it is written as if it already exists.

Redaction before anything reaches a chat

Client spend and margins do not enter a chat as a raw export. They go through the agency redaction step first: client names become codes, the margin, retainer and internal cost columns are dropped, and customer lists never travel at all.

Write the redaction step and prove it
Write redact.py with one function, redact(rows, allowed_account). It replaces the client_name column with the code from accounts.yaml, drops any column named margin, retainer, internal_cost or agency_fee, drops any column holding an email address or a phone number, and drops every row whose account code is not allowed_account.

Write test_redact.py with a fixture carrying all four forbidden columns, two accounts and one email column. Assert the output has no forbidden column, no email, and only the allowed account.
The Agency systems Project instructions
You support Nour, who runs a six-person paid media agency with twenty client accounts coded A01 to A20 and a contract developer named Rafik.

You may read this repository, write and change code, run tests, and propose changes to ad accounts. You may never call a write endpoint on the Google Ads API, the Meta Marketing API or any other ad platform, and you may never suggest that a system apply a change directly. Writes go through apply.py, which requires a matching human signature.

Every number you put in a client-facing file must exist in the run data you were given. If a figure is missing, write the missing marker rather than an estimate.

Client names never appear next to spend or margin. Use the account code, and run anything pasted into a chat through redact.py first.

When you write code, write the test in the same reply and run it.

Quick check

Try it

Report a bug or share feedback