---
title: "Search workflows"
method: POST
path: "/api/workflows/search"
tags: ["workflows"]
---

# Search workflows

`POST /api/workflows/search`

## Request

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

List the team's outreach campaigns - multichannel LinkedIn + email sequence containers. Filter by status; q = LIKE over name + description; include records_counts for the per-campaign enrolled/active funnel, sources for audiences, published_version for the live step graph (heavy - full steps JSON). counts.groups.status rides every response; page_size:0 = status dashboard. Default UI scope excludes archived (client-side). Who is enrolled / stuck where → workflow-records search.

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

## Request body

- SearchWorkflowsRequest: Request body of `search_workflows`.
  - `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.
    - `playtable_sid` object: The home playtable.
      - `eq` string
      - `in` string[]
    - `sid` object: Prefix-validated wf_wf_ sid(s).
      - `eq` string: Workflow sid (wf_wf_…).
      - `in` string[]
    - `status` object: Default UI scope excludes archived client-side.
      - `eq` 'draft' | 'active' | 'paused' | 'archived'
      - `ne` 'draft' | 'active' | 'paused' | 'archived'
      - `in` string[]
      - `nin` string[]
    - `published_version_sid` object: is_null:true = never published.
      - `eq` string
      - `is_null` boolean
    - `agent_sid` object: The AI SDR the workflow picked (ub_ag_…); is_null:true = the workflows on their table's pick.
      - `eq` string
      - `in` string[]
      - `is_null` boolean
    - `q` string: Free text over name + description (MySQL LIKE, prefix/infix).
    - `created_at` object: ISO 8601 range; combine bounds in one object.
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
    - `updated_at` object: "Recently edited" scans; ISO 8601 range.
      - `gte` string
      - `lte` string
      - `gt` string
      - `lt` string
    - `deleted_at` object: Default scope is { is_null: true } (live rows).
      - `gte` string
      - `lte` string
      - `is_null` boolean
  - `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' | 'updated_at' | 'name', required: The column to order by.
    - `direction` 'asc' | 'desc': asc or desc; default desc.
  - `page_size` integer: 0..500, default 50; 0 = counts only, 1 = the first row.
  - `cursor` string, nullable: pagination.next_cursor of the previous page.

## Response `200`

`search` success envelope.

- SearchWorkflowsResponse
  - `success` true, required
  - `operation` 'search', required
  - `items` object[], 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
              - …
          - 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).
      - `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
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
            - object
              - …
        - `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[]
              - …
      - `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
  - `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: Workflow rows matching the filter scope.
    - `groups` object, required
      - `status` union, required: Campaign distribution by status (feeds list badges); absent key = zero (PHP empty map serialises as []).
        - object
          - `draft` integer
          - `active` integer
          - `paused` integer
          - `archived` integer
        - 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)
