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 byoperationId. 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)
Reading workflow channels
workflowType has been removed from workflow responses. Channels are
TEXT, EMAIL, both, or an empty list for automation with no messaging.
channelsongetWorkflowreads 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.channelsAcrossVersionsincludes every non-deleted version, including superseded versions and unpublished drafts. It is never null and is the field returned by both paginated workflow listings.
["TEXT", "EMAIL"] in a listing and ["EMAIL"] in
getWorkflow.channels. Neither field proves messages are being sent: check
status separately.
Authoring a graph
UseupdateWorkflowGraph’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.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
ReadgetWorkflow.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, leavesystemCategory 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, andlistContentTemplateFolders(READ). Usetype: TASKortype: TASK_SETto find templates for the apply operations above. - Write with
createContentTemplate,updateContentTemplate, anddeleteContentTemplate(WRITE). Deleting a referenced template requires a dependency preview and replacement. - Organize with
moveContentTemplateToFolder,createContentTemplateFolder,renameContentTemplateFolder, anddeleteContentTemplateFolder(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.