Drafts
What a draft is
Every write goes on the record before it goes to a platform. When you, the
in-app agent, or your own AI over MCP asks for a change to a
campaign, an ad group or an ad, Tempio writes a change_request — a draft —
and stops. Nothing reaches the ad account until that row is approved.
A draft holds the operation, the entity it targets, the payload it would write,
the state that entity is in right now, and the checks it passed on the way in.
It waits at the gate: Drafts in the app, list_drafts over MCP.
| Status | What it means |
|---|---|
pending_approval | Waiting at the gate. It can be edited, approved or rejected here, and this is the only status approval moves a row out of. |
applying | Approved and claimed. The apply is running against the platform. |
applied | The platform took the change, and Tempio recorded the ids it created. |
partial | The operation half-landed. |
failed | The platform refused the change, or the apply ran out of retries. Nothing was applied. Editing its payload puts it back to waiting, which is how a stopped batch is resumed. |
rejected | Turned down at the gate. Terminal, and nothing was ever sent. |
partial exists because some operations are more than one call. Creating an ad
on Meta is two: the creative, then the ad that uses it. If the creative lands
and the ad does not, neither "applied" nor "failed" is true, so the row is
partial and records which halves succeeded and which failed. Google's writes
are atomic, so a Google row is never partial.
The step from pending_approval to applying is an atomic claim: two people
pressing approve at the same moment produce one apply, and the second gets an
error rather than a duplicate change.
When an apply stops answering. The apply runs outside the request, retries
transient platform errors, and writes the platform's id to the row before it
touches anything else, so a retry sees the id rather than the platform. If a
transport failure leaves the outcome genuinely unknown, the row stays
applying and says so instead of guessing. A draft still applying after 15
minutes can be marked failed by hand: check the platform yourself, then close
the row with Mark failed, which records that the outcome was never
confirmed.
Batches
A change that spans entities — a campaign, an ad group inside it, and an ad
inside that — is one draft per entity, never one draft that means three things.
The rows share a correlationId, carry their position in the run, and are
reviewed and approved as a single unit. A batch carries up to 50 operations.
Rows apply in the order they were written. A child create leaves its parent's id out of its own payload and takes it from the row before it once that row has actually landed, so an ad group is attached to the campaign this run created rather than to an id someone guessed in advance.
Nothing stores a batch status. The rollup you read — "3 of 5 applied", and what is still waiting, partial, failed or rejected — is counted from the rows every time the page is drawn.
When a row fails. The batch stops at that row. Rows behind it were claimed
but never attempted, so they go back to the gate as waiting, each one marked
with the id of the change that stopped the run. What already landed stays
landed: nothing is rolled back, because a compensating write is itself a real
change to live spend, made at the moment Tempio's view of platform state is
least certain. A row that half-landed — partial — stops the batch the same
way a failure does, and for the same reason: the rows behind it were drafted
against a structure that only partly exists.
The batch is resumable. Edit the failed row's payload with whatever the platform's error says fixed, and it goes back to waiting — re-validated, re-guarded, and no longer carrying the error of the attempt that died. Then approve the group again. Approval claims only the rows still waiting and skips the ones that already landed, so the run carries on from where it stopped: a child create takes its parent's id from the row that landed in the earlier run, never from a second copy of the parent. If the change cannot be fixed here, the group is finished as it stands — what landed stays landed, and you draft the change again.
What a draft can change
The operations below are the entity-and-verb surface each platform can express. A platform's adapter is what makes them real, and the adapters land one at a time: Meta first, then Google and TikTok.
| Entity | Verb | Meta | TikTok | Note | |
|---|---|---|---|---|---|
| Campaign | create | yes | yes | yes | A Google search campaign is created with its budget and keywords, so it can serve |
| Campaign | rename | yes | yes | yes | |
| Campaign | pause, resume | yes | yes | yes | TikTok creates disabled, so going live is a second recorded change |
| Campaign | archive | yes | n/a | n/a | Google and TikTok refuse archived |
| Campaign | remove | n/a | yes | yes | Terminal on both |
| Campaign | daily budget | yes | yes | yes | |
| Campaign | lifetime budget | yes | yes | yes | The mode is part of the payload, not an assumption |
| Campaign | bid strategy and targets | yes | yes | yes | |
| Campaign | schedule and dayparting | yes | yes | yes | |
| Campaign | special ad categories | yes | yes | yes | Each platform's own field |
| Ad group | create | yes | yes | yes | |
| Ad group | rename | yes | yes | yes | |
| Ad group | pause, resume | yes | yes | yes | |
| Ad group | remove | n/a | yes | yes | |
| Ad group | budget | yes | n/a | yes | Google budgets attach to campaigns |
| Ad group | bid | yes | yes | yes | |
| Ad group | targeting | yes | yes | yes | A complete replacement, never a partial edit |
| Ad | create | yes | yes | yes | |
| Ad | rename | yes | n/a | yes | Google responsive search ad names are not settable |
| Ad | pause, resume | yes | yes | yes | Status is the only mutable field on a live Google ad |
| Ad | copy and creative | yes | yes | yes | Google replaces the ad and removes the old one in one atomic change |
| Ad | remove | n/a | yes | yes | |
| Creative | create, update | yes | n/a | n/a | Google's creative is the ad; TikTok's lives on the ad |
| Media | upload | yes | yes | yes | |
| Keyword | create, update, remove | n/a | yes | n/a | Positive and negative |
| Negative list | create, update | n/a | yes | n/a | Shared across campaigns |
| Asset group | create, update | n/a | yes | n/a | Performance Max, with its assets in one change |
| Lead form | create | yes | n/a | n/a | An ad references it by id |
| Account defaults | update | yes | yes | yes | Page, pixel, identity, conversion action and the like |
Everything in that table goes through the gate, account defaults included.
Guardrails
Guardrails are checked when the draft is written, again when it is approved, and once more inside the apply, immediately before the platform is called. A draft can sit behind three others while the account moves underneath it, so the last check is the one that decides.
The kill switch. A connection that is not active blocks every apply on it. Pausing a connection on Connections is the single control that stops Tempio writing to that account, whatever is queued and whatever rule would otherwise have cleared it.
The account's daily cap. Each ad account gets its own ceiling: three times the highest single-campaign daily spend it recorded in the last 28 days, floored at the default for its currency. It is computed from the account's own history because a number picked for a demo blocks a real account's real campaigns, and refreshed by the weekly deep re-read. A lifetime budget is checked as its daily equivalent over the flight it states.
The 100% rule. A budget may not move more than 100% from what the entity spends today, in either direction.
The first budget. An entity with no budget yet is held to the initial cap for its currency. Once an account's own derived cap has risen above the currency default — which only happens on the strength of its own 28 days — that derived cap is the ceiling for a first budget too.
The 24-hour aggregate. Applied budget increases on one connection are summed over the last 24 hours, and the sum plus the raise you are approving must stay under the account's daily cap. This is the check that stops a run of individually legal increases from doubling an account by dinner.
Currency. Money is an integer in the account's own minor units, and the currency is stamped onto the payload from the account. It is never read from whatever asked for the change. Caps are compared in the same currency and never converted.
The platform's own answer. Where a platform offers a dry run, Tempio asks it while the draft is being written and records the verdict on the row, so a refusal — with the platform's own message and the fields it names — is visible at the gate rather than after the change was approved.
Approved by you, or by a rule you wrote
Every applied change records what cleared it: you, an MCP key, or a rule.
- You. Approving in the app records your user against the row.
- An MCP key. A key minted with the
approvescope may approve, including a draft it wrote itself. The record says so — the gate reads "Approved by the MCP key that drafted it" — because a key approving its own work is a different fact from a person approving it. The in-app agent never holds that scope. - A rule. An approval rule your workspace wrote can clear a draft in place of a person. The row names the rule, and so does the change's own page: "Approved by a rule", with the rule's name.
One rule kind exists today, and it is off until someone writes one. It names one connection or every connection in the workspace, and can approve pauses, and budget or bid decreases below an amount it states in a currency it states. That is the whole of it. Rules are written by an owner or an admin under Settings → Approvals, and Approvals is the page for them — and for the chains that hold a change until several people have signed.
A rule never approves:
- a create of any kind, a media upload, a removal, or a change to account defaults;
- a budget or bid increase, an amount over the rule's own cap, or an amount in a currency other than the rule's;
- a change whose direction cannot be read — no pre-image, a budget that switched between daily and lifetime, a field the account's mirror never held;
- a draft the platform's dry run refused;
- a draft carrying a special ad category, which is a compliance answer a person signs;
- anything on a connection whose kill switch is on.
A batch is one reviewable unit, so a rule clears a batch only when it clears every row in it. One row a rule cannot approve sends the whole group to the gate.
Undo
An undo is a new draft, built from the state the entity was in before the change, and it goes to the same gate as anything else. Tempio never reverses a change behind your back.
| What was applied | What the undo is |
|---|---|
| A pause | Resume. The exact inverse, always available |
| A budget, bid, name or schedule change | Put back what was there before |
| A targeting or creative change | Put back the previous spec exactly, because these writes are complete replacements |
| A create | Pause what it made. Pausing is not deleting, and the new draft says so |
| An account defaults change | Put back the previous defaults |
| A removal | Nothing |
A removal has no undo. The entity is gone on the platform and nothing here can bring it back, which is why a removal at the gate says that in a sentence before you approve it. A media upload has none either: the asset stays in the account's library, and an ad built from it is a change of its own. Undo is also refused for a change that never landed, and for one whose pre-image was not recorded.
The audit log
Every move a draft makes is a row in the audit log: written, edited, approved, applied, refused, reverted behind a failed sibling, rejected, undone, or marked failed by hand after it stopped answering. Each row names who acted — a person, the agent, an MCP key, or a rule — and, for an approval, which authority cleared it and whether the approver was the same actor that drafted the change.
The log records an actor by name rather than by a link to a user account, so it holds a rule or an MCP key as readily as a person, and it outlives the draft: a change request can be edited or superseded, and what happened to it stays readable on the entity's own activity feed.
Campaigns
What the campaign pages show, what a row can change, and why every one of those changes is a draft before it is a change.
Guardrails
The checks a change passes before it reaches an ad platform — the kill switch, the account's own daily cap, the movement rule, the first-budget cap and the 24-hour aggregate — and the overrides a workspace writes over them.