---
title: "Preview actions"
method: POST
path: "/agent/workspaces/{workspace}/actions/dry-run"
tags: ["ActionDryRunApi"]
---

# Preview actions

`POST /agent/workspaces/{workspace}/actions/dry-run`

## Request

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

Preview policy decisions and execution diffs for the approved account without saving a proposal. A kill switch returns blocked policy decisions.

## 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`

Evaluated dry-run preview. 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.

- ActionDryRunRead
  - `dry_run` 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.
    - `actions` union[], required
      - union
        - object
          - `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
          - `status` 'evaluated', required
          - `policy_decision` 'allow' | 'require_approval' | 'block', required
          - `policy_reasons` string[], required
          - `approval_required` boolean, required
          - `would_auto_execute` boolean, required
          - `diff` object, required
            - `before` unknown, required
            - `proposed` unknown, required
        - object
          - `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
          - `status` 'dry_run_unavailable', required
          - `reason` string, required
    - `next_cursor` integer, nullable
    - `truncated` boolean
    - `continuation` string
    - `total` integer

## Other responses

- `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.
- `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.

---

[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)
