---
title: "Complete workflow task"
method: POST
path: "/api/workflow-tasks/{sid}/complete"
tags: ["workflow_tasks"]
---

# Complete workflow task

`POST /api/workflow-tasks/{sid}/complete`

## Request

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

Close a manual task (pending to done); the note lands in result.note and the record advances. Any other type or status is 409; a repeat answers already_completed true. A question a play asked is answered with choice: remove ends the journey, keep lets it go on, move ends it and puts the person into workflow_sid, add puts an unenrolled person there. Skipping an automated step is cancel_workflow_task; a play's draft is approve_workflow_task.

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

## Path parameters

- `sid` string, required: Workflow task sid (wf_tk_…).

## Request body

- CompleteWorkflowTaskRequest: Request body of `complete_workflow_task`.
  - `note` string: Outcome note - stored in result.note and in the manual_task_completed activity payload.
  - `choice` 'remove' | 'keep' | 'move' | 'add': A play's question (act.do = ask): one of act.options. remove = the journey ends; keep = it goes on; move = it ends and the person enters workflow_sid (default: the act's target); add = a person in no journey enters workflow_sid. Required on a question.
  - `workflow_sid` string: The workflow a move or an add puts the person into, when the act names none.

## Response `200`

`action` success envelope.

- CompleteWorkflowTaskResponse
  - `success` true, required
  - `operation` 'action', required
  - `action` string, required: kebab-case verb; matches the route segment.
  - `item` object, nullable, required
    - `sid` string, required: Task sid (wf_tk_…) - one row per step of a journey, kept across retries; rides gtm's rows as client_reference.
    - `team_sid` string, required: Owning team (ts_tm_…, external - gs.service.id).
    - `workflow_sid` string, nullable, required: Parent workflow (wf_wf_…), denormalized for gates and filters. null on a play's task, unless it is a question about a journey (2026-10-14).
    - `workflow_version_sid` string, nullable, required: Pinned version (wf_vr_…); (workflow_version_sid, step_id) resolves the step definition. null on a play's task.
    - `workflow_record_sid` string, nullable, required: Owning record (wf_rc_…) - the person×workflow journey this task executes one step of. On a play's task only the journey its question is about, else null.
    - `people_sid` string, required: The person (cr_pp_…), denormalized for the person timeline.
    - `step_id` integer, nullable, required: Step id within the pinned version (small int, server-assigned, unique per workflow). null on a play's task.
    - `type` 'linkedin_send_connection_request' | 'linkedin_send_message' | 'linkedin_send_inmail' | 'linkedin_visit_profile' | 'linkedin_endorse_skills' | 'linkedin_withdraw_connection_request' | 'email_send' | 'wait' | 'wait_for_event' | 'condition' | 'api_call' | 'ask_ai' | 'manual_task' | 'linkedin_like_latest_post' | 'linkedin_comment_latest_post' | 'ab_test' | 'move_to_workflow' | 'add_tag' | 'remove_tag' | 'set_field' | 'change_sender' | 'run_column', required: Step kind copied at planning: 7 send-class (Messaging) + 6 orchestration-class values.
    - `attempt` integer, required: Which attempt the row is on (>= 1; the executor's own retries stop at 3, a human retry_workflow_task may go past it). A retry re-arms the SAME row: attempt+1, the outcome before it archived in previous_attempts, the sid kept.
    - `status` 'pending' | 'scheduled' | 'dispatched' | 'done' | 'failed' | 'skipped' | 'skipped_reply' | 'skipped_stoplisted' | 'cancelled' | 'expired', required: Execution state; terminal = done | failed | skipped | skipped_reply | skipped_stoplisted | cancelled | expired. skipped = nothing to do (error_code already_invited: an invite from the sender profile is pending; already_connected: the person is a connection) and the record moved on.
    - `initiator` 'workflow_record' | 'play' | 'user', required: workflow_record = one step of one record's journey (every task until 2026-10-14); play = a play's act on a fact: a draft waiting in Signals, a scheduled send, a question about a journey; user = a person's act by hand on an event of the feed (2026-10-15, act_on_activity): a Like, a comment, a message.
    - `play_sid` string, nullable, required: The play that bore the task (pt_pl_…); null on a journey's task.
    - `trigger_activity_sid` string, nullable, required: The fact a play's or a person's task answers (cr_ac_…: a birthday, a job change, a post, a visit, any event a person acted on by hand): the join from an event to the act waiting or done under it; a play never acts on one fact twice. On a journey's task only a wait the event woke, or (2026-10-15) a Like or comment naming the post its read picked; else null.
    - `act` object, nullable, required: A play's act frozen at birth (what the task does, the post or workflow it names, a question's options); null on a journey's task.
      - `do` 'send_message' | 'add_to_workflow' | 'like' | 'comment' | 'ask', required: What the task does: a LinkedIn message (variables_snapshot.message), the person entering target_workflow_sid, a reaction on post_urn, a comment under post_urn (variables_snapshot.comment), a question (options).
      - `about` 'birthday' | 'work_anniversary' | 'job_change' | 'post' | 'website_visit' | 'event', required: The fact kind the act answers; event = a person's act by hand on a kind no play answers (an accept, a profile view, a follow).
      - `actor_sid` string: A person's act by hand (initiator user, 2026-10-15): who pressed it, the caller's actor sid.
      - `target_workflow_sid` string: add_to_workflow, and a question whose move or add names one.
      - `post_urn` string: like, comment: the post the act goes to (an activity URN).
      - `reaction` string: like: the reaction as the network names it (like, celebrate, support, love, insightful, funny), the play's own or the one the AI picked from the post.
      - `options` string[]: ask: the choices complete_workflow_task takes. remove | keep | move about a journey the play paused; add for a person in no journey.
      - `suggested` 'remove' | 'keep' | 'move' | 'add': ask: the choice the play's own Do would have made (remove for Remove from every workflow, move for Move to a workflow), shown first; absent for pause-and-ask.
    - `sender_profile_sid` string, nullable, required: Sending identity (ub_sp_…) - send-class only; pinned at planning, re-stamped at dispatch. null = orchestration-class.
    - `assignee_user_sid` string, nullable, required: manual_task only: assignee user sid (external - gs.service.id, prefix TBD); fallback = sender profile assignee; null = team queue.
    - `scheduled_at` string, nullable, required: ISO 8601 UTC - when the dispatcher may act; doubles as the manual_task due hint. null for pending-born types.
    - `timeout_at` string, nullable, required: ISO 8601 UTC - wait_for_event only: give-up clock for the timeout edge.
    - `dispatched_at` string, nullable, required: ISO 8601 UTC - stamped on scheduled → dispatched.
    - `expires_at` string, nullable, required: ISO 8601 UTC - dispatched commands only; the executor refuses to START after it (send-class: dispatch+24h, ask_ai: +1h).
    - `finished_at` string, nullable, required: ISO 8601 UTC - stamped once on any terminal status.
    - `variables_snapshot` object, nullable, required: Resolved template variables frozen at dispatch (people.* / record.<slug> / ai.* keys) - send-class + ask_ai only.
    - `result` object, nullable, required: Terminal outcome payload, keys per type (message_sid, branch_id, next_step_id, output, note, …). A failure on the sender profile's browser adds error_cause (proxy_failed, linkedin_session, …) and cause_owner (client: the team fixes it; support: we do).
    - `error_code` string, nullable, required: Machine reason token when status = failed | expired (max 64), or skipped (already_invited, already_connected). send_unconfirmed: gtm-api could not tell for a day whether the send went out, so it may have reached the person (see retry_workflow_task).
    - `error_message` string, nullable, required: Human detail of the failure (max 1024); null on a skip.
    - `previous_attempts` object[], nullable, required: The outcomes this row was re-armed past, oldest first (a retry, the executor's own or a person's, keeps the row and archives the attempt here); null until the first retry.
      - `attempt` integer, required: The attempt number it was.
      - `status` 'pending' | 'scheduled' | 'dispatched' | 'done' | 'failed' | 'skipped' | 'skipped_reply' | 'skipped_stoplisted' | 'cancelled' | 'expired', required: How it ended: failed | expired | skipped_stoplisted (the retryable outcomes).
      - `error_code` string, nullable, required: Its reason token.
      - `error_message` string, nullable, required
      - `result` object, nullable, required: Its result payload, when it had one (an api_call's http_status; a browser failure's error_cause and cause_owner).
      - `dispatched_at` string, nullable, required: ISO 8601 UTC.
      - `finished_at` string, nullable, required: ISO 8601 UTC.
    - `created_at` string, required: ISO 8601 UTC - the planning moment.
    - `updated_at` string, required: ISO 8601 UTC.
    - `deleted_at` string, nullable, required: ISO 8601 UTC - set ONLY by the owning record's cascade (no delete verb on this entity); null = live.
  - `result` object, required
    - `already_completed` boolean, required: true = idempotent repeat on an already-done manual task (no side effects; activity is null).
    - `activity` object, nullable, required: Full manual_task_completed row (ActivityDomain - entities/activities.md; §4.12a embed); null on idempotent repeat and on a play's question.
    - `updated_workflow_record` object, nullable, required: The owning record AFTER the advance (WorkflowRecordDomain - entities/workflow_records.md): current_step_id / status show what happens next. A question about a journey: that record after the choice; null for a question about a person in no journey.
  - `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)
