Tempio Docs
API

MCP reference

What Tempio exposes

Tempio is one workspace over several ad platforms. Every read is normalised to the same shape whichever platform it came from, and every write is a change request first: a draft that is checked against the workspace's guardrails when it is written and again at the gate, applied only when a person or a rule the workspace wrote clears it.

Tempio's programmatic surface is a Model Context Protocol server. There is no separate REST API; the MCP server and the in-app agent call the same org-scoped tool layer, so a capability behaves identically whichever client calls it.

  • Endpoint: https://app.tempio.ai/api/mcp (streamable HTTP; no SSE transport)
  • Credentials: a bearer key minted under Settings → MCP, or an OAuth token from a client that signs in instead of sending a header. Either way the workspace is taken from the verified credential, never from tool arguments.
  • Rate limits: 120 requests per minute per IP before authentication, 60 per minute per credential after.

Two tools carry the whole surface. tempio_help describes it and tempio_manage runs it, dispatching on {entity, action} onto the same capabilities the explicit surface exposes one tool each.

Which of the two a client sees is the credential's surface, and every credential carries one. compact is the default: a key already in use registers tempio_manage and tempio_help in place of the individual tools, and reaches every capability through them. A key minted as explicit registers the individual tools, plus tempio_help beside them. The capabilities, the scopes and the answers are the same on both.

The first call: tempio_help

Ask before you guess. The catalogue is generated at request time from the same tables the tools run on — the route table, the tool registry, the change operations and their payload schemas, the warning registry, and each live connection's own capabilities — so it cannot drift from the surface, and it is filtered to the scopes your credential holds, which means discovery, description and execution agree.

tempio_help({
  path?: string,                  // "manage", "manage campaign", "manage campaign create", "drafts", "money"
  q?: string,                     // keyword search across names, descriptions and payload field names
  detail?: "summary" | "full",    // "full" adds parameters and never removes them
})
ParameterWhat it does
pathWalks the catalogue from the root down to one entity, then to one verb. Called with no arguments it returns the root: what Tempio is, the entity model, the verbs, the draft lifecycle, the rules about money and ratios, warnings and links, and two worked calls.
qSearches names, descriptions and payload field names. This is how you find a capability nobody told you the path of.
detail"full" adds each call's parameters to whatever path or q selected. It only ever adds.

Before telling someone a capability is unavailable, search here:

tempio_help({ q: "<what they asked for>" })

The catalogue is generated from the same tables the tools run on, so if it is not here, it is not there either — and if it is here, the path says how to call it.

The entity model

The model is workspace → connection → ad account → campaign → ad group → ad, and it is the same on every platform. Creatives, keywords, media assets, negative lists, asset groups and lead forms hang off the level that holds them. Where a platform's own levels differ (an ad set, an asset group, a product group), Tempio maps them onto these, so one loop over campaigns, ad groups and ads works everywhere. Every entity carries the id Tempio gave it; pass that id back, never the platform's. Beside that tree, a brand_kit is the workspace's own brand as data; getting it returns the colour roles with their contrast, the type, the voice, a compliance-line template with its permit slots, the forbidden words and the labelled products. Scraping one reads a public website and answers a proposed kit — name, colour roles, type — that nothing saves until it is passed to update. A preset is a kind of still the workspace can make; listing them returns the copy fields each template renders and the ceiling on each. A creative_asset is a still Tempio made — a scene with no text in it and the copy drawn over it, one composite per aspect ratio; listing them returns each with its copy fields and a URL per ratio, and it reaches an ad only by being registered as a media asset for one account, uploaded by an upload_media draft and named in a create_ad draft's creative.media. A creative_batch is one generation request: getting it returns what was asked for, each generation's status and why one failed, and the stills that have landed so far.

What you can do: tempio_manage

tempio_manage({
  entity: ManageEntity,
  action: ManageAction,
  params?: Record<string, unknown>,
})

The outer schema is deliberately loose on params, because MCP publishes one JSON Schema per tool and a discriminated union of every entity and action would put every capability's own schema into that one envelope. The real contract is the inner parse: {entity, action} selects a capability, and that capability's own schema is what params is validated against. A wrong field answers invalid_payload: <path>: <message> — the field's own message and its path, not a generic refusal. Runtime dispatch selects the typed schema; it never replaces it.

Scopes are enforced on the call, not by hiding it. A route whose scope your credential does not hold answers forbidden: <entity>.<action> needs the '<scope>' scope; this credential holds <held>, so an agent is told what to ask for instead of concluding the capability does not exist.

Every entity and action pair tempio_manage accepts, grouped by entity, with the scope it needs, the capability it resolves to and whether it answers today.
EntityActionScopeResolves toStatus
connectionlistreadlist_connectionsAvailable
accountlistreadlist_ad_accountsAvailable
updatedraftdraft_changes (update_account_defaults · account)Available
campaignlistreadlist_campaignsAvailable
getreadget_campaignAvailable
createdraftdraft_changes (create_campaign · campaign)Available
updatedraftdraft_changes (update_budget, update_status, update_name, update_bid, update_schedule, update_targeting · campaign)Available
removedraftdraft_changes (remove_entity · campaign)Available
ad_grouplistreadlist_structureAvailable
createdraftdraft_changes (create_ad_group · ad_group)Available
updatedraftdraft_changes (update_budget, update_status, update_name, update_bid, update_schedule, update_targeting · ad_group)Available
removedraftdraft_changes (remove_entity · ad_group)Available
adlistreadlist_structureAvailable
createdraftdraft_changes (create_ad · ad)Available
updatedraftdraft_changes (update_status, update_name, update_creative · ad)Available
removedraftdraft_changes (remove_entity · ad)Available
creativelistreadlist_structureAvailable
createdraftdraft_changes (create_creative · creative)Available
updatedraftdraft_changes (update_creative · creative)Available
keywordlistreadlist_structureAvailable
createdraftdraft_changes (create_keyword · keyword)Available
updatedraftdraft_changes (update_keyword · keyword)Available
removedraftdraft_changes (remove_entity · keyword)Available
negative_listcreatedraftdraft_changes (create_negative_list · negative_list)Available
updatedraftdraft_changes (update_negative_list · negative_list)Available
asset_groupcreatedraftdraft_changes (create_asset_group · asset_group)Available
updatedraftdraft_changes (update_asset_group · asset_group)Available
lead_formcreatedraftdraft_changes (create_lead_form · lead_form)Available
medialistreadlist_media_assetsAvailable
createdraftdraft_changes (upload_media · media)Available
draftlistreadlist_draftsAvailable
getreadlist_draftsAvailable
createdraftdraft_changesAvailable
updatedraftupdate_draftAvailable
approveapproveapprove_draftsAvailable
rejectapprovereject_draftsAvailable
revertdraftrevert_draftAvailable
planlistreadlist_media_plansAvailable
getreadget_media_planAvailable
createdraftcreate_media_planAvailable
updatedraftupdate_media_planAvailable
materializedraftmaterialize_planAvailable
plan_itemcreatedraftupsert_plan_itemAvailable
updatedraftupsert_plan_itemAvailable
removedraftdelete_plan_itemAvailable
metricslistreadget_metricsAvailable
campaign_metricslistreadget_campaign_metricsAvailable
briefgetreadget_account_briefAvailable
researchsearchreadresearch_keywordsAvailable
geosearchreadgeo_searchAvailable
search_termlistreadget_search_termsAvailable
placementlistreadget_placementsAvailable
change_eventlistreadget_change_eventsAvailable
platform_requestgetreadplatform_api_requestAvailable
mutatedraftplatform_api_mutate (platform_api_request · account)Available
library_adgetreadget_library_adAvailable
searchreadsearch_libraryAvailable
library_advertiserlistreadlist_library_advertisersAvailable
getreadget_library_advertiserAvailable
createdraftadd_library_advertiserAvailable
searchreadlist_library_advertisersAvailable
library_watchlistreadlist_library_watchesAvailable
createdraftset_library_watchAvailable
updatedraftset_library_watchAvailable
removedraftset_library_watchAvailable
swipelistreadlist_swipeAvailable
createdraftupdate_swipeAvailable
updatedraftupdate_swipeAvailable
removedraftupdate_swipeAvailable
swipe_collectionlistreadlist_swipeAvailable
createdraftupdate_swipeAvailable
updatedraftupdate_swipeAvailable
removedraftupdate_swipeRefused — A swipe collection is not removed on its own: it exists while ads are saved into it. Remove the ads with update_swipe and `remove: true`, one adId at a time.
playbooklistreadlist_playbooksAvailable
getreadget_playbookAvailable
guardraillistreadlist_guardrailsAvailable
getreadlist_guardrailsAvailable
approval_chainlistreadlist_approval_chainsAvailable
getreadlist_approval_chainsAvailable
connection_permissionlistreadlist_connection_permissionsAvailable
allocationproposedraftpropose_reallocationAvailable
timelinegetreadget_timelineAvailable
pacinggetreadget_pacingAvailable
phaselistreadlist_phasesAvailable
createdraftupsert_phaseAvailable
updatedraftupsert_phaseAvailable
removedraftdelete_phaseAvailable
envelopelistreadlist_envelopesAvailable
goalcreatedraftupsert_goalAvailable
updatedraftupsert_goalAvailable
removedraftdelete_goalAvailable
brand_kitgetreadget_brand_kitAvailable
updatedraftupdate_brand_kitAvailable
scrapereadscrape_brand_kitAvailable
presetlistreadlist_creative_presetsAvailable
creative_assetlistreadlist_creative_assetsAvailable
createdraftgenerate_creativesAvailable
resizedraftresize_creativeAvailable
registerdraftregister_creative_mediaAvailable
creative_batchgetreadget_creative_batchAvailable
lane_colourupdatedraftplanner_colour_setAvailable
lane_orderupdatedraftplanner_order_setAvailable
plan_dependencycreatedraftplan_dependency_addAvailable
removedraftplan_dependency_removeAvailable
key_datelistreadplanner_key_dates_listAvailable
createdraftplanner_key_date_upsertAvailable
updatedraftplanner_key_date_upsertAvailable
removedraftplanner_key_date_removeAvailable

108 routes over 46 entities, 107 of them available today. A route whose scope the credential does not hold is refused by name rather than hidden.

The platform_request entity is the escape hatch: a call straight at a platform's own API, through that platform's client, for the endpoint Tempio has not normalised yet. It is off until an owner turns it on, and a mutation through it is still a change request. The escape hatch covers what it refuses and what Tempio does not verify.

A connection whose coverage is connect_only lists its ad accounts and takes raw requests, and Tempio models nothing else on it: no campaigns, no daily numbers, and an empty operations list, so no modelled draft reaches it. Read it with platform_request.get, which executes the read on the platform's own API and returns the platform's own answer in the platform's units. Change it with platform_request.mutate and a sentence saying what the request changes and why; the change waits at the gate for a person, because no approval rule clears a raw request. Both are off until a workspace owner turns raw requests on in Settings.

entity: "draft", action: "create" is the n-ary form: params is a list of operations with dependsOn indices, and it is how a campaign, its ad group and its ads arrive as one reviewable batch. The single-entity write forms are sugar over it — campaign.create is a one-entry batch through the same call — so there is one write path and not two.

A row that reads Refused is live and reachable and answers a refusal by design, because the verb has nothing to do at that entity. The cell carries the sentence it refuses by, and that sentence names what to call instead.

The ad library

Five entities read and write the ad library — a corpus of other people's ads captured from the public transparency archives, shared by every workspace rather than scoped to yours.

  • library_ad searches the corpus and reads one ad. Results are deliberately not filtered to your workspace, because the corpus is public data about third parties; what is yours alone is saved, savedNote and watched on each row.
  • library_advertiser lists and reads the registry, adds an advertiser by website, name or a pasted archive URL, and answers the similar-advertisers read through search with similarTo.
  • library_watch is competitor tracking: the watches this workspace holds, its unread alerts, and one call that watches, unwatches and changes the alert and digest settings, because all three are one row.
  • swipe and swipe_collection are the swipe file. list returns the collections beside the items, so the file renders in one call.

Three of those carry the draft scope and create no change request. Drafting is for a write to an ad entity in an account you connected; adding an advertiser to a shared corpus, watching one, or saving somebody else's public ad to a board changes nothing in anyone's ad account and has nothing to apply. draft is the scope because it is what a tool may put in front of a person, not a promise that it drafts — the same reason create_media_plan carries it.

Read the warnings on a library answer before reading an absence. A run that stopped at its ceiling makes an advertiser's ads a sample; an archive that covers only some regions makes an absence here not an absence everywhere; a spend range is always labelled an estimate with its basis named, and is null rather than zero where no platform published one; and the hook, offer, angle and audience are Tempio's reading of the ad's own copy, never something the advertiser said. Coverage is the rule all of that follows from.

The envelope

Every result is { data, report?, warnings }. warnings is always present and empty when nothing is wrong. Each warning carries a class (stale_sync, connection_expired, permission_missing, write_gate_closed, budget_guardrail, currency, other), a severity (info, degraded, blocking) and a sentence saying what to do. State a degraded warning before the number it qualifies; never infer a value a warning says is unavailable; a blocking warning means say what is blocked and stop. Every record a result names carries a url a person can open in Tempio.

Every call returns { data, report?, warnings }. The envelope is assembled in one place for every capability, so no tool can forget it and its shape cannot vary by tool. report states once, authoritatively, what a scope covers: its currency (null when the scope spans currencies), the timeZone the dates are in, the dateRange, the entity level, and whether the scope is the returned_rows or all_rows.

warnings is a list, always present, [] when there is nothing wrong. Each entry carries a code (snake_case, an open set), a class, a severity, a message written as what to do about it, and a subject with that record's deep-link when it is about one record.

The classes a warning can carry, and what each one says about the answer it travels with.
ClassWhat it says
stale_syncThe numbers are older than they look, or incomplete.
connection_expiredA grant needs re-authorising, and until it does this platform's rows stop moving.
permission_missingThe credential cannot do what was asked, and what it would need.
write_gate_closedA draft cannot be written here, said before one is attempted.
budget_guardrailThe amount is close to, or over, a cap — said on the read, not only at the refusal.
currencyThe money is not what it appears to be.
otherThe degrade target for a code no registry row covers, and the home for a registered code that is about none of the other six.

Severity belongs to the code, not the class: info, degraded, blocking, least to most severe.

The class is the stable thing to branch on; the code set is open and adapters add to it. A code with no registry row degrades to class other at severity info and keeps its own message rather than failing the call. A warning never changes a value: an FX warning does not round a number and a staleness warning does not hide one.

Every record a result names carries an absolute url into the app, because an agent hands the string to a person and a relative path is not something a person can open.

Every kind of record a result can name, and the path its deep-link takes into the app.
KindPath
draft/drafts/{id}
batch/drafts?batch={correlationId}
campaign/campaigns/{id}
ad_group/campaigns/{campaignId}/ad-groups/{id}
ad/campaigns/{campaignId}/ad-groups/{adGroupId}?ad={id}
plan/plans/{id}
media/media?asset={id}
connection/connections?connection={id}
account/connections?account={id}
library_advertiser/library/advertisers/{id}
library_ad/library/ads/{id}
swipe/library/swipe?item={id}
playbook/docs/playbooks/{slug}

A link is built from a row a query already returned under the workspace, never from an id in the arguments, so an id from another workspace never produces a URL because it never produces a row. The URL is not a grant either: opening it needs a session, and the page checks membership regardless.

Drafts and the gate

A draft moves pending_approval → applying → applied, partial or failed, or is rejected. It is applied when a person approves it from the review queue or when an approval rule the workspace wrote clears it, and the row records which (approverKind). Approving is a separate call from drafting, and a credential needs the approve scope for it. While a draft is applying, its outcome is in flight or unknown: do not retry, do not draft it again; read it back.

Applying a cleared change to a platform is in private beta.

Money and the numbers

Money is an integer in minor units beside its currency, and the server stamps the currency from the account, so never convert or round. Ratios (CPC, CPM, CTR, ROAS) are computed at read time and are null when the denominator is zero, never 0. A blended figure across currencies is a labelled estimate, not a total. A ranking marked raw is an order, not a ranking. A date range is in the workspace's reporting time zone, stated in the report.

Playbooks and prompts

Beyond the tools, this server serves resources and prompts. A resource is a document a client can attach to a conversation: the playbook shelf at tempio://playbooks, one playbook at tempio://playbooks/{platform}/{topic}, this catalogue at tempio://help, and an account brief at tempio://workspace/accounts/{adAccountId}/brief. A playbook is what Tempio keeps about a platform, or about one part of Tempio itself; every one carries the date a person last confirmed it, and one that has gone more than ninety days without a review says so in its warnings rather than leaving a reader to notice. A prompt is a task handed over with the workspace context and the relevant playbook already composed into it: plan a flight, review an account, explain a set of warnings, write the brief a reviewer reads at the gate. Both are filtered to this credential's scopes exactly as the tools are, and a workspace resource reaches only the accounts this credential's workspace owns.

Two calls

Two calls that cover most sessions. First, read the account:

tempio_manage({ entity: "brief", action: "get", params: { adAccountId } })

returns what is running, what it spent and what changed, with its warnings (omit adAccountId for every account the credential can see). Second, draft a campaign with an ad group and two ads as one reviewable batch:

tempio_manage({ entity: "draft", action: "create", params: { connectionId, operations: [ create_campaign, create_ad_group (dependsOn 0), create_ad (dependsOn 1), create_ad (dependsOn 1) ] } })

and hand the person the url on the batch. Nothing reaches a platform until it is cleared.

Credentials

Two kinds reach the same surface, and past the door nothing knows which one arrived.

  • A bearer key. Minted under Settings → MCP, prefixed tmpio_, shown once, hashed at rest, revocable, and bound to one workspace. The creator's membership is re-read on every call, so a key dies with the membership. This is the right credential for a server-to-server agent and for claude mcp add.
  • An OAuth token. For a client that signs in through OAuth rather than sending a bearer header — the clients a connector directory lists cannot send a header of their own. The workspace is chosen once on the consent screen and the token is bound to it for life, exactly as a key is. OAuth for MCP clients covers the flow end to end. Tempio names a particular directory's connector as supported only once a handshake against a real workspace has been recorded; until then what is true is the sentence above — a client that signs in through OAuth reaches this surface under the same scopes a key does.

A credential also carries a surface. The choice is made at the mint, under Settings → MCP: compact registers tempio_manage and tempio_help, and explicit registers each capability as a tool of its own alongside tempio_help. Both reach the same capabilities under the same scopes; the choice is how much of your agent's context the tool list costs.

Scopes

Every credential carries one or more scopes.

The scopes a Tempio credential can carry, and what each one allows.
ScopeWhat it allows
readConnections, ad accounts, campaigns, structure, media assets, metrics, drafts, media plans.
draftCreate change requests (drafts), edit and undo them, and edit media plans. Nothing reaches a live ad account.
approveApprove or reject pending drafts. Opt-in when the credential is minted. The in-app agent never holds this scope; a draft is also applied when a rule the workspace wrote approves it, and the result says which.

The capabilities

Every Tempio capability, grouped by the scope a credential needs to reach it, with the purpose each one serves.
ScopeCapabilityPurpose
readgeo_searchResolve a place name to the Google location ids a campaign can target.
get_account_briefWhat Tempio has learned about each connected ad account from its last 90 days: coverage, totals, a 28-day period against the one before it, the objective mix, spend concentration, the biggest campaigns, ROAS and CPA rankings, spenders with no conversions, and weekday rhythm.
get_brand_kitRead the workspace's brand kit: its name, the logo, the six colour ROLES (primary, secondary, accent, surface, onSurface, onAccent) each with its hex, the intent a template reads it for, and the two contrast ratios a composite draws text in (onSurface over surface, onAccent over accent), the typography with the descriptor a model acts on, the voice lines to write copy in, the compliance-line template with its {permit}/{permits} slots, the forbidden words, and the labelled products.
get_campaignRead ONE campaign by its Tempio id: status, objective, budget, flight window, delivery status and the platform's own external id, plus `url`, an absolute link a person can open in Tempio.
get_campaign_metricsPer-campaign performance for a date range as { rows, ranking }, highest spend first.
get_change_eventsGoogle's own change history: who changed what, when, from where, and exactly which fields moved.
get_creative_batchRead one creative batch: its status (queued, running, complete, partial, failed), how many generations are in each state, what was asked for, each generation with its model and — when it failed — why, and every asset that has landed so far with its copy fields and one composite URL per aspect ratio.
get_library_adOne ad from the library, in full: every creative in order, the landing page it points at and how many of the advertiser's other ads point at the same one, the derived hook, offer, angle and audience (Tempio's reading of the ad's own copy, never something the advertiser said), every region the platform published reach or spend for, the longevity score with the formula version it was computed under, and this workspace's own swipe entries for it — collection, note and all.
get_library_advertiserOne advertiser: its identity on each public archive with that source's last run, its status and whether the source is enabled at all; ad counts by status and format and how many ads appeared in the last thirty days, all computed at read time; the landing-page rollup with the query strings intact, because the UTM parameters ARE the competitor's campaign structure; and the advertisers described the same way.
get_media_planGet one media plan with its items in board order.
get_metricsBlended cross-channel KPIs plus a per-channel breakdown for a date range.
get_pacingHow one campaign or one planned flight is tracking against its plan, day by day.
get_placementsWhere a Google display, video or Performance Max campaign's money actually went: the sites, apps and YouTube channels the ads ran on.
get_playbookRead one playbook.
get_search_termsWhat people actually typed before a Google search ad showed, with the keyword that matched them and how.
get_timelineThe workspace's operating calendar: every live campaign and every planned flight that touches the window, grouped by ad account.
list_ad_accountsList canonical ad accounts.
list_approval_chainsList the approval chains this workspace has written, in the order they are considered: a chain says how many signatures a change needs and whose, and the first enabled chain that matches a draft is snapshotted onto it when the draft is created.
list_campaignsList ALL campaigns (including ones with no recent delivery) with canonical ids, status, objective and daily budget.
list_connection_permissionsWho reaches each connection in this workspace, and how far.
list_connectionsList the ad platform connections in this workspace with their status, last sync time, last sync attempt and what each platform can actually do.
list_creative_assetsThe creative gallery: this workspace's generated stills, newest first, each with its copy fields, one composite URL per aspect ratio, its warnings and a deepLink to the batch it came from.
list_creative_presetsList the creative presets this workspace can generate with: the seven system presets (product_shot, graphic_ad, marketplace_listing, prize_hero, spec_sheet, urgency_story, early_bird) and any this workspace cloned, a workspace row shadowing the system row of the same key.
list_draftsList change-request drafts and their statuses, newest first, or one batch in the order it applies when you name its correlationId.
list_envelopesThe budget envelopes a person has set on these campaigns or plan items.
list_guardrailsThe caps a budget change on each ad account is held to: the daily cap, the first-budget cap and the movement ceiling, with where each one came from and the workspace override behind it when there is one.
list_library_advertisersThe advertiser registry: who the library holds ads for, alphabetically, with each one's ad count computed at read time, the domain it was resolved from, its industry, category and tags, and whether THIS workspace watches it.
list_library_watchesList the advertisers this workspace tracks, with their alert and digest settings, plus the unread activity alerts they have produced.
list_media_assetsList the workspace's media library — the images and videos Tempio holds and has handed to the platforms — newest first.
list_media_plansList media plans newest-first with their item count and total budget.
list_phasesEvery phase on one campaign or plan item, earliest first.
list_playbooksList the playbooks Tempio keeps: researched per-platform operating notes, plus the house's own notes under the `tempio` platform.
list_structureList the ad groups, ads, creatives or keywords under a campaign or ad group, newest first.
list_swipeThis workspace's swipe file: every collection it has, and the saved ads themselves, newest first.
planner_key_dates_listThe key dates in a window: the marketable periods of the year for a region, and the dates this workspace has added of its own, earliest first.
platform_api_requestRead straight from a connected platform's own API on Meta, Google, TikTok, Reddit, X and Microsoft Advertising.
research_keywordsGoogle keyword, topic and interest research, for resolving what to target BEFORE drafting it.
scrape_brand_kitRead a public website and PROPOSE a brand kit from it — nothing is saved.
search_librarySearch the ad library — other people's live ads, captured from the public transparency archives of Meta, Google, LinkedIn and TikTok.
draftadd_library_advertiserAdd an advertiser to the shared library by website, by name, or by pasting a Meta, Google, LinkedIn or TikTok ad-library URL — at least one is required, and a pasted URL is the manual override where a domain resolves to nothing.
create_media_planCreate a media plan (free to edit — plans are working documents; only materializing creates gated campaign drafts).
delete_goalRemove one goal by id.
delete_phaseRemove one phase by id.
delete_plan_itemRemove an item from a plan.
draft_changesDraft one or more changes as ONE reviewable batch.
generate_creativesQueue a batch of ad stills from a preset over the workspace's brand kit.
materialize_planTurn every plan item into a create-campaign DRAFT at the approval gate — nothing goes live and nothing spends until each draft is approved at the gate — by a person, or by a rule the workspace wrote (a rule never approves a create, so these wait for a person).
plan_dependency_addLink two planner lanes: `successor` starts after `predecessor` ends.
plan_dependency_removeRemove the link between two planner lanes.
planner_colour_setGive one or many planner lanes a colour, or clear it.
planner_key_date_removeDelete one of this workspace's own key dates by its `id`, as planner_key_dates_list returns it in `custom`.
planner_key_date_upsertAdd a key date to this workspace's planner, or edit one: pass `id` to edit in place, omit it to add.
planner_order_setPlace one planner lane directly before or after another lane in the same account group.
platform_api_mutateDraft a raw write to a connected platform's own API on Meta, Google, TikTok, Reddit, X and Microsoft Advertising.
propose_reallocationRun the budget allocator over this workspace: every active campaign with an enabled envelope, scaled by the multiplier of the phase it is in today, funded in phase order under the workspace's daily cap and each account's own, clamped to the guardrail's maximum daily movement.
register_creative_mediaUse one composite of a creative asset in an ad on one ad account.
resize_creativeRender one more aspect ratio of an existing creative asset from the scene it already has — no model call and no generation spent, so it is free and takes seconds.
revert_draftUndo a change that already landed by drafting its inverse at the same gate — never an automatic rollback.
set_library_watchWatch an advertiser, stop watching it, or change its alert and digest settings — one call for all three, because it is one row.
update_brand_kitCreate or change the workspace's brand kit — owner or admin only; anyone else is refused with `forbidden`.
update_draftEdit the payload of a draft still waiting at the gate, or one that failed, instead of rejecting it and drafting again.
update_media_planRename a plan, rewrite its brief, move its window, or archive it.
update_swipeSave an ad to this workspace's swipe file, write or change its note, move it to another collection, or remove it.
upsert_goalPut a goal on one campaign or plan item, or edit one in place with `id`.
upsert_phaseAdd or edit a phase on one campaign or plan item: pass `id` to edit in place, omit it to add.
upsert_plan_itemAdd or update a planned campaign: platform, concept name, WHOLE-FLIGHT budget in minor units of the plan's currency, flight dates inside the plan window.
approveapprove_draftsAPPROVE a batch, or a set of loose drafts, as one act.
reject_draftsReject a batch, or a set of loose drafts, so none of them can ever be applied.

68 capabilities. On the explicit surface each is a tool of its own; on the compact surface each is a row of the matrix above, and both reach it under the scope named here.

get_account_brief follows the money rules per account: one brief is one account in one currency, so its money is never comparable with another brief's without converting, and a brief older than seven days is flagged stale — say when it was computed before quoting it.

Drafting a batch

draft_changes is the one write capability, reached as entity: "draft", action: "create". It takes a connection and a list of operations and writes them as a single reviewable batch of change requests. A campaign, the ad group inside it and the ad inside that are three entries in one call; dependsOn is the index of the earlier entry that creates this one's parent, so a child never carries an id that does not exist yet. A batch carries at most 50 entries, and a longer one is refused rather than truncated.

{
  "connectionId": "8f2a1c34-6d0b-4f19-9c73-2b5e7a1d0c48",
  "operations": [
    {
      "operation": "create_campaign",
      "targetLevel": "campaign",
      "payload": {
        "adAccountId": "1d9c4e57-3a20-4b6f-8e11-77c0d4a9b312",
        "name": "AU spring intake — traffic",
        "objective": "OUTCOME_TRAFFIC",
        "budget": { "mode": "daily", "amountMinor": 25000 },
        "specialAdCategories": []
      }
    },
    {
      "operation": "create_ad_group",
      "targetLevel": "ad_group",
      "dependsOn": 0,
      "payload": {
        "name": "AU 25-54 broad",
        "optimizationGoal": "LINK_CLICKS",
        "billingEvent": "IMPRESSIONS",
        "bidStrategy": "lowest_cost",
        "targeting": {
          "geo": { "include": [{ "type": "country", "code": "AU" }] },
          "age": { "min": 25, "max": 54 }
        }
      }
    },
    {
      "operation": "create_ad",
      "targetLevel": "ad",
      "dependsOn": 1,
      "payload": {
        "name": "Spring intake — single image",
        "adType": "SINGLE_IMAGE",
        "identity": { "pageId": "102938475610293" },
        "creative": {
          "primaryTexts": ["Every ad account, one room."],
          "headlines": ["Spring intake is open"],
          "finalUrls": ["https://example.com/spring"],
          "media": [
            {
              "mediaAssetId": "b41f8e02-95c7-4a3d-8f60-1e2d5c9a7b04",
              "role": "image",
              "format": "square"
            }
          ]
        }
      }
    }
  ]
}

Money in that call is minor units: 25000 is 250.00 in the ad account's own currency. Which currency that is, is not yours to state — the server stamps it onto every payload from the target account and overwrites whatever arrives in a currency field. Every create lands paused on the platform, so approving this batch builds the structure without spending; going live is a second update_status change through the same gate.

The result is the batch, its deep-link, and who cleared it:

{
  "correlationId": "3c7d5a90-1b48-4e62-a0f5-9d6c2e18b473",
  "reviewUrl": "https://app.tempio.ai/drafts?batch=3c7d5a90-1b48-4e62-a0f5-9d6c2e18b473",
  "approvedBy": "pending",
  "drafts": [
    {
      "id": "5a0e7c21-98d4-4b3f-8607-c1f2a9b40d56",
      "operation": "create_campaign",
      "targetLevel": "campaign",
      "targetEntityId": null,
      "status": "pending_approval",
      "correlationId": "3c7d5a90-1b48-4e62-a0f5-9d6c2e18b473",
      "batchSeq": 0,
      "approverKind": null,
      "validation": null,
      "warnings": []
    }
  ]
}

reviewUrl is the gate for the whole group and never one row of it: give it to the person you drafted for. It is absolute, so it opens from wherever your agent is running. approvedBy is "pending" when the batch is waiting for a person and "policy" when a rule the workspace wrote cleared every row of it on the way in — those are the two authorities, and the field says which one this batch got. batchSeq is the row's place in the run, counting from zero, and approverKind fills in with human, mcp_key or policy once something has cleared the row.

A draft that needs a change does not have to be rejected and drafted again: update_draft replaces a waiting or failed row's payload whole, which re-validates it, re-stamps the currency, re-runs the guardrails, and puts a failed row back to waiting, which is how a stopped batch is resumed. revert_draft undoes a change that already landed by drafting its exact inverse at the same gate, so an undo is a change request like any other rather than a rollback behind your back.

approve_drafts and reject_drafts each take either a correlationId — the whole batch — or a draftIds list of loose rows, and move them whole or not at all. Approving claims the rows and the apply runs on its own moments later, so read the outcome back with list_drafts; approving a correlation again continues a stopped batch from the rows that already landed, rather than starting it over.

Reading structure

list_structure answers what sits under a campaign: ad_group, ad, creative or keyword, newest first, with a parentId to scope it to one campaign or one ad group. Each row carries its Tempio id — the id a draft points at — the platform's own externalId, name, status, money in minor units of the row's own currency where the level holds any, and writable.operations: the operations of that level intersected with what the connected platform can express. An operation missing from that list cannot be drafted at all, and an empty list means the connection has no adapter registered. writable is a capability answer and not a permission: every change it allows is still a change request at the gate.

list_media_assets lists the images and videos the workspace holds, each with the ad account it belongs to, its kind, its fileName, its dimensions, and the platform side of the record — platformExternalId (a Meta image hash, a TikTok video id, a Google asset resource name) and platformStatus. Reference an asset from a creative by its platformExternalId, the id the platform holds those bytes under, rather than uploading the same file twice. The media library covers how a file gets there.

list_connections carries the same kind of answer one level up. Its capabilities object holds operations (the change operations the platform can express), budgetLevels (where money attaches — Google budgets are campaign-level, so a Google ad group has no budget to change), objectives (the accepted values, or the string "unrestricted"), deleteSemantics, and supportsValidate (whether a draft can be dry-run against the platform before it is applied). deleteSemantics: "remove" — Google and TikTok — means a removal is irreversible: the entity is gone, not archived, and nothing here brings it back. "archive_only" is Meta, whose archive is update_status with status archived, and that is undoable.

Renamed tools

The write surface was five narrow tools over four operations; it is draft_changes over every operation it drafts (the raw platform request is its own entity). The old names are deleted rather than aliased, because no external consumer held them.

  • create_campaign_draft is draft_changes with one create_campaign entry.
  • update_campaign_budget_draft is draft_changes with one update_budget entry, at whichever level holds the budget.
  • update_campaign_status_draft is draft_changes with one update_status entry.
  • approve_draft is approve_drafts, which takes a correlationId or a list of ids.
  • reject_draft is reject_drafts, on the same selection.

Client setup

Claude Code

claude mcp add --transport http tempio https://app.tempio.ai/api/mcp --header "Authorization: Bearer YOUR_KEY"

Cursor (mcp.json)

{
  "mcpServers": {
    "tempio": {
      "url": "https://app.tempio.ai/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_KEY" }
    }
  }
}

Any MCP client that can send an Authorization header over streamable HTTP works the same way. A client that cannot send a header of its own signs in through OAuth instead: OAuth for MCP clients.

The draft model

Every write from every client is a change request. A draft records who asked for it (a person, the in-app agent, or an MCP credential), what it changes, the state the entity was in before it, and the guardrail checks it passed. Applying it is a separate act under the approve scope: the guardrails run again, the change goes through the platform adapter, Tempio verifies it landed, and the audit log records it. The authority that cleared it is on the row either way — you, a credential you minted, or a rule your workspace wrote.