---
title: "Test an API call step"
method: POST
path: "/api/workflows/test-api-call"
tags: ["workflows"]
---

# Test an API call step

`POST /api/workflows/test-api-call`

## Request

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

Fire ONE api_call step for ONE real person of the team, for real, and return both halves: result.request = the wire request as it went out (method, URL with the variables rendered and the query appended, headers, body; before the connection's auth was injected) and result.status / http_status / response / error_code / error_message = what the target answered. The step is the draft node verbatim (ids optional, nothing saved); {{ai.<key>}} has no record to read and renders empty (or its fallback). A refusing target is status failed in a 200, never an error envelope; the error envelope is reserved for an incomplete node (422, keyed step.<field>) or an unknown person (404). Throttled 10/min. Use it before publish to see exactly what a journey would send.

Contract:
- MCP tool `test_api_call_workflow`, registry package `mcp.playtables/workflows`, mount `playtables.workflows`.
- Operation `action`, response envelope `action`.
- Flags: none.

## Request body

- TestApiCallWorkflowRequest: Request body of `test_api_call_workflow`.
  - `step` union, required
    - 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).
  - `people_sid` string, required: A person of the team; the templates render against their row.

## Response `200`

`action` success envelope.

- TestApiCallWorkflowResponse
  - `success` true, required
  - `operation` 'action', required
  - `action` 'test-api-call', required
  - `item` unknown, required
  - `result` object, required
    - `request` object, required
      - `method` string, required
      - `url` string, required
      - `headers` object, required
      - `body` unknown
      - `provider_connection_sid` string, nullable, required
      - `timeout_secs` integer, required
    - `status` 'success' | 'failed', required
    - `http_status` integer, nullable, required
    - `response` unknown
    - `error_code` string, nullable, required
    - `error_message` string, nullable, required
    - `provider_call_sid` string, nullable, required
  - `meta` object

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