openapi: 3.2.0 info: title: Plainrouter Conversion Action Rule API version: 0.5.0 description: Plainrouter public Signals Conversion API. servers: - url: https://plainrouter.com/api/v1 description: Production security: - workspaceSecret: [] tags: - name: ActionRuleApi paths: /agent/workspaces/{workspace}/action-rules: get: operationId: actions.rules.index description: Read rule summaries, current revision and last-run status without histories or decisions. Continue with cursor; use actions.rules.show for detail. summary: List rule summaries tags: - ActionRuleApi parameters: - name: workspace in: path required: true description: The workspace ID schema: type: integer - name: account_id in: query description: Optional integer account ID. JSON booleans return 422 with message plus field errors. A valid supplied ID is ignored by an account-bound key. An unbound key requires exactly one active available advertising account with an active connection; omission uses that account and a supplied ID must match it. Multiple active accounts return 422 even when account_id is supplied. schema: type: integer minimum: 1 - name: platform_ad_account_id in: query description: Optional integer account ID. JSON booleans return 422 with message plus field errors. A valid supplied ID is ignored by an account-bound key. An unbound key requires exactly one active available advertising account with an active connection; omission uses that account and a supplied ID must match it. Multiple active accounts return 422 even when account_id is supplied. Alias of account_id; account_id takes precedence when both are supplied. schema: type: integer minimum: 1 - name: cursor in: query description: Continue using next_cursor with the same filters; rule lists use an immutable rule ID, other lists an offset or object ID. schema: type: string - name: per_page in: query description: Maximum complete rows; the byte budget may return fewer. Defaults to 25. schema: type: integer default: 25 minimum: 1 maximum: 100 - name: page in: query description: Integer page from 1 through 10000000000000000; integer strings are accepted. Zero, negatives, fractions, non-numeric strings and booleans return 422 with a page field validation error. schema: type: integer minimum: 1 maximum: 10000000000000000 responses: '401': description: Missing, invalid, expired or revoked workspace key. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' '403': description: Insufficient ability or grant, or a conflicting workspace selector. content: application/json: schema: anyOf: - $ref: '#/components/schemas/ErrorMessage' - type: object properties: error: type: string enum: - insufficient_scope error_description: type: string required: - error - error_description '404': description: Workspace, account or rule unavailable in this scope. content: application/json: schema: $ref: '#/components/schemas/ApiRouteNotFound' '422': description: Invalid rule input or account ID, mismatched account binding, or ambiguous active accounts. content: application/json: schema: $ref: '#/components/schemas/ValidationError' '429': description: The shared API rate limit is exceeded. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' headers: Retry-After: description: Seconds until a retry is allowed. schema: type: integer '200': description: Bounded rows; no nested history is included in rule summaries. 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. content: application/json: schema: type: object properties: rules: type: object description: 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. properties: data: type: array items: type: object properties: id: type: string name: type: string status: type: string enum: - enabled - disabled - suspended account: type: object properties: id: type: integer name: type: - string - 'null' current_revision: type: object additionalProperties: {} last_run_status: type: - string - 'null' last_run_at: type: - string - 'null' counts: type: object properties: revisions: type: integer runs: type: integer required: - id - name - status - account - current_revision - last_run_status - last_run_at - counts total: type: integer per_page: type: integer next_cursor: type: - string - 'null' truncated: type: boolean continuation: type: string required: - rules security: - ruleReader: [] post: operationId: actions.rules.store description: 'Write an account-scoped rule using its validated definition. Send Accept: application/json for JSON errors. New rules start disabled.' summary: Create an action rule tags: - ActionRuleApi parameters: - name: workspace in: path required: true description: The workspace ID schema: type: integer requestBody: required: true content: application/json: schema: type: object description: Creates a disabled rule. enabled must be absent. window, window_days, author_type and current_revision_id must be absent or empty. properties: name: type: string pattern: \S maxLength: 255 definition: anyOf: - type: object description: Only target_level, action, budget_change_percent, minimum_counted_arrivals, cost_comparison and cost_ratio are accepted. properties: target_level: type: string enum: - ad - ad_set action: type: string enum: - pause minimum_counted_arrivals: type: integer minimum: 50 maximum: 9223372036854775807 cost_comparison: type: string enum: - at_least - at_most cost_ratio: description: Numeric value from 0.10 to 10.00, with zero to two decimal places. anyOf: - type: number minimum: 0.1 maximum: 10 multipleOf: 0.01 - type: string pattern: ^\+?0*(?:\.[1-9][0-9]?|[1-9](?:\.[0-9]{0,2})?|10(?:\.0{0,2})?)$ required: - target_level - action - minimum_counted_arrivals - cost_comparison - cost_ratio additionalProperties: false - type: object description: Only target_level, action, budget_change_percent, minimum_counted_arrivals, cost_comparison and cost_ratio are accepted. properties: target_level: type: string enum: - ad_set - campaign action: type: string enum: - increase_budget minimum_counted_arrivals: type: integer minimum: 100 maximum: 9223372036854775807 cost_comparison: type: string enum: - at_least - at_most cost_ratio: description: Numeric value from 0.10 to 10.00, with zero to two decimal places. anyOf: - type: number minimum: 0.1 maximum: 10 multipleOf: 0.01 - type: string pattern: ^\+?0*(?:\.[1-9][0-9]?|[1-9](?:\.[0-9]{0,2})?|10(?:\.0{0,2})?)$ budget_change_percent: type: integer minimum: 1 maximum: 50 required: - target_level - action - minimum_counted_arrivals - cost_comparison - cost_ratio - budget_change_percent additionalProperties: false - type: object description: Only target_level, action, budget_change_percent, minimum_counted_arrivals, cost_comparison and cost_ratio are accepted. properties: target_level: type: string enum: - ad_set - campaign action: type: string enum: - decrease_budget minimum_counted_arrivals: type: integer minimum: 50 maximum: 9223372036854775807 cost_comparison: type: string enum: - at_least - at_most cost_ratio: description: Numeric value from 0.10 to 10.00, with zero to two decimal places. anyOf: - type: number minimum: 0.1 maximum: 10 multipleOf: 0.01 - type: string pattern: ^\+?0*(?:\.[1-9][0-9]?|[1-9](?:\.[0-9]{0,2})?|10(?:\.0{0,2})?)$ budget_change_percent: type: integer minimum: 1 maximum: 50 required: - target_level - action - minimum_counted_arrivals - cost_comparison - cost_ratio - budget_change_percent additionalProperties: false account_id: type: integer description: Optional integer account ID. JSON booleans return 422 with message plus field errors. A valid supplied ID is ignored by an account-bound key. An unbound key requires exactly one active available advertising account with an active connection; omission uses that account and a supplied ID must match it. Multiple active accounts return 422 even when account_id is supplied. minimum: 1 platform_ad_account_id: type: integer minimum: 1 window: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 window_days: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 author_type: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 current_revision_id: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 required: - name - platform_ad_account_id - definition not: required: - enabled responses: '201': description: Saved rule with immutable revisions; no runs are included. content: application/json: schema: $ref: '#/components/schemas/ActionRuleWriteRead' '401': description: Missing, invalid, expired or revoked workspace key. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' '403': description: Insufficient ability or grant, or a conflicting workspace selector. content: application/json: schema: anyOf: - $ref: '#/components/schemas/ErrorMessage' - type: object properties: error: type: string enum: - insufficient_scope error_description: type: string required: - error - error_description '404': description: Workspace, account or rule unavailable in this scope. content: application/json: schema: $ref: '#/components/schemas/ApiRouteNotFound' '422': description: Invalid rule input or account ID, mismatched account binding, or ambiguous active accounts. content: application/json: schema: $ref: '#/components/schemas/ValidationError' '429': description: The shared API rate limit is exceeded. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' headers: Retry-After: description: Seconds until a retry is allowed. schema: type: integer '503': description: The bounded workspace transaction timed out; retry after one second. content: application/json: schema: $ref: '#/components/schemas/WorkspaceLockTimeout' headers: Retry-After: description: Seconds until the operation may be retried. required: true schema: type: integer enum: - 1 security: - ruleWriter: [] /agent/workspaces/{workspace}/action-rules/{action_rule}: get: operationId: actions.rules.show description: 'Read the account-scoped rule, immutable revisions and recent run decisions. Send Accept: application/json for the documented JSON errors.' summary: Get an action rule tags: - ActionRuleApi parameters: - name: workspace in: path required: true description: The workspace ID schema: type: integer - name: action_rule in: path required: true schema: type: string - name: account_id in: query description: Optional integer account ID. JSON booleans return 422 with message plus field errors. A valid supplied ID is ignored by an account-bound key. An unbound key requires exactly one active available advertising account with an active connection; omission uses that account and a supplied ID must match it. Multiple active accounts return 422 even when account_id is supplied. schema: type: integer minimum: 1 responses: '200': description: Rule with revisions and recent runs. content: application/json: schema: $ref: '#/components/schemas/ActionRuleDetailRead' '401': description: Missing, invalid, expired or revoked workspace key. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' '403': description: Insufficient ability or grant, or a conflicting workspace selector. content: application/json: schema: anyOf: - $ref: '#/components/schemas/ErrorMessage' - type: object properties: error: type: string enum: - insufficient_scope error_description: type: string required: - error - error_description '404': description: Workspace, account or rule unavailable in this scope. content: application/json: schema: $ref: '#/components/schemas/ApiRouteNotFound' '422': description: Invalid rule input or account ID, mismatched account binding, or ambiguous active accounts. content: application/json: schema: $ref: '#/components/schemas/ValidationError' '429': description: The shared API rate limit is exceeded. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' headers: Retry-After: description: Seconds until a retry is allowed. schema: type: integer security: - ruleReader: [] patch: operationId: actions.rules.update description: 'Write an account-scoped rule using its validated definition. Send Accept: application/json for JSON errors. New rules start disabled.' summary: Update an action rule tags: - ActionRuleApi parameters: - name: workspace in: path required: true description: The workspace ID schema: type: integer - name: action_rule in: path required: true schema: type: string requestBody: content: application/json: schema: type: object description: All fields are optional. platform_ad_account_id must be absent. window, window_days, author_type and current_revision_id must be absent or empty. properties: name: type: string pattern: \S maxLength: 255 definition: anyOf: - type: object description: Only target_level, action, budget_change_percent, minimum_counted_arrivals, cost_comparison and cost_ratio are accepted. properties: target_level: type: string enum: - ad - ad_set action: type: string enum: - pause minimum_counted_arrivals: type: integer minimum: 50 maximum: 9223372036854775807 cost_comparison: type: string enum: - at_least - at_most cost_ratio: description: Numeric value from 0.10 to 10.00, with zero to two decimal places. anyOf: - type: number minimum: 0.1 maximum: 10 multipleOf: 0.01 - type: string pattern: ^\+?0*(?:\.[1-9][0-9]?|[1-9](?:\.[0-9]{0,2})?|10(?:\.0{0,2})?)$ required: - target_level - action - minimum_counted_arrivals - cost_comparison - cost_ratio additionalProperties: false - type: object description: Only target_level, action, budget_change_percent, minimum_counted_arrivals, cost_comparison and cost_ratio are accepted. properties: target_level: type: string enum: - ad_set - campaign action: type: string enum: - increase_budget minimum_counted_arrivals: type: integer minimum: 100 maximum: 9223372036854775807 cost_comparison: type: string enum: - at_least - at_most cost_ratio: description: Numeric value from 0.10 to 10.00, with zero to two decimal places. anyOf: - type: number minimum: 0.1 maximum: 10 multipleOf: 0.01 - type: string pattern: ^\+?0*(?:\.[1-9][0-9]?|[1-9](?:\.[0-9]{0,2})?|10(?:\.0{0,2})?)$ budget_change_percent: type: integer minimum: 1 maximum: 50 required: - target_level - action - minimum_counted_arrivals - cost_comparison - cost_ratio - budget_change_percent additionalProperties: false - type: object description: Only target_level, action, budget_change_percent, minimum_counted_arrivals, cost_comparison and cost_ratio are accepted. properties: target_level: type: string enum: - ad_set - campaign action: type: string enum: - decrease_budget minimum_counted_arrivals: type: integer minimum: 50 maximum: 9223372036854775807 cost_comparison: type: string enum: - at_least - at_most cost_ratio: description: Numeric value from 0.10 to 10.00, with zero to two decimal places. anyOf: - type: number minimum: 0.1 maximum: 10 multipleOf: 0.01 - type: string pattern: ^\+?0*(?:\.[1-9][0-9]?|[1-9](?:\.[0-9]{0,2})?|10(?:\.0{0,2})?)$ budget_change_percent: type: integer minimum: 1 maximum: 50 required: - target_level - action - minimum_counted_arrivals - cost_comparison - cost_ratio - budget_change_percent additionalProperties: false account_id: type: integer description: Optional integer account ID. JSON booleans return 422 with message plus field errors. A valid supplied ID is ignored by an account-bound key. An unbound key requires exactly one active available advertising account with an active connection; omission uses that account and a supplied ID must match it. Multiple active accounts return 422 even when account_id is supplied. minimum: 1 enabled: type: boolean window: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 window_days: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 author_type: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 current_revision_id: anyOf: - type: 'null' - const: '' - type: array items: {} maxItems: 0 - type: object maxProperties: 0 not: required: - platform_ad_account_id responses: '200': description: Saved rule with immutable revisions; no runs are included. content: application/json: schema: $ref: '#/components/schemas/ActionRuleWriteRead' '401': description: Missing, invalid, expired or revoked workspace key. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' '403': description: Insufficient ability or grant, or a conflicting workspace selector. content: application/json: schema: anyOf: - $ref: '#/components/schemas/ErrorMessage' - type: object properties: error: type: string enum: - insufficient_scope error_description: type: string required: - error - error_description '404': description: Workspace, account or rule unavailable in this scope. content: application/json: schema: $ref: '#/components/schemas/ApiRouteNotFound' '422': description: Invalid rule input or account ID, mismatched account binding, or ambiguous active accounts. content: application/json: schema: $ref: '#/components/schemas/ValidationError' '429': description: The shared API rate limit is exceeded. content: application/json: schema: $ref: '#/components/schemas/ErrorMessage' headers: Retry-After: description: Seconds until a retry is allowed. schema: type: integer '503': description: The bounded workspace transaction timed out; retry after one second. content: application/json: schema: $ref: '#/components/schemas/WorkspaceLockTimeout' headers: Retry-After: description: Seconds until the operation may be retried. required: true schema: type: integer enum: - 1 security: - ruleWriter: [] components: schemas: ActionRuleDetailRead: type: object properties: rule: type: object properties: id: type: string workspace_id: type: integer platform_ad_account_id: type: integer name: type: string enabled: type: boolean suspended_at: type: - string - 'null' format: date-time suspended_reason: type: - string - 'null' window: type: object properties: days: type: integer enum: - 3 kind: type: string enum: - last_complete_days timezone: type: string required: - days - kind - timezone current_revision: type: object properties: id: type: string target_level: type: string enum: - ad - ad_set - campaign action: type: string enum: - pause - increase_budget - decrease_budget budget_change_percent: type: - integer - 'null' minimum_counted_arrivals: type: integer cost_comparison: type: string enum: - at_least - at_most cost_ratio: type: string author_type: type: string enum: - user - agent created_at: type: string format: date-time required: - id - target_level - action - budget_change_percent - minimum_counted_arrivals - cost_comparison - cost_ratio - author_type - created_at revisions: type: array description: Present on show and write responses. items: type: object properties: id: type: string target_level: type: string enum: - ad - ad_set - campaign action: type: string enum: - pause - increase_budget - decrease_budget budget_change_percent: type: - integer - 'null' minimum_counted_arrivals: type: integer cost_comparison: type: string enum: - at_least - at_most cost_ratio: type: string author_type: type: string enum: - user - agent created_at: type: string format: date-time required: - id - target_level - action - budget_change_percent - minimum_counted_arrivals - cost_comparison - cost_ratio - author_type - created_at runs: type: array description: Show only; at most five recent runs, each with at most 100 decisions. items: type: object properties: id: type: string revision_id: type: string window_start: type: string format: date-time window_end: type: string format: date-time timezone: type: string status: type: string reason: type: - string - 'null' account_spend_minor: type: - string - 'null' account_counted_arrivals: type: - integer - 'null' evaluated_at: type: - string - 'null' format: date-time decisions: type: array items: type: object properties: id: type: string target_type: type: string target_id: type: string target_name: type: - string - 'null' target_spend_minor: type: string target_counted_arrivals: type: integer decision: type: string reason: type: string action_type: type: string new_daily_budget_minor: type: - integer - 'null' action_batch_id: type: - string - 'null' required: - id - target_type - target_id - target_name - target_spend_minor - target_counted_arrivals - decision - reason - action_type - new_daily_budget_minor - action_batch_id decisions_truncated: type: boolean required: - id - revision_id - window_start - window_end - timezone - status - reason - account_spend_minor - account_counted_arrivals - evaluated_at - decisions - decisions_truncated required: - id - workspace_id - platform_ad_account_id - name - enabled - suspended_at - suspended_reason - window - current_revision - revisions - runs required: - rule title: ActionRuleDetailRead ApiRouteNotFound: type: object properties: error: type: object properties: code: type: string enum: - api_route_not_found message: type: string resolution: type: string required: - code - message - resolution request: type: object properties: method: type: string path: type: string required: - method - path resources: type: object properties: documentation: type: string openapi: type: string required: - documentation - openapi required: - error - request - resources title: ApiRouteNotFound ValidationError: type: object properties: message: type: string errors: type: object additionalProperties: type: array items: type: string required: - message - errors title: ValidationError ActionRuleWriteRead: type: object properties: rule: type: object properties: id: type: string workspace_id: type: integer platform_ad_account_id: type: integer name: type: string enabled: type: boolean suspended_at: type: - string - 'null' format: date-time suspended_reason: type: - string - 'null' window: type: object properties: days: type: integer enum: - 3 kind: type: string enum: - last_complete_days timezone: type: string required: - days - kind - timezone current_revision: type: object properties: id: type: string target_level: type: string enum: - ad - ad_set - campaign action: type: string enum: - pause - increase_budget - decrease_budget budget_change_percent: type: - integer - 'null' minimum_counted_arrivals: type: integer cost_comparison: type: string enum: - at_least - at_most cost_ratio: type: string author_type: type: string enum: - user - agent created_at: type: string format: date-time required: - id - target_level - action - budget_change_percent - minimum_counted_arrivals - cost_comparison - cost_ratio - author_type - created_at revisions: type: array description: Present on show and write responses. items: type: object properties: id: type: string target_level: type: string enum: - ad - ad_set - campaign action: type: string enum: - pause - increase_budget - decrease_budget budget_change_percent: type: - integer - 'null' minimum_counted_arrivals: type: integer cost_comparison: type: string enum: - at_least - at_most cost_ratio: type: string author_type: type: string enum: - user - agent created_at: type: string format: date-time required: - id - target_level - action - budget_change_percent - minimum_counted_arrivals - cost_comparison - cost_ratio - author_type - created_at required: - id - workspace_id - platform_ad_account_id - name - enabled - suspended_at - suspended_reason - window - current_revision - revisions required: - rule title: ActionRuleWriteRead ErrorMessage: type: object properties: message: type: string required: - message title: ErrorMessage WorkspaceLockTimeout: type: object properties: error: type: object properties: code: type: string enum: - workspace_lock_timeout message: type: string required: - code - message required: - error title: WorkspaceLockTimeout securitySchemes: workspaceSecret: type: http description: Per-workspace Server secret. It authenticates only the Signals workspace represented by the credential. scheme: bearer sandboxCredential: type: http description: Short-lived Sandbox key issued by POST /sandbox/keys. scheme: bearer workspaceActionKey: type: http description: Workspace key with read-actions for reads or propose-actions for writes. Optional positive account ID. A bound key may name only its own account; a different ID returns 422. An unbound key requires exactly one active available advertising account with an active connection; omission uses that account and a supplied ID must match it. Multiple active accounts return 422 even when account_id is supplied. scheme: bearer planWriter: type: http description: An OAuth agent or account-bound Workspace Key with write access and the plan-create grant. scheme: bearer workspaceInventoryKey: type: http description: Workspace read key with the get_inventory_metrics grant. Account-bound keys can read only their bound account. scheme: bearer planReader: type: http description: An OAuth agent or account-bound Workspace Key with read access to the approved ad account. scheme: bearer ruleReader: type: http description: 'A workspace-scoped key with read ability and actions.rules.index or actions.rules.show access to the bound account. Send Accept: application/json.' scheme: bearer ruleWriter: type: http description: 'A workspace-scoped key with write ability and the actions.rules.store or actions.rules.update grant. Send Accept: application/json.' scheme: bearer agentContextKey: type: http description: Workspace key with read ability, ad-account.read permission and the agent.context grant. scheme: bearer agentContextOAuth: type: oauth2 description: OAuth access token for an active approved agent principal with mcp:use, ad-account.read and agent.context authorization. Authorization code with PKCE and device authorization tokens are accepted. This scheme applies only to agent context. flows: authorizationCode: authorizationUrl: https://plainrouter.com/oauth2/authorize tokenUrl: https://plainrouter.com/oauth2/token refreshUrl: https://plainrouter.com/oauth2/token scopes: mcp:use: Use the approved agent principal. creativeWriter: type: http description: Workspace-bound write credential with a grant authorizing creative intake. scheme: bearer testReader: type: http description: A workspace principal with read tier and the launch.plans.index and launch.plans.show grants for this account. scheme: bearer testWriter: type: http description: A workspace principal with write tier and the launch.plans.store grant for this account. scheme: bearer x-action-types: - increase_budget - decrease_budget - pause - resume - replace_creative - shift_spend - rollback - create_campaign - create_ad_set - create_ad - upload_asset x-signed: true