---
title: "Propose actions"
method: POST
path: "/agent/workspaces/{workspace}/actions"
tags: ["ActionProposalApi"]
---

# Propose actions

`POST /agent/workspaces/{workspace}/actions`

## Request

- Base URL: `https://plainrouter.com/api/v1`
- URL: `https://plainrouter.com/api/v1/agent/workspaces/{workspace}/actions`
- Auth: HTTP bearer

Submit or replay a governed proposal for an account available to this key. A kill switch saves a blocked proposal without executing provider writes.

## Path parameters

- `workspace` integer, required

## Request body

- ActionProposalInput
  - `limit` integer: Maximum complete result actions; the shared JSON byte budget can return fewer.
  - `cursor` integer: Continue using next_cursor. Read saved proposal actions with get_action_batch to avoid another write; dry runs repeat the same inputs.
  - `actions` union[], required
    - union
      - object
        - `type` 'increase_budget', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
          - `new_daily_budget_minor` integer, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'decrease_budget', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
          - `new_daily_budget_minor` integer, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'pause', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'resume', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'replace_creative', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'shift_spend', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'rollback', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'create_campaign', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'create_ad_set', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'create_ad', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'upload_asset', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
          - `staged_asset_id` string, required
          - `asset_type` 'image', required
          - `filename` string, required
          - `content_sha256` string, required
          - `mime_type` string, nullable
          - `size_bytes` integer, nullable
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'adjust_budget', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
          - `new_daily_budget_minor` integer, required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'set_status', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
          - `status` 'active' | 'paused', required
        - `rationale` string, required
        - `idempotency_key` string
      - object
        - `type` 'duplicate_ad_with_creative', required
        - `target_entity` object, required
          - `type` 'ad_account' | 'campaign' | 'ad_set' | 'ad', required
          - `id` string, required
          - `name` string, nullable
        - `params` object, required
          - `asset_id` string, required
          - `status` 'paused', required
          - `name_suffix` string, nullable
        - `rationale` string, required
        - `idempotency_key` string
  - `rationale` string, required
  - `idempotency_key` string, required
  - `evidence` object[], required
    - `source_tool` 'get_account_state' | 'get_signal_health' | 'get_performance' | 'get_inventory_metrics' | 'get-creative-library' | 'staged-asset-manifest', required
    - `fields_used` string[], required
    - `action_index` integer
  - `target_source` 'human_supplied', required
  - `account_id` integer
  - `workspace_id` integer

## Response `200`

Saved idempotent proposal. Result projections share the 80,000-byte MCP JSON budget; defaults are bounded, next_cursor continues complete rows, and oversized single rows are explicitly refused. Plan creative continuation uses the read endpoint; proposal continuation uses the batch read endpoint.

- ActionProposalRead
  - `proposal` object, required: Complete projection rows are paged within 80,000 JSON bytes, accounting for both MCP representations. Defaults are bounded. Follow next_cursor with the same scope and filters. One oversized row is explicitly refused; fields are never silently clipped.
    - `id` string, required
    - `scope` unknown, required
    - `evidence_provenance` unknown, required
    - `status` 'pending' | 'approved' | 'approved_without_execution' | 'auto_approved' | 'rejected' | 'executing' | 'executed_pending_verification' | 'compensating' | 'measuring' | 'completed' | 'failed' | 'rolled_back' | 'rollback_incomplete' | 'blocked' | 'halted', required
    - `policy_decision` 'allow' | 'require_approval' | 'block', required
    - `policy_reasons` string[], required
    - `approval_required` boolean, required
    - `inbox_url` string, required
    - `approval_queue_url` string, required
    - `rationale` string, required
    - `proposed_by` object, required
      - `type` string, required
      - `id` string, required
      - `name` string, nullable, required
    - `actions` object[], required
      - `id` string, required
      - `type` 'increase_budget' | 'decrease_budget' | 'pause' | 'resume' | 'replace_creative' | 'shift_spend' | 'rollback' | 'create_campaign' | 'create_ad_set' | 'create_ad' | 'upload_asset' | 'adjust_budget' | 'set_status' | 'duplicate_ad_with_creative' | 'duplicate_adset' | 'duplicate_ad_with_creative_v2', required
      - `target_entity` object, required
        - `type` string, required
        - `id` string, required
        - `name` string, nullable, required
      - `params` union, required
        - object
        - unknown[]
          - unknown
      - `rationale` string, required
      - `proposed_by` object, required
        - `type` string, required
        - `id` string, required
        - `name` string, nullable, required
      - `policy_decision` 'allow' | 'require_approval' | 'block', required
      - `policy_reasons` string[], required
      - `policy_evidence` unknown, required
    - `next_cursor` integer, nullable
    - `truncated` boolean
    - `continuation` string
    - `total` integer

## Other responses

- `201`: New proposal created. Result projections share the 80,000-byte MCP JSON budget; defaults are bounded, next_cursor continues complete rows, and oversized single rows are explicitly refused. Plan creative continuation uses the read endpoint; proposal continuation uses the batch read endpoint.
- `401`: Missing, invalid, revoked or expired credentials return error invalid_token and error_description. Missing principal permissions or an issuer whose current role no longer covers the key return error insufficient_scope and error_description. A management key returns message instead.
- `403`: A revoked or insufficient workspace grant returns error insufficient_scope and error_description. Missing creative.write permission returns message without a code.
- `404`: A missing or inaccessible workspace or plan, or a disabled Plan surface, returns error.code api_route_not_found with error.message, error.resolution, request and resources.
- `409`: A replay whose parameters or proposing principal differ returns message and errors.idempotency_key containing idempotency_key_parameters_mismatch: This submission key belongs to a different proposal.
- `413`: Oversized POST data returns message "The POST data is too large." before route authorization.
- `422`: Invalid input or account ID, mismatched account binding, ambiguous active accounts, evidence, parameters, targets, repeated management targets, missing staged assets or source ads, and invalid budget direction return message plus errors mapping field names to arrays of messages. Stable reason codes are prefixes of field messages, not an error.code property.
- `429`: The shared API rate limit is exceeded; message is "Too Many Attempts." Retry-After gives seconds until a retry is allowed.
- `500`: An unexpected server or provider-read fault returns message "Server Error" when debug is disabled.
- `503`: The proposal could not be recorded: error.code proposal_not_recorded and error.message "The proposal could not be recorded. Retry." No proposal is committed. The bounded workspace transaction timed out: error.code workspace_lock_timeout and error.message "The workspace is busy. Retry the operation shortly." Retry-After is 1 second.

---

[API](https://skmtc.dev/plainrouter/apis/plainrouter-conversion-api.md) · [All operations](https://skmtc.dev/plainrouter/apis/plainrouter-conversion-api/llms.txt) · [OpenAPI document](https://skmtc.dev/plainrouter/apis/plainrouter-conversion-api/revisions/d071d4f4f05c?raw)
