---
title: "Create playtable run"
method: POST
path: "/api/playtable-runs"
tags: ["playtable_runs"]
---

# Create playtable run

`POST /api/playtable-runs`

## Request

- Base URL: `https://beta.getsales.io/playtables/v1`
- URL: `https://beta.getsales.io/playtables/v1/api/playtable-runs`
- Auth: HTTP bearer and API key in header `Team-SID`

The single entry point for every bulk operation on a playtable. Async: the created row is the progress handle: poll get_playtable_run with include progress every ~5s until progress.state is finished or cancelled.

type branches the body. enrich is the only paid type (action columns over the scope; a formula column never runs, it fills through backfill_playtable_field): always estimate_playtable_run first, tell the user the cost range and set max_budget to its cost_max (exhaustion auto-cancels, reason budget_exhausted; a cap at cost_expected cancels a row short); params force_rerun re-queues done cells (re-charge consent), params cells not_run | error narrows to those. export writes the scope to CSV (params format csv, a file_url) or an external tool (write_tool_sid). import batch-promotes a data source (params data_source_sid, no scope). bulk runs tag, field, workflow, move and delete edits (params operation).

scope, required for enrich, export and bulk: one form, object_sids or view_sid/filter; an empty filter is rejected, the scope is snapshotted at create; hand-picked object_sids and exclude_object_sids are capped at 10 000 each (422 scope_too_large). 409 conflict when a field already has an active run (its sid in error.context), 429 at the team concurrency cap; already_exists is always false. When NOT: single-cell fixes (update_playtable_field_value); continuous source syncing (data sources; import here is a one-shot promotion).

Contract:
- MCP tool `create_playtable_run`, registry package `mcp.playtables/playtable_runs`, mount `playtables.columns`.
- Operation `create`, response envelope `create`.
- Flags: none.

## Request body

- CreatePlaytableRunRequest: Request body of `create_playtable_run`.
  - `playtable_sid` string, required: Parent playtable sid (pt_tb_…).
  - `object_type` 'people' | 'company', required: Which tab the run targets: people | company.
  - `type` 'enrich' | 'export' | 'import' | 'bulk', required: enrich (PAID - estimate first, set max_budget) | export | import | bulk. Branches which fields below are required.
  - `field_sids` string[]: REQUIRED for type=enrich, forbidden otherwise (422): 1..20 action columns (a widget, a plain column or a formula column is 422; a formula column fills through backfill_playtable_field instead).
  - `scope` object: REQUIRED for enrich | export | bulk, forbidden for import (422). Exactly one form: object_sids XOR view_sid/filter.
    - `object_sids` string[], nullable: Explicit targets (cr_pp_… / cr_co_… of the run object_type), min 1, max 10 000 (a wider selection is a view/filter, optionally with exclude_object_sids). Exactly one scope form: XOR with view_sid/filter - both or neither → 422.
    - `view_sid` string, nullable: Saved view (pt_vw_…) - its filter AST is snapshotted into the run at create; later view edits do not retarget. Combinable with filter; XOR with object_sids.
    - `exclude_object_sids` string[], nullable: Rows of the view/filter form to leave out (select all minus the unticked rows), at most 10 000; never with object_sids.
    - `filter` union: Ad-hoc filter AST over the tab (§4.18 atoms, depth <= 5); stored resolved. Combinable with view_sid; XOR with object_sids. An empty/match-all filter form is rejected.
      - object
        - `and` object[], required
      - object
        - `or` object[], required
      - object
        - `not` object, required: Recursive node: the same object shape repeats here. A static OpenAPI document cannot express the cycle, so this level is documented as an open object.
      - object
        - `any` object, required
          - `path` string, required: Array-valued path - the only way to reach inside an array.
          - `where` object, required: Recursive node: the same object shape repeats here. A static OpenAPI document cannot express the cycle, so this level is documented as an open object.
      - object
        - `field` string, required: Namespaced path: record.<slug> or a built-in object attribute; element-relative inside any.where.
        - `op` object, required
          - `eq` union
            - string
            - number
            - boolean
          - `ne` union
            - string
            - number
            - boolean
          - `in` union[]
            - union
              - …
          - `nin` union[]
            - union
              - …
          - `gt` union
            - number
            - string
          - `gte` union
            - number
            - string
          - `lt` union
            - number
            - string
          - `lte` union
            - number
            - string
          - `is_null` boolean
          - `contains` string
          - `not_contains` string
          - `contains_any` string[]
          - `not_contains_any` string[]
          - `starts_with` string
      - unknown
  - `params` object: Type-specific params, snapshotted into params_snapshot: enrich {force_rerun?, cells?}; export {format:"csv"} or {write_tool_sid}; import {data_source_sid}; bulk {operation, …op payload}.
    - `force_rerun` boolean: type=enrich: also re-queue done cells (explicit re-charge consent - §4.6). Default false.
    - `cells` 'not_run' | 'error': type=enrich: run only the cells that have not run (not_run) or only the error cells (error); omit for every cell but the done ones. Pass the same value to the estimate.
    - `format` 'csv': type=export: CSV to S3 → file_url. Alternative to write_tool_sid.
    - `write_tool_sid` string: type=export: push rows to an external tool via this gtm-api write tool (external sid). Alternative to format.
    - `data_source_sid` string: type=import (REQUIRED there): the data source whose items are batch-promoted.
    - `operation` 'add_tags' | 'remove_tags' | 'replace_tags' | 'set_field_value' | 'delete_objects' | 'add_to_workflow' | 'cancel_from_workflows' | 'move_to_playtable': type=bulk (REQUIRED there): the bulk edit op. Its payload keys ride alongside: tag_sids[] (add / remove / replace_tags), playtable_field_sid + value (set_field_value), workflow_sid (add_to_workflow), target playtable (move_to_playtable); checked per op.
  - `max_budget` number, nullable: Decimal credits - enrich only: hard per-run spend cap; the budget guard cancels the run when exhausted (reason=budget_exhausted). Omit/null = uncapped. Base it on the estimate cost range.

## Response `200`

`create` success envelope.

- CreatePlaytableRunResponse
  - `success` true, required
  - `operation` 'create', required
  - `item` object, required
    - `sid` string, required
    - `team_sid` string, required
    - `playtable_sid` string, required
    - `object_type` 'people' | 'company', required
    - `type` 'enrich' | 'export' | 'import' | 'bulk', required
    - `field_sids` string[], nullable, required
    - `scope` object, nullable, required
      - `object_sids` string[], nullable, required
      - `view_sid` string, nullable, required
      - `filter` union, required: Filter AST: and/or/not/any nodes (depth <= 5, root counts) over {field, op} leaves; op = >=1 of the §4.18 atoms; atoms AND.
        - object
          - `and` object[], required
        - object
          - `or` object[], required
        - object
          - `not` object, required: Recursive node: the same object shape repeats here. A static OpenAPI document cannot express the cycle, so this level is documented as an open object.
        - object
          - `any` object, required
            - `path` string, required: Array-valued path - the only way to reach inside an array.
            - `where` object, required: Recursive node: the same object shape repeats here. A static OpenAPI document cannot express the cycle, so this level is documented as an open object.
        - object
          - `field` string, required: Namespaced path: record.<slug> or a built-in object attribute; element-relative inside any.where.
          - `op` object, required
            - `eq` union
              - …
            - `ne` union
              - …
            - `in` union[]
              - …
            - `nin` union[]
              - …
            - `gt` union
              - …
            - `gte` union
              - …
            - `lt` union
              - …
            - `lte` union
              - …
            - `is_null` boolean
            - `contains` string
            - `not_contains` string
            - `contains_any` string[]
            - `not_contains_any` string[]
            - `starts_with` string
        - unknown
    - `params_snapshot` object, nullable, required
    - `max_budget` number, nullable, required
    - `estimated_cost` number, nullable, required
    - `file_url` string, nullable, required
    - `created_by` object, required
      - `actor_type` 'user' | 'support' | 'api_key' | 'system' | 'agent', required
      - `actor_sid` string, nullable, required
      - `team_sid` string, required
      - `actor_name` string, nullable: The OAuth client that acted ("Claude", "n8n"); null for a user, an API key or a system job.
      - `oauth_client_sid` string, nullable: The acting OAuth client (id_oc_*); null off the OAuth path.
      - `reason` string, nullable: Why a system actor wrote the row (snapshot_capture_job, ...); null otherwise.
      - `permissions` object, required: Internal audit context: the grant set the actor held at write time. Not a contract.
      - `cluster_id` integer, nullable: Internal audit context: the cluster that served the write.
      - `trace_id` string, nullable: Internal audit context: the trace id of the request that wrote the row.
    - `created_at` string, required
    - `finished_at` string, nullable, required
  - `already_exists` boolean, required
  - `meta` object, required
    - `trace_id` string, required: UUID v7; same 128-bit value as the X-Trace-Id header.
    - `span_id` string, required: 16 hex chars, root span of this request.
    - `timestamp` string, required: ISO 8601 UTC (Y-m-dTH:i:sZ), response time.
    - `duration_ms` integer, required: Server-side wall clock.
    - `team_sid` string, nullable: The team this call ran in (the token team, or the team_sid override). Null when unauthenticated; absent from pre-2026-08-20 backends.
    - `actor_type` string, nullable: user | agent | api_key | system. Null when unauthenticated; absent from pre-2026-08-20 backends.

## Other responses

- `4XX`: MCP error envelope. `error.code` is one of validation_failed, nothing_to_update, not_found, relation_not_found, invalid_transition, limit_exceeded, payment_required, duplicate_rejected, conflict, delete_blocked, unauthorized, forbidden, rate_limited, not_implemented.
- `5XX`: MCP error envelope with `error.code` internal_error or service_unavailable.

---

[API](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-playtables.md) - [All operations](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-playtables/llms.txt) - [OpenAPI document](https://skmtc.dev/getsales/apis/getsales-public-contract-gs-service-playtables/revisions/aa394506515d?raw)
