Tempio Docs
Connections

Raw platform requests

Tempio models the entities every platform has in common — campaigns, ad groups, ads, creatives, keywords, media, account defaults — and normalises them so one loop works everywhere. Platforms are larger than that model. Meta adds a field, Google exposes a resource Tempio never mapped, and the thing you need is one endpoint away from something we already read.

The escape hatch is that endpoint. It reaches the platform's own API on a connection you have already made, through the platform's own client, and it is off until a workspace owner turns it on.

It covers six platforms: Meta, Google, TikTok, Reddit, X and Microsoft Advertising. On Meta, Google and TikTok it reaches past what Tempio models. On Reddit, X and Microsoft Advertising it is how you work with the account at all, because those connections are connect only.

What it is for

Reads first, because reads are most of it: a Meta field added last month, a Google resource with no mirror, a TikTok report shape Tempio does not build. A read through the hatch executes immediately and hands back the platform's own response body.

Writes are the other half, and they go through the same gate as every other change Tempio makes: a raw write is a change request before it is a change.

Connect-only connections

A Reddit, X or Microsoft Advertising connection is connect only. Tempio holds the connection, keeps its token in the vault and lists its ad accounts, and reads no campaigns and no daily numbers from it. list_connections shows it with capabilities.coverage: "connect_only" and an empty operations list, and tempio_help marks it the same way. get_account_brief answers each of its ad accounts with "Tempio reads no daily numbers from Reddit." (or X, or Microsoft Advertising) in place of figures. Nothing Tempio models can be drafted on it: no budget change, no status change, no new campaign.

The escape hatch is what does work on it. Read with platform_request.get, which executes the read and hands back the platform's own answer. Change something with platform_request.mutate and a sentence saying what the request changes and why; the change waits at the gate for a person. The switches, refusals, limits and audit rows on this page apply to a connect-only connection exactly as they apply to a Meta one.

Every tool result that carries workspace warnings, a raw read included, carries connect_only_connection for each connect-only connection, in place of the operation_unsupported a connection with an empty operations list would otherwise get:

CodeClassSeverityWhat it says
connect_only_connectionwrite_gate_closedinfoThis connection lists ad accounts and carries raw requests only. Tempio reads no campaigns or daily numbers from it; use platform_request to read or change anything on it.

Turning it on

Settings → Raw platform requests (/settings/escape-hatch). Two switches, and both start off:

SwitchWhat it allows
Raw readsReads. An agent holding a key for this workspace can call a read endpoint on any connected platform.
Raw writesMutations. A write endpoint may be drafted; nothing is sent until the draft is approved.

Turning raw reads on, or either switch off, takes effect on the press. Turning raw writes on asks first. The box, Turn on raw writes?, says an agent may then draft writes to a platform's own API and that each one waits for a person to approve it, and the switch stays where it was until you press Turn on.

Owner only. An admin can write approval rules, mint keys and approve drafts, and cannot touch this one — it is the single setting that reaches past Tempio's own model, so it belongs to the workspace rather than to its administration. An admin sees the switches disabled with the reason in a row under them.

Turning raw reads off turns raw writes off with them, and the raw writes switch stays disabled until reads are back on. "Reads off, writes on" is not a state anyone means.

How a request is written

Both halves take the same five things: the connection, a method, the platform's own path, an optional query and body, and a description.

{
  "entity": "platform_request",
  "action": "get",
  "params": {
    "connectionId": "…",
    "method": "GET",
    "path": "/v26.0/act_123456/adsets",
    "query": { "fields": "name,optimization_goal" }
  }
}

The path is the platform's own path and nothing else. It starts with /, names no host and no scheme, and cannot climb above the platform's API base. The base comes from the same constant the adapter's client uses, so it is never an argument and can never be pointed somewhere else. A path that resolves off the platform's host is refused before anything is sent.

Each platform's paths look like these. The Reddit, X and Microsoft rows show the shape of that platform's paths, not a promise that the endpoint exists; the platform's own API reference is the list of what does.

PlatformPath
Meta/v26.0/act_123456/adsets
Google/customers/1234567890/googleAds:search
TikTok/campaign/get/
Reddit/ad_accounts/{id}/campaigns
X/accounts/{id}/campaigns
Microsoft Advertising/CampaignManagement/v13/Campaigns/QueryByAccountId

Microsoft Advertising runs one host per service, so its path starts with the service: /CampaignManagement/, /CustomerManagement/, /Reporting/, /AdInsight/ or /Bulk/. The service picks the host, and a path under none of the five is refused. The five services, their hosts and the read rule below rest on the spec's "Microsoft reads" and "Microsoft REST API" vendor-fact rows until the local session checks them on a real Microsoft Advertising account.

Query parameters are strings, exactly as they go on the wire. A parameter that carries a credential — access_token, developer_token, client_secret — is refused outright: your platform token travels in a header, never in a URL.

The same goes for the credentials and signing parameters the client adds itself, in the query and the body alike: any key starting with oauth_, and developertoken, authenticationtoken and client_id. Keys are matched exactly and without regard to case, so pageToken and page_token, the platforms' own paging cursors, pass.

customerid and customeraccountid are refused in the query only. The client sets both as headers from the connection. A body may still name a customer or an account, because some platforms' read operations take CustomerId in the body, and the path and the connection's grant decide what a request reaches either way.

Two further refusals apply to the query and the body alike. A key that overrides the HTTP method (method, _method, X-HTTP-Method-Override) is refused everywhere, because the method and the endpoint are what decide read or change and a request that rewrites its own method would decide for itself. And a GET carries no body: send the fields as query parameters, or send the method that carries one.

Read or write is decided by the endpoint

Not by the HTTP method. Google's two reads (googleAds:search and searchStream) are POSTs, and Microsoft Advertising reads with POSTs too, so a classifier that trusted the verb would stage a read as a draft and hand you nothing back. Each platform has its own table of read endpoints:

PlatformReads
MetaGET on a Graph node or edge under a version segment — /v26.0/act_123/insights
GooglegoogleAds:search, googleAds:searchStream and the :generate* planning methods, and GET on any resource
TikToka path whose LAST segment is get or list — /campaign/get/, /advertiser/list/ — plus the two asynchronous report reads, /report/task/check/ and /report/task/download/
Redditany GET
Xany GET. The analytics job endpoints are POSTs that create a job, so they are writes, as TikTok's /report/task/create/ is
Microsoft Advertisinga POST or GET whose last path segment starts with Get, Query or Search, such as /CampaignManagement/v13/Campaigns/QueryByAccountId. A DELETE or PUT to one of those paths is a write

The Reddit, X and Microsoft Advertising rows carry the same caveat as their paths above: they describe the shape of each platform's API, not a checked list of its endpoints.

Anything the table does not match is treated as a write. That is the safe direction to be wrong in: a misclassified read costs you an approval, and a misclassified write would be an unreviewed change on a live ad account.

Ask for a write endpoint through platform_request.get and it is refused, by name, and told to use platform_request.mutate.

Size and time

Every raw request, on all six platforms, is held to the same limits:

LimitValueWhat happens past it
Path2,000 charactersRefused as invalid_payload
Query50 parameters, and the whole URL at most 8 KiBRefused as invalid_payload, naming the limit
Request body256 KiB, measured as the JSON that is sentRefused before anything is sent, and a write with a larger body is refused when it is drafted: "The body is 300 KiB; a raw request carries at most 256 KiB."
A read's answer1 MiBThe body is replaced by { "truncated": true, "responseBytes": … } with the escape_hatch_response_truncated warning
A write's stored response64 KiBThe stored response is replaced by its size, as below
Time30 seconds per callA read fails as network; an applied write fails and is not sent again

A body of exactly 1 MiB comes back whole. When an answer is too large, ask for fewer fields or a smaller page.

The 30 seconds cover the whole call: a platform that sends its headers and then stalls on the body is stopped at the same deadline as one that never answers.

Redirects are never followed

A raw request carries your platform credential in a header, and a redirect names an address you did not choose. So the hatch never follows one. A 3xx answer is refused; on Meta the sentence is "Meta answered with a redirect to another address. A raw request never follows one, because it would carry your credential there." Nothing is sent to the address the redirect named.

Credentials are removed from every answer

Platforms echo what they were sent. A debug endpoint can repeat the Authorization header, an OAuth endpoint answers with tokens, and an error message can quote the credential it rejected. Before a raw answer leaves Tempio, the hatch removes:

  • the value of any key named access_token, refresh_token, id_token, token, client_secret, developer_token, developertoken, oauth_token, oauth_token_secret, authorization, password, api_key or apikey, matched exactly and without regard to case;
  • inside every string, the connection's own tokens and Tempio's app secrets for that platform, wherever they appear;
  • anything shaped like Bearer followed by twenty or more token characters.

Each is replaced by [redacted]. A body that is not JSON is treated as one string. Paging cursors are not on the list and come through untouched.

This happens inside the escape hatch before anything else sees the answer: a read's body, a write's response before it is stored on the draft, and the platform's own words in an error. An agent's result, the stored response, a failed draft's reason and the sentence you are refused with all carry the redacted text. When anything was removed, the answer carries escape_hatch_redacted, and the audit row records how many values were replaced.

What a raw request can reach

A raw request carries the connection's grant, so it reaches whatever that grant can reach on the platform, not only the account the connection names. When one consent picked two accounts, one grant backs both connections, and a raw read on the first can name the second account in its path. The same holds on every platform, Meta included.

Restricted connections decide who may use a connection. They do not narrow what its grant reaches. Tempio does not pin a raw request to the connection's account: that would mean recognising an account id in every path shape each platform uses (/ad_accounts/, /accounts/, a CustomerAccountId in a Microsoft body), and a pin that missed one shape would read as a guarantee it is not. Microsoft Advertising's client sets the CustomerAccountId header from the connection, which scopes Microsoft's account-level services; it is not a pin either. That header rests on the spec's "Microsoft headers" row until the same check.

To keep a person away from one of two accounts, connect the two under separate logins. Each connection's grant then reaches its own account, and restricting that person from one connection keeps them off its account.

The raw budget

Each connection may send 30 raw requests a minute, reads and applied writes together. The budget is taken before anything is sent, so running out costs the platform nothing.

  • A read past the budget is refused as rate_limited, and the refusal says how many seconds to wait. Another connection in the same workspace has its own budget and still reads.
  • An approved write takes its call from the budget before the draft is marked submitted. When the budget is empty, the apply waits for the minute to turn and runs again; this is the one failure of a raw write that is retried, because nothing was sent. If the budget is still empty after every retry, the draft fails with "Nothing was sent: this connection's raw request budget stayed empty through every retry." A write whose apply stopped for any other reason before it was sent fails with "Nothing was sent: the change never reached the step that sends it."

The platform's own rate limits still apply on top. A platform that answers with its own limit is reported as rate_limited.

What a read's answer carries

A read comes back in the same envelope as every other tool result, with the platform's body as data and these warnings:

CodeSeverityWhenWhat it says
escape_hatch_unmodelledinfoEvery raw readThis is the platform's own answer. Tempio has not normalised it: money is in the platform's units rather than minor units, ratios are the platform's own, and none of it is in Tempio's reports.
escape_hatch_response_truncateddegradedThe answer was larger than 1 MiBThe platform's answer was larger than 1 MiB and is not included. Ask for fewer fields or a smaller page.
escape_hatch_redactedinfoSomething was removed from the answer, or from a write's stored responseValues shaped like credentials were removed from the platform's answer before it reached you.

An applied write carries escape_hatch_unverified, below, and escape_hatch_redacted when its stored response had anything removed.

Why a write becomes a change request

Because it is a change, and every change Tempio makes is a change request first. A raw write lands in the same queue, at the same page, with the same audit trail as a budget change.

Two things are true of it that are not true of anything else in that queue:

  • The description is mandatory, and has to read as a sentence. A reviewer opening the draft sees your words and the request line; POST /v26.0/act_x/adsets describes nothing, so it is refused as a description.
  • No rule can approve it. An approval rule is written against a shape — an operation, a cap, a direction — and a raw request has none of those. It waits for a person, always, whatever rules your workspace has written.

Everything else about the gate holds. An approval chain that names platform_api_request, or names no operation, holds a raw change like any other, and with separation of duties on, the person who drafted it cannot approve it.

What Tempio does not verify

Every other change Tempio applies is read back from the platform afterwards and checked against what was asked for. A raw request cannot be: there is no canonical row to read back and no marker to stamp, because Tempio does not model the endpoint.

So an applied raw write carries a warning that says exactly that — the platform's own response is the only record of what happened — and the same sentence is shown at the gate before you approve, not after. The platform's response is stored on the draft, so you can read what it said; a response over 64 KiB is replaced by its own size, because a change request is a row somebody opens in a browser and not a copy of your account.

Once the write lands, the connection re-syncs. A raw write can change an entity Tempio mirrors, and a slow mirror is better than a silently wrong one. On a connect-only connection the re-sync is the accounts step, so a raw change that renamed or closed an account shows in the account list.

What is on the record

Every call — read, write, and every refusal — writes an audit row carrying who made it, which credential kind they used, the connection, the method, the host, the path, the description, the platform's own request id, whether it succeeded, how large the response was, and how many credential-shaped values were removed from it.

A refused call is on the record too: the row carries the method, the path as you wrote it, and the sentence you were refused with, so a workspace can read what an agent tried as well as what it did.

The response body is never written to the audit log. It can carry customer data, and an audit log is not a data store. The size is; the bytes are not.

What it cannot do

  • Reach a host that is not the platform's.
  • Run on a connection outside your workspace.
  • Run on a connection whose kill switch is closed — a paused or disconnected connection refuses a raw request exactly as it refuses an apply.
  • Run on a platform this deployment has not enabled.
  • Return a token, a grant, or a header Tempio sent. You get the platform's body, with any credential it echoed removed, and nothing else.
  • Follow a redirect, or run longer than 30 seconds.
  • Skip the gate for a write.
  • Approvals — the rules that can clear a change, the chains that hold one, and why neither clears this one.
  • MCP reference — the tools, the scopes and the keys. A raw read needs a read key; drafting a raw write needs a draft key.