Tempio Docs
Drafts

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.

StatusWhat it means
pending_approvalWaiting at the gate. It can be edited, approved or rejected here, and this is the only status approval moves a row out of.
applyingApproved and claimed. The apply is running against the platform.
appliedThe platform took the change, and Tempio recorded the ids it created.
partialThe operation half-landed.
failedThe 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.
rejectedTurned 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.

EntityVerbMetaGoogleTikTokNote
CampaigncreateyesyesyesA Google search campaign is created with its budget and keywords, so it can serve
Campaignrenameyesyesyes
Campaignpause, resumeyesyesyesTikTok creates disabled, so going live is a second recorded change
Campaignarchiveyesn/an/aGoogle and TikTok refuse archived
Campaignremoven/ayesyesTerminal on both
Campaigndaily budgetyesyesyes
Campaignlifetime budgetyesyesyesThe mode is part of the payload, not an assumption
Campaignbid strategy and targetsyesyesyes
Campaignschedule and daypartingyesyesyes
Campaignspecial ad categoriesyesyesyesEach platform's own field
Ad groupcreateyesyesyes
Ad grouprenameyesyesyes
Ad grouppause, resumeyesyesyes
Ad groupremoven/ayesyes
Ad groupbudgetyesn/ayesGoogle budgets attach to campaigns
Ad groupbidyesyesyes
Ad grouptargetingyesyesyesA complete replacement, never a partial edit
Adcreateyesyesyes
Adrenameyesn/ayesGoogle responsive search ad names are not settable
Adpause, resumeyesyesyesStatus is the only mutable field on a live Google ad
Adcopy and creativeyesyesyesGoogle replaces the ad and removes the old one in one atomic change
Adremoven/ayesyes
Creativecreate, updateyesn/an/aGoogle's creative is the ad; TikTok's lives on the ad
Mediauploadyesyesyes
Keywordcreate, update, removen/ayesn/aPositive and negative
Negative listcreate, updaten/ayesn/aShared across campaigns
Asset groupcreate, updaten/ayesn/aPerformance Max, with its assets in one change
Lead formcreateyesn/an/aAn ad references it by id
Account defaultsupdateyesyesyesPage, 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 approve scope 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 appliedWhat the undo is
A pauseResume. The exact inverse, always available
A budget, bid, name or schedule changePut back what was there before
A targeting or creative changePut back the previous spec exactly, because these writes are complete replacements
A createPause what it made. Pausing is not deleting, and the new draft says so
An account defaults changePut back the previous defaults
A removalNothing

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.