---
title: "Search workflow versions"
method: POST
path: "/api/workflow-versions/search"
tags: ["workflow_versions"]
---

# Search workflow versions

`POST /api/workflow-versions/search`

## Request

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

Immutable published snapshots of a workflow's step graph - one row per publish, never edited or deleted. Version history: filter.workflow_sid, sort version desc; items carry the FULL steps JSON - keep page_size small (history rarely exceeds 20). include workflow_records_counts = how many records are pinned to each version and in which statuses (the drift check after a republish). page_size:0 = counts only. To change steps edit the workflow's draft_steps and publish.

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

## Request body

- SearchWorkflowVersionsRequest: Request body of `search_workflow_versions`.
  - `filter` object: Field filters, ANDed: {field: {eq | ne | in | nin | gte | lte | gt | lt | is_null}}; q inside it is free text. Omit for every row.
    - `sid` object: Version sid(s), wf_vr_ prefix (prefix-validated).
      - `eq` string: Workflow version sid (wf_vr_…).
      - `in` string[]
    - `workflow_sid` object: History scope - versions of one workflow (wf_wf_…).
      - `eq` string: Parent workflow sid (wf_wf_…).
      - `in` string[]
    - `version` object: Per-workflow sequence number ("versions after v5"); meaningful with a workflow_sid filter.
      - `eq` integer
      - `gte` integer
      - `lte` integer
      - `gt` integer
      - `lt` integer
    - `created_at` object: Publish moment, ISO 8601 range ("what was live last week"); combine bounds in one object.
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
  - `include` string[]: Relations to attach to every item; omit for the bare rows.
  - `sort` object: One column and its direction; omit for the default order.
    - `field` 'created_at' | 'version', required: The column to order by.
    - `direction` 'asc' | 'desc': asc or desc; default desc.
  - `page_size` integer: A version row carries the whole step tree, so a page is capped at 100 here (the platform default is 500).
  - `cursor` string, nullable: pagination.next_cursor of the previous page.

## Response `200`

`search` success envelope.

- SearchWorkflowVersionsResponse
  - `success` true, required
  - `operation` 'search', required
  - `items` object[], required
    - `item` object, required
      - `sid` string, required: Version sid (wf_vr_…).
      - `team_sid` string, required: Owning team (ts_tm_…, external - gs.service.id).
      - `workflow_sid` string, required: Parent workflow (wf_wf_…) - written once at publish, never repointed; see ./workflows.md.
      - `version` integer, required: Per-workflow sequence, max+1 at publish (serialized by the publish FOR UPDATE on the workflow row).
      - `steps` union[], required: Immutable chain-and-blocks snapshot: array order = execution order, entry = steps[0], root-array exhaustion completes the record; 1..100 steps TOTAL incl. nested, <=512 KB serialized. Step ids are stable across versions.
        - union
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_send_connection_request', required
            - `message` string, nullable: Invite note template; rendered note <=300 chars - enforced at publish after variable resolution (research: max 300 RENDERED). null/omitted = blank invite.
            - `required_fields` object, nullable: When a value the note needs is empty: refs required on top of the text + on_missing policy.
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_send_message', required
            - `message` string, required: Message template ({{people.*}} / {{company.*}} / {{record.<slug>}} / {{ai.<key>}}); rendered at task time.
            - `required_fields` object, nullable: When a value the text needs is empty: refs required on top of the text + on_missing policy.
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_send_inmail', required
            - `subject` string, nullable: InMail subject template, max 200.
            - `message` string, required: InMail body template, 1..8000.
            - `required_fields` object, nullable: When a value the text needs is empty: refs required on top of the text + on_missing policy.
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_visit_profile', required
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_endorse_skills', required
            - `skills_count` integer, required: Endorse the person's top N listed skills, 1..10 (named-skill targeting deferred - count-only in v1).
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_withdraw_connection_request', required
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_like_latest_post', required
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'linkedin_comment_latest_post', required
            - `comment` string, required: Comment template posted on the person's latest post, 1..1250 rendered; same variable namespaces as message templates.
            - `required_fields` object, nullable: When a value the comment needs is empty: refs required on top of the text + on_missing policy.
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'email_send', required
            - `subject` string, required: Email subject template, 1..255.
            - `body` string, required: Email body template, HTML, 1..100000.
            - `required_fields` object, nullable: When a value the text needs is empty: refs required on top of the text + on_missing policy.
              - …
            - `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 (no templates: a copy goes to a colleague).
            - `bcc` string[], nullable: Up to 5 literal blind-copy addresses.
            - `attachments` object[], nullable: Up to 5 library files sent with the email; the executor reads the bytes at send time.
              - …
            - `reply_in_thread` boolean, nullable: True threads the send onto the prior email exchange.
            - `track_opens` boolean, nullable
            - `track_clicks` boolean, nullable
            - `unsubscribe_link` boolean, nullable
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `type` 'wait', required
            - `for` string, required: How long to pause the record before the next step - "45m" / "12h" / "14d" (replaces v3 timer.duration_seconds).
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `type` 'wait_for_event', required
            - `event` 'linkedin_connection_request_accepted' | 'email_opened' | 'email_link_clicked', required: Event a wait_for_event step blocks on (observed via activities ingest).
            - `timeout` object, required: Required since 2026-10-11: a wait always has a time limit (a wait with no end is a stuck journey); steps: [] = on timeout, continue as if the event fired. The reply-pause machinery still applies (KNOWLEDGE §4.9).
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'condition', required
            - `branches` object[], required: 1..9, evaluated in order, first match wins; the fired branch id lands on the condition task result (workflow_tasks.md).
              - …
            - `else` union[], required: REQUIRED no-match path (may be []); falls through unless it ends with `end`.
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'ab_test', required
            - `branches` object[], required: 2..9 weighted arms; the record is assigned to one deterministically by hashing its sid into the weight distribution (workflow_versions.md). No `else` - weights cover the whole distribution.
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `type` 'end', required
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'api_call', required
            - `method` 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', required: The request method. POST / PUT / PATCH carry the JSON body; GET / DELETE must publish with body null.
            - `url` string, required: https-only (publish check), max 2048. A template: {{people.*}} / {{company.*}} / {{record.<slug>}} / {{ai.<key>}} may stand in the path and the query and are URL-encoded where they stand.
            - `headers` object, nullable: Up to 20 custom request headers; values are templates. Auth comes from provider_connection_sid, never from a literal here.
            - `query` object, nullable: Up to 20 query parameters appended to the URL; values are templates.
            - `body` string, nullable: A JSON body template with the variables INSIDE quotes ("{{people.full_name}}"), stood in JSON-safely at call time. null = the fixed payload {event, workflow_sid, workflow_record_sid, workflow_task_sid, step_id, people_sid, context}.
            - `provider_connection_sid` string, nullable: A `custom` provider connection of the team (provider-connections.search filter provider=custom): the integration platform injects its auth (bearer / basic / header / query) when the call fires. null = no auth (a public API).
            - `timeout_secs` integer, nullable: The call budget in seconds; null = 15.
            - `retry_on_error` boolean, nullable: null/true: a 5xx, a 429, a timeout or an unreachable target earns a fresh attempt (3 in all, with backoff); false: the record fails on the first error. A 4xx / auth refusal never retries.
            - `remove_empty_values` boolean, nullable: true: empty rendered values leave the query and the body, so they never overwrite the receiver's data; null/false: sent as empty strings.
            - `output_key` string, nullable: The answer's name for the steps after this one (2026-10-11): a slug, unique within the version together with the ask_ai keys (publish check). Later steps read {{api.<output_key>.<path>}} (a dotted path into the decoded JSON answer) and condition leaves filter on api.<output_key>.<path>; the answer is kept on the done task row, capped at 64 KB. null = the answer is not kept.
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'ask_ai', required
            - `prompt` string, required: Prompt template, 1..8000 - same variable namespaces as message templates.
            - `output_key` string, required: Slug 1..64, unique within the version (publish check); later steps read {{ai.<output_key>}} and condition leaves filter on ai.<output_key>. Outputs live on the completed task row, never on records.
            - `preset_sid` string, nullable: gtm-api preset for the AI call (external sid, opaque); null/omitted = team default (service KNOWLEDGE §1).
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `type` 'manual_task', required
            - `title` string, required: Todo title template, 1..255.
            - `instructions` string, nullable: Rendered into the human todo, max 4000; the journey waits until the task is completed (workflow_tasks.md manual queue).
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'move_to_workflow', required
            - `target_workflow_sid` string, required: Enroll the person into this campaign (wf_wf_…); reuses the existing record per the re-entry policy (workflow_records.md). 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
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'add_tag', required
            - `tag_sids` string[], required: 1..20 CRM tags (cr_tg_…) attached to the person mid-flow, then falls through (tags.md).
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'remove_tag', required
            - `tag_sids` string[], required: 1..20 CRM tags (cr_tg_…) detached from the person mid-flow, then falls through.
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'set_field', required
            - `field_slug` string, required: Playtable field slug to write (pipeline_stage is a scalar_select field - service KNOWLEDGE §3).
            - `value` union, required: Scalar cell value written for the person (one cell; no arrays/objects); null clears the field (playtable_field_values.md).
              - …
          - object
            - `id` integer, required: Server-assigned small int, unique across the whole document (steps + branch ids); immutable, never reused; user-facing in the editor tree.
            - `name` string, nullable: Editor label.
            - `delay` string, nullable: How long the record waits at this step before it runs: "45m" / "12h" / "2d"; null/omitted = runs as soon as it arrives. Not allowed on wait (use `for`), wait_for_event, manual_task, end.
            - `type` 'change_sender', required
            - `sender_profile_sid` string, nullable: Explicit forward-only re-pin of workflow_records.sender_profile_sid (ub_sp_…). Exactly one of the three shapes (publish check).
            - `rotation` 'fair' | 'random' | 'new_sender' | 'prior_engagement', nullable: Re-pick the sender from the workflow pool by this strategy instead of a fixed sid, forward only. Exactly one of the three shapes (publish check).
            - `rotations` object[], nullable: The legacy gs_rotate_sender_profile list (D45): each pass through the node consumes the next entry and RESTARTS the journey from its entry step with its sender profile (touches start over; workflow_records.rotations_done is the cursor); past the end the journey continues. Exactly one of the three shapes (publish check).
              - …
      - `steps_count` integer, required: Denormalized total step count (incl. nested) at insert - lets lists/events skip the JSON.
      - `entry_step_id` integer, required: = steps[0].id, denormalized at publish; every enrollment starts here.
      - `created_by` object, required: The PUBLISHER - actor of workflows.publish; answers "who shipped v7".
        - `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: ISO 8601 UTC - the publish moment. No updated_at: the row never changes.
    - `included` object, required
      - `workflow` object: Parent workflow (WorkflowDomain - entities/workflows.md): name/status + published_version_sid to check whether THIS row is the live pointer.
        - `sid` string, required
        - `name` string, required
        - `status` string, required
        - `published_version_sid` string, nullable, required
      - `workflow_records_counts` object: Parent-include counts (§4.17) computed against {workflow_version_sid: parent.sid}: how many records are pinned to this version and in which statuses, without loading rows.
        - `total_count` integer, required: Records pinned to this version.
        - `groups` object, required: Axes per WorkflowRecordGroupableFieldsEnum (canonical shape - entities/workflow_records.md). PHP empty map serialises as [].
          - `status` union, required: Funnel by WorkflowRecordStatusEnum - non-terminal buckets = "who is still running this version".
            - object
            - unknown[]
              - …
  - `pagination` object, required
    - `next_cursor` string, nullable, required
    - `has_more` boolean, required
    - `total_count` integer, nullable, required
  - `applied_filters` 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.
  - `counts` object
    - `total_count` integer, required: Versions matching the filter - full filter scope, not the page.
    - `groups` object, required: One axis per WorkflowVersionGroupableFieldsEnum value (§4.17). PHP empty map serialises as [].
      - `workflow_sid` union, required: Flat {wf_wf_ sid: version count} - versions-per-workflow distribution; backed by (team_sid, workflow_sid, version DESC).
        - object
        - unknown[]
          - unknown

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