Skip to main content

About

Full lifecycle control over workflow automations from your own code: list and inspect workflows, edit drafts, promote them live, pause and resume, trigger runs against records, control in-flight runs, read performance stats, and organize workflows into folders. You can also manage reusable content templates and apply a saved task or a whole task set to a contact. All operations go through the developer API gateway — one endpoint, called by operationId. If you haven’t set up a key yet, start with the Developer API Overview.
Groups, versions, and draftsA workflow is a group of versions. At most one version is ACTIVE (live) and at most one is a DRAFT being edited. Reads return the group id used by every other workflow operation; version-level operations take a version id from getWorkflow.

Operations

Reading workflows (READ scope)

Building and shipping (WRITE scope)

A task set can partially succeedAlways inspect workflowAutomationsMutation.applyTaskSet.failedStep. A step failure is returned in the payload (index, taskTemplateId, message), without a GraphQL error. Execution stops at that step and keeps tasks already created. Do not apply the whole set again with a new idempotency key to repair it: that can duplicate those tasks.

Reading workflow channels

workflowType has been removed from workflow responses. Channels are TEXT, EMAIL, both, or an empty list for automation with no messaging.
  • channels on getWorkflow reads the most recently published version, even if it is now paused or suspended. It falls back to the pending draft only when nothing has ever been published. Null means the graph could not be read; an empty list can also mean the group has no version.
  • channelsAcrossVersions includes every non-deleted version, including superseded versions and unpublished drafts. It is never null and is the field returned by both paginated workflow listings.
A workflow whose old version texted and whose latest publication only emails can therefore return ["TEXT", "EMAIL"] in a listing and ["EMAIL"] in getWorkflow.channels. Neither field proves messages are being sent: check status separately.

Authoring a graph

Use updateWorkflowGraph’s discovery detail for the current step vocabulary and configuration shapes for each workflow domain. Preserve the serialized shape returned by getWorkflowVersionGraph; a graph contains nodes, edges, triggers, and graph-level config. Omitting a top-level key preserves its stored value; sending triggers replaces that collection, so an existing trigger left out of the array is detached. A trigger needs only config. A text/email step without channel communication configuration gets the editor default; replace an UNSET sender before promotion. DEAL graphs can write a deal custom field, add a note, create a task or appointment linked to the deal, and enroll the deal in another DEAL workflow. send_webhook_action is available on CONTACT, PROPERTY, DEAL, and APPOINTMENT graphs. CONTACT graphs also support send_mail_action through the organization’s connected mail account; a refused mailing is recorded on the run and the run continues. Read discovery for each step’s required config. EVENT graphs support ai_conditional (Ask AI). Its config.context.event selects { mode: "whole" } for the entire webhook body or { mode: "fields", paths: ["call.outcome"] } for 1–25 accessor paths. An empty event selection is refused; it is not a selection of record ids.
Preserve redacted webhook secretsIn getWorkflowVersionGraph and createOrReturnWorkflowDraft, a send_webhook_action step’s config.auth.secret returns the reserved string __GOLIATH_WEBHOOK_SECRET_REDACTED__. Send that exact string back unchanged in updateWorkflowGraph to keep the stored secret. Replace it only when deliberately rotating the credential.The sentinel cannot supply a secret for a new step or a changed node id. A save without a matching stored secret is refused; re-read the graph and edit the fresh copy.
CONTACT, PROPERTY, and DEAL conditionals use their domain’s full saved-filter vocabulary: discover it with listFilterFieldNames, then request the fields you need with listFilterFields. Those discovery operations accept CONTACT, PROPERTY, and DEAL; their absence for another domain does not establish which conditions that domain can evaluate. Merge fields are exact and case-sensitive, and depend on the node’s rendering context. For example, a CONTACT text body can use {{firstName}} and {{contactPropertyAddress}}; {{contact.firstName}} is not a supported namespace. EVENT bodies use accessors into the webhook payload instead. Read the per-domain vocabulary in discovery before authoring a body.
Read draft validation before promotingAfter editing the draft, inspect workflowGroup.draftValidation on the updateWorkflowGraph response and fix its errors before calling promoteWorkflowDraft. The scan includes workflow-level email subjects and fallbackTextBody under SEND_FALLBACK. It also checks fallbackEmailSubject and fallbackEmailBody under SEND_FALLBACK when the effective primary subject or a body stored in the graph can miss a variable. By default that subject comes from the workflow; sendInSameThread: false uses the node’s subject instead.Referenced template bodies are still outside the scan. A template step’s fallbacks are not scanned solely because its external template body could miss a variable. Check those fields against the rendering context too; unknown tokens can reach recipients as literal braces.
config.exclusiveEnrollment: true stops the record’s runs in other workflows when it enters this one. It leaves runs from another version of this same workflow alone and has no effect on EVENT workflows. config.activation.triggerEligibleRecordsNow is retired and has no effect. Do not set it to enroll existing matches. Use triggerWorkflowRun for one record or enrollInWorkflow for a contact selection. A manual trigger is not the only way a workflow can receive records: steps in other workflows can enroll them. getDeletionImpact(targetKind: WORKFLOW_GROUP, targetId: ...) reports reference counts, not the names of feeding workflows; read their graphs to identify those references. To narrow what a workflow acts on, edit and promote a draft with the appropriate conditional; pausing alone does not change its criteria. CONTACT and APPOINTMENT graphs configure messaging under config.communicationConfig.text and .email; both can be present at once. Each channel has its own sender. The sending schedule belongs to the group, outside the graph. Set each required sender before promoting; UNSET fails draft validation. A text_action or email_action step can set config.senderOverride to replace the workflow’s sender for that step. Omit it to inherit the workflow sender. Text overrides support POINT_PERSON, PHONE with fromPhoneNumberId, or RANDOM_PHONE with fromPhoneNumberIds. Email overrides support POINT_PERSON, IDENTITY with fromEmail and fromClientId, or RANDOM_IDENTITY with identities. Both also accept UNSET, which must be configured before activation. LOCAL_PRESENCE is not a step override.

Sending schedule

Read getWorkflow.sendWindow and change it with updateWorkflowSendWindow. It applies to live runs at their next send, without republishing; a run already waiting for the old schedule’s next opening keeps that wait and checks again when it wakes. createWorkflow.sendWindow seeds it on creation. updateWorkflowGraph cannot change it and refuses a graph carrying a different sending schedule. For sendWindow.timezoneMode, choose explicitly: Send timezone as an IANA zone, such as America/New_York, with days and optional timezoneMode. An omitted mode uses BUSINESS. Each entry in the days array contains day: "MON" through "SUN" and its own intervals of { start: { hour, minute }, end: { hour, minute } }. Both time components are required. An absent day is closed; intervals cannot cross midnight, and end: { hour: 0, minute: 0 } means midnight at the end of that day. The older daysOfWeek/startTime/endTime shape is still accepted, but reads return the per-day shape. An unset sending schedule defaults to Monday–Friday, 09:00–19:00 Eastern. The configured window is final for text and email; no extra lead-local quiet hours are added. A window with no days or hours is refused; send null through updateWorkflowSendWindow to restore the default.

Enrollment and exit behavior

config.triggerWindow controls enrollment and always uses its own timezone. It accepts RECIPIENT or CONFIGURED as timezoneMode without changing that behavior; BUSINESS is rejected there. CONTACT exit conditions can also stop a run when a human, AI employee, or another workflow sends to the contact: use outbound_from_human, outbound_from_ai_employee, or outbound_from_other_workflow in config.exitConditions.exitConditionTypes. A workflow’s own messages do not trigger those exits. deals_closed ends a run enrolled before the contact’s last live deal reached a dead stage (terminal, or named like Dead / Lost / Not interested). It leaves later win-back runs alone. A contact with no live deals does not meet this condition.

Custom and system workflows

Every domain supports custom workflows, including PROPERTY and APPOINTMENT. For a custom PROPERTY workflow, leave systemCategory null; it starts blank and can run manually, on property signals, or from custom data pipelines. To use the skip-trace starter instead, send templateId: "property_signal_skip_trace" with systemCategory: "PROPERTY_SIGNAL_SKIP_TRACE"; it carries the template’s validation rules and daily skip-trace cap. Before promoting, configure a MUTATION trigger whose filter names property signal types in a flat OR group and a MATCH trigger with an AND root containing a top-level spicyLeadScore minimum for the buy box. Read draftValidation for missing requirements. Any non-null category requires a template id. Stored categories APPOINTMENT_REMINDER and INTEGRATION_LEAD_HANDLING are not offered for creation. APPOINTMENT workflow creation is available to members under their normal workflow permissions. Integration lead-handling workflows are created on integration connection; their steps cannot be added, removed, or rewired through graph editing.

Skip-trace run budget (ADMIN scope)

Each run spends a skip-trace credit, so raising the cap enables real spending and requires a team admin’s ADMIN key. Confirm the old and new cap before changing it.

Templates and task sets

The Content Templates API covers these operations in the workflows domain:
  • Read with getContentTemplate, listContentTemplates, and listContentTemplateFolders (READ). Use type: TASK or type: TASK_SET to find templates for the apply operations above.
  • Write with createContentTemplate, updateContentTemplate, and deleteContentTemplate (WRITE). Deleting a referenced template requires a dependency preview and replacement.
  • Organize with moveContentTemplateToFolder, createContentTemplateFolder, renameContentTemplateFolder, and deleteContentTemplateFolder (WRITE).
applyTaskTemplate.overrides accepts title, description, endDate, timezone, taskType, autoCompleteOnNoteAdded, and autoCompleteOnReassignment. Send timezone with an endDate override; otherwise the task retains the template’s timezone. Unspecified values and supporting-file links still come from the template. For a task set, each step’s own timing overrides the referenced template’s timing, and all offsets resolve from one shared starting instant. See the Tasks API to manage the resulting tasks.

Pausing and resuming (WRITE scope)

Run control (WRITE scope)

enrollInWorkflow accepts one selection form at a time, never ids combined with a filter. A filter requires selectCount; there is no select-all. excludeIds removes contacts from that first-N window, which uses default filter order. Use countContacts to size the audience first. The maximum is 20,000 ids or selected contacts per call; split larger audiences with exclusions or narrower filters. Confirm the workflow and count before submitting: enrollment can start texts or emails under its sending rules. For listWorkflowRuns, stoppedReason explains STOPPED and NOT_ENROLLED outcomes; the latter never entered the workflow. PAUSED runs can carry pausedReason and a carrier sendFailureCode. Only a PENDING run’s queuedSend is current: reasons include DAILY_CAP, SEND_WINDOW, WINDOW_LIMIT, and OUT_OF_SMS_CREDITS, with nextAttemptAt for its next attempt. FAILED runs have no reason field, and older stopped runs can lack a recorded reason. Report those gaps instead of inferring a cause.

Folders (READ + WRITE scope)

Folders are a tree: each has an id, a name, a parentFolderId (null at the root) and a displayOrder. Every folder operation addresses folders by id.
Organization guardWorkflow operations are org-guarded on the ids you pass — a workflowId, folderId, or content-template id from another organization returns 403 forbidden. Listing and creating happen under your own org. See Authorization for the model.

Example

Trigger the live version of a workflow against one contact:
Full variable schemas, response shapes, and worked examples for every operation are available from the discovery endpoint.