Approval model and safety controls
What Synter checks before a write reaches an ad platform, which writes wait for a person, and who that person is on each surface. A write here means anything that can start, stop, or change ad spend.
Every write passes the safety gate
Every script run, from the Synter app, REST, an SDK, the CLI, or MCP, goes through the same server-side safety gate. Each decision (allowed, changed, held, or blocked) is written to the workspace audit log. The gate does three things:
- Blocks writes the workspace policy forbids: tools on the deny list or off the allow list, calls outside allowed operating hours, changes over the configured monetary limits, and budgets above the daily-budget ceiling. Nobody can approve past a block.
- Creates paused. New campaigns and ads are created paused while "Enforce paused on create" is on (the default). Turning them on is a separate write.
- Holds higher-risk writes for approval, depending on the workspace autonomy level and the surface (table below). A held write returns
pending_reviewwith anaudit_idand the exact reviewed arguments. Nothing runs until it is approved.
Workspace owners and admins set the autonomy level in the Synter app under Settings → Autonomous: Off (live changes blocked), Suggest (the default), Auto (safe), or Auto (full). Under Off and Suggest, these writes are held: enabling or resuming something, deleting or removing something, creating campaigns, sending ad creative to a platform, and unrecognized write scripts. Routine updates, including pausing and changing a budget, bid, or keyword, run immediately and are audited. Auto (full) skips the hold but never a block.
Who approves what, by surface
| Surface | Pause, budget, bid changes | Held writes (default Suggest policy) | Who approves a held write |
|---|---|---|---|
| Synter app agent (campaign conversation) | Run immediately, audited | Shown in the conversation as the exact reviewed action | The person who asked, by replying I APPROVE in that conversation. An active owner or admin of the workspace can approve a teammate's held action with their own reply. |
| REST, SDKs, CLI, local stdio MCP (API key) | Run immediately, audited | Returned as HTTP 202: status: "pending_review" on /api/v1/tools/run, error: "APPROVAL_REQUIRED" on api.synterai.com/v1 | Not the API key. Run the action again from a campaign conversation in the Synter app, check the reviewed action, and reply I APPROVE. |
| Hosted MCP (OAuth or X-Synter-Key) | Run immediately, audited | Production: held, same as REST. Staging: accepted automatically (see rollout note) | Production: same as REST. Staging: the authenticated MCP user is treated as having approved the call; nobody else is asked. |
| Background autonomous agents | Governed by the approval mode in Settings → Autonomous. The default, "Ask before acting", holds changes to your ad accounts for approval; read-only work and low-risk housekeeping run on their own. Draft, Auto, and Fully autonomous loosen this step by step; workspace limits and blocks still apply. | ||
Rollout note (as of October 7, 2026)
approval_basis: "mcp_auto_acceptance". New campaigns are still created paused, and blocks still apply. The same change lets plan execution through an API key or MCP approve a draft or in-review plan automatically (next section). It is not in production yet; in production today, hosted MCP writes are held exactly like REST writes. REST, the SDKs, the CLI, and the Synter app are unchanged.Your MCP client's confirmation
Synter marks every write tool with the MCP destructiveHint annotation. Spend-changing tools such as execute_campaign_plan and update_campaign_budget also tell the agent to confirm the account, amount, and campaign with you before calling. Whether you see a prompt depends on your client and its settings: Claude Code, Cursor, and others can ask before each tool call or be set to auto-approve. That prompt runs on your machine; Synter does not enforce it. Where hosted MCP accepts held writes automatically (staging, above), your client's prompt is the only per-call confirmation. Keep it on for write tools, or set the workspace autonomy level to Off for a read-only workspace.
Campaign launches: plan → approve → execute
create_campaign_planbuilds the launch plan. Pass aplan_keyfor idempotency; the response carries theexecute_token.run_launch_preflightchecks conversion tracking and the landing page at plan time (read-only).get_launch_gate_policyreads whether a failing Plan QA review blocks launch in this workspace.publish_plan_documentmoves the plan to review. A person approves it from the share link, or the agent callsapprove_campaign_planafter you have seen the plan. That call requires theexpected_versionyou saw, so a plan edited in between is not approved unseen. It is recorded under the account that owns the key or OAuth session.execute_campaign_planactivates the entities in dependency order. If a step fails, entities it already activated are paused (best effort).get_plan_executionreports per-entity results.
In production, execution refuses a plan that is not approved, and qa_override_approved defaults to false. On staging (rollout note above), executing through an API key or MCP approves a draft or in-review plan automatically, with a review row recorded as "MCP Operator", and qa_override_approved defaults to true. Launching from the Synter app still requires an approved plan.
Pausing
Pausing is a routine update. It runs without an approval step on every surface under the default policy and is audited. Through REST, POST /v1/campaigns/{id}/pause pauses child ad sets or ad groups too on Meta, Microsoft, Reddit, TikTok, and X. Turning a campaign back on is an enable, which is held under Off and Suggest. A campaign guardrail (below) can pause a Google Ads campaign for you.
Ongoing controls
- Campaign guardrails (Google Ads only):
set_campaign_guardrailarms an automatic pause for one campaign once it passes a minimum age and spend threshold with zero conversions on a verified conversion action. Setting it up counts as standing approval for that pause.list_campaign_guardrailsshows state;disable_campaign_guardrailturns the rule off without changing the campaign. - Spend alerts:
set_spend_alertnotifies by email, Slack, SMS, or WhatsApp when weekly spend passes a threshold. It alerts; it does not pause anything.
After a change: verify
get_ad_readbackreads a launched ad back from the platform API and checks the CTA and copy. Supported for Meta, Reddit, LinkedIn, and Google Ads; other platforms return a labeled unsupported result.get_account_changelogreturns the Google Ads change history (the platform API caps it at 30 days). Other platforms reportchangelog_supported: false.get_spend_reconciliationcompares Google Ads and The Trade Desk totals for up to 92 days (read-only).
Pending writes over REST
On https://api.synterai.com/v1, a held or blocked write returns HTTP 202. safety_gate_action tells you which: pending is waiting for approval; block was refused by policy. Credits are only deducted when a write succeeds.
{
"success": false,
"error": "APPROVAL_REQUIRED",
"safety_gate_action": "pending",
"audit_id": "3f0c…",
"reviewed_args": ["--campaign-id", "1234567890", "--action", "enable"]
}GET /v1/tools/pendinglists held writes for the key's workspace.POST /v1/tools/approve/{audit_id}andPOST /v1/tools/reject/{audit_id}(aliasDELETE /v1/tools/pending/{audit_id}) need the caller to be a workspace owner or admin and a review saved by the Synter app when that person repliedI APPROVE. With an API key alone they return 409review_required_or_changed(403 for non-admins). Approve in the Synter app instead.
Related: scopes (a key without a write scope cannot write at all), Pause Campaign, Update Budget, errors.
