---
title: "Get workflow"
method: GET
path: "/api/workflows/{sid}"
tags: ["workflows"]
---

# Get workflow

`GET /api/workflows/{sid}`

## Request

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

Fetch one workflow by sid - the editor-open call. draft_steps rides the item; include published_version to diff draft vs live, records_counts for the header funnel.

Contract:
- MCP tool `get_workflow`, registry package `mcp.playtables/workflows`, mount `playtables.workflows`.
- Operation `get`, response envelope `get`.
- Flags: read only.

## Path parameters

- `sid` string, required: The wf_wf_* sid of the row to read.

## Query parameters

- `include[]` string[]: Relations to attach to the row; omit for the bare row.

## Response `200`

`get` success envelope.

- GetWorkflowResponse
  - `success` true, required
  - `operation` 'get', required
  - `item` object, required
    - `sid` string, required
    - `team_sid` string, required
    - `name` string, required
    - `description` string, nullable, required
    - `status` 'draft' | 'active' | 'paused' | 'archived', required
    - `published_version_sid` string, nullable, required: wf_vr_… [ro] - written ONLY by publish; null until first publish. Old versions are never repointed back to (no rollback verb - republish the old steps instead).
    - `draft_steps` union[], required: Working copy of the step document (chain-and-blocks v2); step ids always present on read (server-assigned). Not executable until published. Max 100 steps incl. nested / 512 KB.
      - union
        - object
          - `type` 'linkedin_send_connection_request', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `message` string, nullable: Invite note - max 300 RENDERED chars (server-checked); null/omitted = blank invite. Template namespaces: {{people.*}} {{company.*}} {{record.<slug>}} {{ai.<key>}}; per-variable fallback {{record.slug|text}}.
          - `required_fields` object, nullable
            - `refs` string[], required: 0..20 bare template refs required on top of the text's own variables (people.first_name, company.name, record.<slug>, company.record.<slug>, ai.<key>).
            - `on_missing` 'fail' | 'skip', required: fail | skip - what to do when a value the text needs is empty at task time; no policy (null) sends the text as it renders.
        - object
          - `type` 'linkedin_send_message', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `message` string, required: Message template (same namespaces as all templates).
          - `required_fields` object, nullable
            - `refs` string[], required: 0..20 bare template refs required on top of the text's own variables (people.first_name, company.name, record.<slug>, company.record.<slug>, ai.<key>).
            - `on_missing` 'fail' | 'skip', required: fail | skip - what to do when a value the text needs is empty at task time; no policy (null) sends the text as it renders.
        - object
          - `type` 'linkedin_send_inmail', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `subject` string, nullable
          - `message` string, required
          - `required_fields` object, nullable
            - `refs` string[], required: 0..20 bare template refs required on top of the text's own variables (people.first_name, company.name, record.<slug>, company.record.<slug>, ai.<key>).
            - `on_missing` 'fail' | 'skip', required: fail | skip - what to do when a value the text needs is empty at task time; no policy (null) sends the text as it renders.
        - object
          - `type` 'linkedin_visit_profile', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
        - object
          - `type` 'linkedin_endorse_skills', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `skills_count` integer, required: Endorse the person's top N listed skills, 1..10.
        - object
          - `type` 'linkedin_withdraw_connection_request', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
        - object
          - `type` 'linkedin_like_latest_post', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
        - object
          - `type` 'linkedin_comment_latest_post', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `comment` string, required: Comment template on the person's latest post, 1..1250 rendered; same namespaces as message templates.
          - `required_fields` object, nullable
            - `refs` string[], required: 0..20 bare template refs required on top of the text's own variables (people.first_name, company.name, record.<slug>, company.record.<slug>, ai.<key>).
            - `on_missing` 'fail' | 'skip', required: fail | skip - what to do when a value the text needs is empty at task time; no policy (null) sends the text as it renders.
        - object
          - `type` 'email_send', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `subject` string, required
          - `body` string, required: HTML body template.
          - `required_fields` object, nullable
            - `refs` string[], required: 0..20 bare template refs required on top of the text's own variables (people.first_name, company.name, record.<slug>, company.record.<slug>, ai.<key>).
            - `on_missing` 'fail' | 'skip', required: fail | skip - what to do when a value the text needs is empty at task time; no policy (null) sends the text as it renders.
          - `email_field` string, nullable: Which address the send uses: any | work_email | personal_email; omitted or any = the best one.
          - `cc` string[], nullable: Up to 5 literal copy addresses.
          - `bcc` string[], nullable: Up to 5 literal blind-copy addresses.
          - `attachments` object[], nullable: Up to 5 library files ({sid: wf_at_…, name}) sent with the email.
            - `sid` string, required
            - `name` string, required
          - `reply_in_thread` boolean, nullable
          - `track_opens` boolean, nullable
          - `track_clicks` boolean, nullable
          - `unsubscribe_link` boolean, nullable
        - object
          - `type` 'wait', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `for` string, required: Duration string: ^[1-9][0-9]*(m|h|d)$ - e.g. "45m", "12h", "14d". Publish lints the range 1m..365d.
        - object
          - `type` 'wait_for_event', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `event` 'linkedin_connection_request_accepted' | 'email_opened' | 'email_link_clicked', required: Event observed via the activities ingest.
          - `timeout` object, required: Required since 2026-10-11: a wait always has a time limit. Bundled: a timeout path cannot exist without a duration.
            - `after` string, required: Duration string: ^[1-9][0-9]*(m|h|d)$ - e.g. "45m", "12h", "14d". Publish lints the range 1m..365d.
            - `steps` 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
          - `type` 'condition', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `branches` object[], required: 1..9 branches, evaluated in order, first match wins.
            - `id` integer: Server-assigned - SAME id-space as step ids; omit on new branches, carry on kept ones. The fired branch id lands on the condition task result.
            - `label` string, nullable: Editor caption.
            - `when` object, required: NON-nullable filter AST, LOOSE mode (predicate-only, never compiled to ES): leaf paths people.* / company.* / record.<slug> / ai.<output_key> / api.<output_key>.<path>; a path missing from the record's playtable makes the leaf false.
            - `steps` 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.
          - `else` 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
          - `type` 'ab_test', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `branches` object[], required: 2..9 weighted arms; the record is assigned deterministically by hashing its sid into the weight distribution. No `else`.
            - `id` integer: Server-assigned - SAME id-space as step ids; omit on new arms, carry on kept ones. The assigned arm id lands on the ab_test task result.
            - `label` string, nullable: Editor caption ("Variant A").
            - `weight` integer, required: Relative weight (int >= 1; need not sum to 100). Record assigned by crc32(record.sid) mod sum(weights) - see workflow_versions.md ab_test algorithm.
            - `steps` 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
          - `type` 'end', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
        - object
          - `type` 'api_call', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', required
          - `url` string, required: https-only (publish check); a template - variables may stand in the path and the query.
          - `headers` object, nullable
          - `query` object, nullable
          - `body` string, nullable: JSON body template, variables inside quotes; null = the fixed payload; must be null on GET / DELETE.
          - `provider_connection_sid` string, nullable: A `custom` provider connection whose auth the integration platform injects at call time; null = no auth.
          - `timeout_secs` integer, nullable
          - `retry_on_error` boolean, nullable
          - `remove_empty_values` boolean, nullable
          - `output_key` string, nullable: The answer's name for later steps (2026-10-11): a slug, unique within the version with the ask_ai keys; later steps read {{api.<output_key>.<path>}} and condition leaves filter on api.<output_key>.<path>; null = the answer is not kept.
        - object
          - `type` 'ask_ai', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `prompt` string, required: Same variable namespaces as message templates.
          - `output_key` string, required: Slug, unique within the version (publish check) - later steps read {{ai.<output_key>}} / condition leaves on ai.<output_key>.
          - `preset_sid` string, nullable: gtm-api preset for the AI call; null = team default.
        - object
          - `type` 'manual_task', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `title` string, required
          - `instructions` string, nullable: Rendered into the human todo; the journey waits until the task is completed.
        - object
          - `type` 'move_to_workflow', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `target_workflow_sid` string, required: Enroll the person into this campaign (wf_wf_…); reuses the existing record per the re-entry policy. THIS record then completes - terminal-for-this-record.
          - `mode` 'from_start' | 'from_current', nullable: Re-entry mode when the person is ALREADY in the target (workflow_records.md, Re-entry): from_start (default; reset to the entry step) | from_current (resume the pinned step, else the entry step). A re-entry is loop-guarded at re_entered_count 10.
        - object
          - `type` 'add_tag', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `tag_sids` string[], required: 1..20 CRM tags (cr_tg_…) attached to the person mid-flow, then falls through.
        - object
          - `type` 'remove_tag', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `tag_sids` string[], required: 1..20 CRM tags (cr_tg_…) detached from the person mid-flow, then falls through.
        - object
          - `type` 'set_field', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `field_slug` string, required: Playtable field slug to write (pipeline_stage is a scalar_select field).
          - `value` union, required: Scalar cell value (one cell; no arrays/objects); null clears the field.
            - string
            - number
            - boolean
            - unknown
            - unknown
        - object
          - `type` 'change_sender', required
          - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
          - `name` string, nullable: Editor label.
          - `sender_profile_sid` string, nullable: Explicit forward-only re-pin (ub_sp_…). Exactly one of sender_profile_sid / rotation (publish check). NOT a restart-from-top.
          - `rotation` 'fair' | 'random' | 'new_sender' | 'prior_engagement', nullable: Re-pick the sender from the workflow pool by this strategy instead of a fixed sid. Exactly one of sender_profile_sid / rotation (publish check).
    - `schedule` object, nullable, required: The campaign's sending windows, the one send-window gate: null = any time (gtm-api's per-account limits still pace the sends). A sender profile carries no window of its own (founder 2026-09-22).
      - `timeblocks` object[], required
        - `dow` integer, required: Day of week, 0=Sunday … 6=Saturday (legacy Carbon dayOfWeek convention).
        - `start_minute` integer, required: Minutes from local midnight (legacy key: min).
        - `end_minute` integer, required: Minutes from local midnight; must be > start_minute (server-validated). Legacy key: max.
      - `timezone` string, required: IANA tz name - base timezone for the blocks.
      - `use_people_timezone` boolean, required: Resolve blocks in the person's timezone when known, falling back to `timezone`.
    - `on_reply` 'autopilot' | 'copilot' | 'stop' | 'continue', required: What a reply does (default copilot): autopilot = the records pause and the AI SDR answers within its caps; copilot = the records pause and the AI SDR drafts for approval; stop = the records pause and the thread is the human's; continue = the records keep sending (nurture), the agent stays out. Reply scope = all the person's live records. Frozen onto each thread the workflow acts on as its automation_ref mode (autopilot | copilot | off).
    - `agent_sid` string, nullable, required: The AI SDR picked for this workflow (ub_ag_…, block 10 step 10.2): the agent that works the threads it starts, every setting on the agent itself (update_agent). Null = its home table's pick, else the team's default. The campaign's own setup of before (ai_sdr_setup, 2026-10-12 to 2026-10-30) became an agent of its own in step 10.3.
    - `sender_profile_sids` string[], required: ub_sp_… each - the sender pool; a record is pinned to ONE sender at enrollment by rotation_strategy.
    - `rotation_strategy` 'fair' | 'random' | 'new_sender' | 'prior_engagement', required
    - `priority` integer, required: 1..3 (low / medium / high) - the workflow's SHARE of a sender profile it shares with other workflows (three actions of high for one of low), never a rank (see ./workflow_tasks.md).
    - `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
  - `included` object, required
    - `published_version` object, nullable: WorkflowVersionDomain (1:1 via published_version_sid; heavy - full steps JSON) - see ./workflow_versions.md.
      - `sid` string, required: wf_vr_….
      - `workflow_sid` string, required: wf_wf_… parent.
      - `version` integer, required: Per-workflow sequence, >=1 (max+1 at publish).
      - `steps` union[], required: Immutable snapshot of the step document at publish.
        - union
          - object
            - `type` 'linkedin_send_connection_request', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `message` string, nullable: Invite note - max 300 RENDERED chars (server-checked); null/omitted = blank invite. Template namespaces: {{people.*}} {{company.*}} {{record.<slug>}} {{ai.<key>}}; per-variable fallback {{record.slug|text}}.
            - `required_fields` object, nullable
              - …
          - object
            - `type` 'linkedin_send_message', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `message` string, required: Message template (same namespaces as all templates).
            - `required_fields` object, nullable
              - …
          - object
            - `type` 'linkedin_send_inmail', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `subject` string, nullable
            - `message` string, required
            - `required_fields` object, nullable
              - …
          - object
            - `type` 'linkedin_visit_profile', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
          - object
            - `type` 'linkedin_endorse_skills', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `skills_count` integer, required: Endorse the person's top N listed skills, 1..10.
          - object
            - `type` 'linkedin_withdraw_connection_request', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
          - object
            - `type` 'linkedin_like_latest_post', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
          - object
            - `type` 'linkedin_comment_latest_post', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `comment` string, required: Comment template on the person's latest post, 1..1250 rendered; same namespaces as message templates.
            - `required_fields` object, nullable
              - …
          - object
            - `type` 'email_send', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `subject` string, required
            - `body` string, required: HTML body template.
            - `required_fields` object, nullable
              - …
            - `email_field` string, nullable: Which address the send uses: any | work_email | personal_email; omitted or any = the best one.
            - `cc` string[], nullable: Up to 5 literal copy addresses.
            - `bcc` string[], nullable: Up to 5 literal blind-copy addresses.
            - `attachments` object[], nullable: Up to 5 library files ({sid: wf_at_…, name}) sent with the email.
              - …
            - `reply_in_thread` boolean, nullable
            - `track_opens` boolean, nullable
            - `track_clicks` boolean, nullable
            - `unsubscribe_link` boolean, nullable
          - object
            - `type` 'wait', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `for` string, required: Duration string: ^[1-9][0-9]*(m|h|d)$ - e.g. "45m", "12h", "14d". Publish lints the range 1m..365d.
          - object
            - `type` 'wait_for_event', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `event` 'linkedin_connection_request_accepted' | 'email_opened' | 'email_link_clicked', required: Event observed via the activities ingest.
            - `timeout` object, required: Required since 2026-10-11: a wait always has a time limit. Bundled: a timeout path cannot exist without a duration.
              - …
          - object
            - `type` 'condition', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `branches` object[], required: 1..9 branches, evaluated in order, first match wins.
              - …
            - `else` 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
            - `type` 'ab_test', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `branches` object[], required: 2..9 weighted arms; the record is assigned deterministically by hashing its sid into the weight distribution. No `else`.
              - …
          - object
            - `type` 'end', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
          - object
            - `type` 'api_call', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', required
            - `url` string, required: https-only (publish check); a template - variables may stand in the path and the query.
            - `headers` object, nullable
            - `query` object, nullable
            - `body` string, nullable: JSON body template, variables inside quotes; null = the fixed payload; must be null on GET / DELETE.
            - `provider_connection_sid` string, nullable: A `custom` provider connection whose auth the integration platform injects at call time; null = no auth.
            - `timeout_secs` integer, nullable
            - `retry_on_error` boolean, nullable
            - `remove_empty_values` boolean, nullable
            - `output_key` string, nullable: The answer's name for later steps (2026-10-11): a slug, unique within the version with the ask_ai keys; later steps read {{api.<output_key>.<path>}} and condition leaves filter on api.<output_key>.<path>; null = the answer is not kept.
          - object
            - `type` 'ask_ai', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `prompt` string, required: Same variable namespaces as message templates.
            - `output_key` string, required: Slug, unique within the version (publish check) - later steps read {{ai.<output_key>}} / condition leaves on ai.<output_key>.
            - `preset_sid` string, nullable: gtm-api preset for the AI call; null = team default.
          - object
            - `type` 'manual_task', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `title` string, required
            - `instructions` string, nullable: Rendered into the human todo; the journey waits until the task is completed.
          - object
            - `type` 'move_to_workflow', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `target_workflow_sid` string, required: Enroll the person into this campaign (wf_wf_…); reuses the existing record per the re-entry policy. THIS record then completes - terminal-for-this-record.
            - `mode` 'from_start' | 'from_current', nullable: Re-entry mode when the person is ALREADY in the target (workflow_records.md, Re-entry): from_start (default; reset to the entry step) | from_current (resume the pinned step, else the entry step). A re-entry is loop-guarded at re_entered_count 10.
          - object
            - `type` 'add_tag', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `tag_sids` string[], required: 1..20 CRM tags (cr_tg_…) attached to the person mid-flow, then falls through.
          - object
            - `type` 'remove_tag', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `tag_sids` string[], required: 1..20 CRM tags (cr_tg_…) detached from the person mid-flow, then falls through.
          - object
            - `type` 'set_field', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `field_slug` string, required: Playtable field slug to write (pipeline_stage is a scalar_select field).
            - `value` union, required: Scalar cell value (one cell; no arrays/objects); null clears the field.
              - …
          - object
            - `type` 'change_sender', required
            - `id` integer: Server-assigned (workflows.step_id_seq). OMIT on new steps, CARRY verbatim on kept steps; always present on read.
            - `name` string, nullable: Editor label.
            - `sender_profile_sid` string, nullable: Explicit forward-only re-pin (ub_sp_…). Exactly one of sender_profile_sid / rotation (publish check). NOT a restart-from-top.
            - `rotation` 'fair' | 'random' | 'new_sender' | 'prior_engagement', nullable: Re-pick the sender from the workflow pool by this strategy instead of a fixed sid. Exactly one of sender_profile_sid / rotation (publish check).
      - `steps_count` integer, required: Denormalized total step count (incl. nested).
      - `entry_step_id` integer, required: = steps[0].id - enrollment starts here.
      - `created_at` string, required: ISO 8601 UTC - publish moment.
    - `sources` object[]: WorkflowSourceDomain audience feeds (1:N, default 50, created_at ASC) - see ./workflow_sources.md.
      - `sid` string, required
    - `records_counts` object: WorkflowRecordCounts over { workflow_sid: parent.sid } - the full-scope enrolled/active/replied/done snapshot for the editor header (§4.17a) - see ./workflow_records.md.
      - `total_count` integer, required
      - `groups` object, required
        - `status` union, required: Journey funnel by WorkflowRecordStatusEnum: waiting_limit / blocked / active / waiting / paused_on_reply / paused_manual / paused_blocked / failed / completed / exited.
          - object
          - unknown[]
            - unknown
    - `forecast` object, nullable: How long the planned sends take at the current limits (TASK_EXECUTION_ARCHITECTURE.md §6.5, D41): the scheduled tasks per (sender profile, type) laid over the days ahead at the workflow's priority share of the sender profile's daily cap, closed days skipped. Computed on read from counts, never stored; rows held at the door are not in it.
      - `scheduled_count` integer, required: Send-class tasks scheduled on a sender profile.
      - `sender_profiles_count` integer, required
      - `days_to_drain` integer, nullable, required: Days from today until the last planned send leaves (1 = everything today); null when nothing is planned.
      - `drains_on` string, nullable, required: YYYY-MM-DD (UTC) of the last send; null past the horizon.
      - `over_horizon` boolean, required: The walk stopped at a year: read "over a year".
      - `buckets` object[], required
        - `sender_profile_sid` string, required
        - `type` string, required: The send-class task type.
        - `scheduled_count` integer, required
        - `daily_share` integer, required: This workflow's share of the (sender profile, type) daily cap, by priority (the sender profile pick's split).
        - `days_to_drain` integer, required
        - `over_horizon` boolean, required
      - `timeline` object[], required: The first 60 days; every bucket added up per day.
        - `day` string, required: YYYY-MM-DD, UTC.
        - `planned` integer, required
        - `by_type` object, required
  - `includes` string[], 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)
