---
title: "Stop the runs on a playtable field"
method: POST
path: "/api/playtable-fields/{sid}/cancel-runs"
tags: ["playtable_fields"]
---

# Stop the runs on a playtable field

`POST /api/playtable-fields/{sid}/cancel-runs`

## Request

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

Stop everything running on one column: every unfinished run holding it, active and waiting, is cancelled through the runs' cancel, waiting ones first; a run over several columns stops whole. Queued cells go back; cells a worker holds finish and are charged (in_flight_cells_count); no refunds. Call with dry_run:true first and tell the user the numbers. Nothing running: runs []. A formula column fills and has no stop. One run: cancel_playtable_run; the table: cancel_playtable_runs.

Contract:
- MCP tool `cancel_playtable_field_runs`, registry package `mcp.playtables/playtable_fields`, mount `playtables.columns`.
- Operation `action`, response envelope `action`.
- Flags: dangerous: the MCP layer gates it behind a preview/commit token, and the effect cannot be undone through this API.

## Path parameters

- `sid` string, required: The column (pt_fd_…) whose runs stop: an action column with runs on it. A plain or formula column answers an empty result (nothing of it ever runs: a formula column fills, and a fill has no stop); another team's is 403.

## Request body

- CancelPlaytableFieldRunsRequest: Request body of `cancel_playtable_field_runs`.
  - `dry_run` boolean: true: answer the counts (the runs, the queued cells that would go back, the cells still running that finish and are charged) and cancel nothing. Call it first and tell the user the numbers; then call without it.

## Response `200`

`action` success envelope.

- CancelPlaytableFieldRunsResponse
  - `success` true, required
  - `operation` 'action', required
  - `action` string, required: kebab-case verb; matches the route segment.
  - `item` object, nullable, required
    - `sid` string, required
    - `team_sid` string, required
    - `playtable_sid` string, required
    - `object_type` 'people' | 'company', required: Object grid this field belongs to: people or company rows. Fixed at create.
    - `name` string, required: Display label, freely editable.
    - `slug` string, required: Template-variable name ({{record.<slug>}}); unique per playtable, IMMUTABLE after create.
    - `category` 'scalar' | 'widget' | 'action', required: scalar = data column (optional free formula) | widget = live CRM projection, no cells | action = paid executor. Derived from the type prefix - never user-supplied.
    - `type` 'scalar_text' | 'scalar_number' | 'scalar_currency' | 'scalar_date' | 'scalar_url' | 'scalar_checkbox' | 'scalar_select' | 'scalar_multiselect' | 'scalar_image' | 'widget_data_source' | 'widget_tags' | 'widget_active_workflows' | 'widget_notes' | 'widget_company' | 'widget_linkedin_id' | 'widget_linkedin_channel' | 'widget_work_email' | 'widget_personal_email' | 'widget_email_channel' | 'widget_phones' | 'widget_phone_channel' | 'widget_last_touch' | 'widget_activity' | 'widget_deal_size' | 'widget_website' | 'widget_people_summary' | 'widget_ai_sdr' | 'action_ai_research' | 'action_waterfall' | 'action_gtm_tool' | 'action_find_people', required: The category derives from the prefix. Scalar and the generic action types fit both grids; action_find_people is company-only. Widgets: data_source, tags, last_touch, activity on both; deal_size, website, people_summary company-only; the rest people-only.
    - `payload` union, required: Per-type payload; the discriminator is the row's type column, not an inner key - the shape must match type (scalar / widget / active_workflows / channel / ai_research / waterfall / gtm_tool / find_people branches) or the save fails with 422.
      - object
        - `formula` string, nullable, required: CEL expression, NO {{ }} braces (those are for templates). Reference paths extract typed values: record.<slug>(.<seg>|[N])* - e.g. "record.enrich.contact_info.websites[0].url" (extraction column - the "Add as column" case; backfills all rows on save, re-derives on every source run; absent path → empty cell), and on a people grid company.record.<slug>(.<seg>|[N])* - the linked account's cell in this playtable (a Companies-tab column; on the company grid the account is the row itself). A company.record reference is checked against the Companies tab at save, backfilled the same way, and is NOT re-derived when the account's cell changes later (no cross-object dependency: a snapshot until the column's next recalc). Full CEL computes: operators, ?: conditions, has(), string concat - e.g. 'record.employees > 50 ? 100 : 40'. Syntax errors 422 at save; runtime failures land as cell error_msg. Evaluated backend-side; null = manually-entered column. Setting/changing it recomputes EVERY cell including pinned ones. When template is set, this is server-GENERATED from it - send the template instead of hand-writing CEL.
        - `template` string, nullable, required: The unified authoring source - prose + {{record.<slug>…}} / {{people.<attr>}} / {{company.<attr>}} / {{company.record.<slug>…}} variables, as in a flow-node message: "Hey {{people.first_name}}, saw {{company.name}}!". record.<slug> is THIS tab's columns only ({{record.full_name}} is refused unless a column has that slug); people.<attr> the person's catalog (full_name, first_name, company_name, ln_nickname, work_email, timezone, pipeline_stage_name...); company.<attr> the linked account's (name, domain, website, ln_nickname, people_count...); company.record.<slug> the company tab's columns. An attribute outside the catalogs is refused at save (unknown template variable people.title); people.role and people.headline are derived (the title and the profile line the enrichment, the search record or the inbox observed), and {{record.data_source.position}} still reads the raw record. Compiled into formula on every save: absent value → empty string; a LONE variable ({{record.enrich.company.employees}}) → the bare typed reference (numbers stay numbers); stray braces → 422 on payload.template. Round-trips verbatim. null = formula authored as CEL; template:"" clears both.
        - `nl_formula` string, nullable, required: Natural-language source of truth the formula was compiled from - agents edit THIS and regenerate formula.
        - `options` string[], nullable, required: Option labels for scalar_select / scalar_multiselect (min 1); null for every other scalar type.
        - `currency` string, nullable, required: ISO-4217 code (e.g. USD) for scalar_currency only; null = render default USD; null for every other scalar type.
        - `option_colors` object, nullable, required: Option name → #rrggbb hex for scalar_select / scalar_multiselect only; keys ⊆ options (min 1 entry - send null instead of an empty map).
      - object: Always {} - widget rendering is type-driven (widget_active_workflows and the two channel widgets carry their own knobs, below).
      - object
        - `workflow_sid` string, nullable, required: null = All workflows: a cell counts the lead's live journeys by state. A wf_wf_ sid = Specific workflow: the column is pinned to it, a cell shows the lead's record there, finished too; a live workflow (422 relation_not_found). A pin outlives its workflow.
      - object
        - `stats_view` 'all' | 'last' | 'select', required: Whose numbers the cell shows: all = every sender profile's facts summed, no face; last = the sender profile that touched the person on that channel last, its face and counters (the default); select = the one named by sender_profile_sid.
        - `sender_profile_sid` string, nullable, required: The sender profile of a select view (ub_sp_): required under select, null otherwise (422 validation_failed). It must name a live sender profile of the team (422 relation_not_found); a deleted pick is kept until another is set.
      - object
        - `prompt` string, required: Twig-templated research instruction; the template's variables, an unknown one 422. What the source found on a person (the role, the location) is in the Data source cell: {{record.data_source}} (its slug on a new table) reads it whole.
        - `model` string, required: 'auto' (gtm-api routes the call) or a concrete gtm-api model id; validated against the gtm-api registry at save.
        - `output` 'text' | 'number' | 'select' | 'json', required: Cell shape: text | number (non-numeric model answer → cell error) | select (one of payload.options) | json (extract values into columns via extraction formulas record.<slug>.<path>; push onto people/company attributes via output_mapping).
        - `options` string[], nullable, required: Required (min 1) when output=select; null otherwise.
        - `output_mapping` object[], nullable, required: Project a 'json' result onto people/company attributes, fill-if-empty (max 20); null = no attribute writes. Columns pull via extraction formulas instead.
          - `path` string, required: JSON path into the tool/model result, e.g. $.email or $.techs[0].name.
          - `target` string, required: Entity-attribute target ONLY: people.<attr> or company.<attr> (writable-attribute registries; fill-if-empty at projection); validated at save. No record.<slug> targets: columns pull via extraction formulas (record.<slug>.<path>), never via push mapping.
        - `web_search` boolean: Default false: the model answers with the vendor's server-side web search on. A per-model capability: 422 relation_not_found at save, and web_search_not_supported on a preview, for a model without it.
      - object
        - `steps` object[], required: 1..10 ordered gtm-api tool steps, first accepted result wins; pass [] when config.preset_sid materializes them server-side (never both).
          - `tool_sid` string, required: The step: a gtm-api tool registry sid, an integration-platform step (in:{provider}.{operation}, in:tool.{sid}) or the row itself (row:people.work_email: the row's own address, free, verified unless already ok; people, once). Price validated at save.
          - `params` object, required: Tool params; string values may be Twig templates over the same variable namespace.
        - `stop_on_first` boolean, required: true (default) = stop at the first step returning a non-empty accepted result; false = run all, first non-empty wins.
        - `output_mapping` object[], nullable, required: Winning step's result → people/company attributes, fill-if-empty (max 20); null = no attribute writes.
          - `path` string, required: JSON path into the tool/model result, e.g. $.email or $.techs[0].name.
          - `target` string, required: Entity-attribute target ONLY: people.<attr> or company.<attr> (writable-attribute registries; fill-if-empty at projection); validated at save. No record.<slug> targets: columns pull via extraction formulas (record.<slug>.<path>), never via push mapping.
        - `verify` object, nullable: One in:{provider}.email.verify step on every email a finder returns, no params. valid accepts and ends the chain, so does catch_all while accept_catch_all is on; invalid, unknown or a failure rejects, the next finder runs. null = none, first found wins.
          - `tool_sid` string, required: The step: a gtm-api tool registry sid, an integration-platform step (in:{provider}.{operation}, in:tool.{sid}) or the row itself (row:people.work_email: the row's own address, free, verified unless already ok; people, once). Price validated at save.
          - `params` object, required: Tool params; string values may be Twig templates over the same variable namespace.
        - `accept_catch_all` boolean: true (default): a catch-all address is accepted the moment the verifier says so and the chain stops there. false = catch-all emails are rejected like invalid ones (safe to send only) and the next finder runs. Meaningful only with verify set.
      - object
        - `tool_sid` string, required: gtm-api tool registry sid (external format); existence + price validated at save.
        - `params` object, required: Tool params; string values may be Twig templates over the same variable namespace.
        - `output_mapping` object[], nullable, required: Tool result → people/company attributes, fill-if-empty (max 20); null = no attribute writes.
          - `path` string, required: JSON path into the tool/model result, e.g. $.email or $.techs[0].name.
          - `target` string, required: Entity-attribute target ONLY: people.<attr> or company.<attr> (writable-attribute registries; fill-if-empty at projection); validated at save. No record.<slug> targets: columns pull via extraction formulas (record.<slug>.<path>), never via push mapping.
      - object
        - `prompt` string, nullable, required: Who to find at each company, in words; null = everyone there. With data_source_sid null on create the server births a find_people source; a new text on update births a new search and parks the old. 422 prohibited beside a picked data_source_sid.
        - `per_company_limit` integer, required: People taken per company, 1..25 (one search page, by relevance). Default 1. Mirrored onto the column's own search; read from a picked one.
        - `data_source_sid` string, nullable, required: The find_people source the cells search (ds_sc_). null on create = the server makes one for the column (the sid comes back). A sid = a find_people search of the SAME table, as is (422 other_table elsewhere, relation_not_found when gone). Never null stored.
        - `qualify` object, nullable: Check on save: {on, criterion} the column's own search takes at every save, judging the people the walk finds before they land (data_sources.qualify); null or absent = no check. 422 prohibited beside a picked data_source_sid (it has its own gate).
          - `on` boolean, required: The switch. `on` with no criterion is refused (422 on qualify.criterion, rule required_if).
          - `criterion` string, nullable, required: Who passes, in the user's words ("a decision maker at a marketing agency, not a freelancer"). Trimmed; null while the check is off.
    - `config` object, required
      - `auto_run` boolean, required: Action auto-fires when a row enters the table / the cell is empty (paid: empty→queued only). Default false.
      - `run_condition` union, required: Per-row filter-AST gate (§4.18 atoms, strict mode); unmet → cell status run_condition_not_met; null = always run.
        - 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
      - `nl_condition` string, nullable, required: Natural-language source of truth for run_condition - agents edit THIS and regenerate the AST.
      - `daily_credit_limit` integer, nullable, required: Max credits this field may spend per UTC day (int > 0, enforced at run time); null = no cap.
      - `preset_sid` string, nullable, required: gtm-api preset sid (external) that materializes waterfall steps at save; action_waterfall only, mutually exclusive with hand-built steps. null otherwise.
    - `backfill` object, nullable, required: The formula column's backfill ledger (see PlaytableFieldBackfill); null on a column with no formula.
      - `status` 'queued' | 'running' | 'done' | 'failed', required
      - `scanned` integer, required: Objects the scan has visited so far.
      - `total` integer, nullable, required: The scan size, known once the job starts (null while queued).
      - `queued_at` string, nullable, required: ISO 8601 UTC: when the column started owing its backfill.
      - `started_at` string, nullable, required: ISO 8601 UTC: when the job took it.
      - `finished_at` string, nullable, required: ISO 8601 UTC: when it ended, done or failed.
      - `error` string, nullable, required: A reason token (backfill_failed) on failed, never an exception message.
    - `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.
    - `deleted_by` object, nullable, 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
    - `updated_at` string, required
    - `deleted_at` string, nullable, required
  - `result` object, required
    - `dry_run` boolean, required: true: the counts below are what a stop WOULD do; nothing was cancelled.
    - `runs` object[], required: The unfinished runs found, in the order they were stopped: the waiting ones first, then the dispatched ones.
      - `sid` string, required: Playtable run sid (pt_rn_…).
      - `waiting` boolean, required: true: the run held no batch yet (it waited behind another run on its field).
      - `reverted_cells_count` integer, required: Its queued cells sent back (on a dry run: the ones that would be).
      - `in_flight_cells_count` integer, required: Its cells a worker holds right now: they finish and are charged either way.
    - `cancelled_count` integer, required: Runs this call cancelled (0 on a dry run; a run that ended on its own meanwhile is not counted).
    - `reverted_cells_count` integer, required: Queued cells sent back, summed over the runs (no prior value → empty, a prior value → done).
    - `in_flight_cells_count` integer, required: Cells still running, summed over the runs: they finish and are charged. Say so to the user before the stop.
    - `spent_credits` number, required: The runs' spend so far, summed; the in-flight cells may still add to it (the ledger is truth).
  - `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)
