GoliathDevelopersGet an API key
Guides / Workflows API

Workflows API

Build workflow drafts, set live sending schedules, enroll contacts in bulk, and manage runs and templates through the Goliath developer API

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 Workflow Tables, example payloads for event workflows, reusable content templates and library files, 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.

Operations

Reading workflows (READ scope)

operationId What it does
listWorkflows List the organization’s workflow automations (paginated, non-archived by default). Narrow by status, domain, name (any-case search) and triggerType; names are not unique, so read every page before calling one row the only match. Returns the group id and channelsAcrossVersions, including channels configured in superseded versions and unpublished drafts.
getWorkflow Fetch one workflow by group id — status, versions, live version, pending draft, draft validation, channels, channelsAcrossVersions, and the group’s live sendWindow. The two channel fields describe different version sets; see below.
getWorkflowVersionGraph Fetch one workflow version including its node/edge graph and configured channels. Webhook authentication secrets are redacted; preserve the returned sentinel when editing.
listWorkflowRuns Page runs of one workflow version with limit (default 20, cap 34) and offset, optionally filtered by status (including NOT_ENROLLED). Returns run ids, stoppedReason, pausedReason, sendFailureCode, and queuedSend with reason and next attempt time.
getWorkflowStats Performance summary for one workflow (by group id) — run counts by outcome, messages sent, reply rate + sentiment split, bounce rate, last run.
getWorkflowVersionStats The same summary scoped to one version, plus sendingCapacity (queuedRuns, nextAttemptAt, reason) for sends waiting on capacity. Null means none are queued.
listWorkflowPerformance Scoreboard — the org’s workflows each with headline stats (runs, active, reply rate, last run) and channelsAcrossVersions.
getWorkflowThrottleUsage Daily send-limit usage for a throttle-backed workflow (usedToday vs dailyCap + history). Null for workflows with no throttle.

Building and shipping (WRITE scope)

operationId What it does
createWorkflow Create a group and its initial DRAFT. Requires name and workflowDomain (CONTACT, DEAL, PROPERTY, APPOINTMENT, or EVENT); optionally seed from a templateId and set the group’s sendWindow. Channels come from the graph. The retired workflowType variable is rejected with 400 unknown_variable.
createOrReturnWorkflowDraft Idempotent “edit” — return the existing DRAFT, or clone the live or most recently paused version into a fresh DRAFT. Webhook authentication secrets are redacted.
updateWorkflowGraph Replace a version’s node/edge graph and triggers — read the graph via getWorkflowVersionGraph, then send back a modified copy. Edit the DRAFT from createOrReturnWorkflowDraft; the response includes workflowGroup.draftValidation (isValid, errors). PAUSED versions are also writable, but that validation still describes the group’s draft, not the paused version you edited. To change only part of a graph, use patchWorkflowGraph. Pass expectedUpdatedAt (the automation’s updatedAt as you read it) so a concurrent save fails with CONFLICT instead of being overwritten. A save that turns off text screening or the stop-on-reply exit is refused until you resend it with acknowledgeGraphWeakening: true, which you set only after the person agreed.
patchWorkflowGraph Edit a version’s graph by sending only the change: graphPatch is a list of ops applied in order, all or nothing — updateNode, addNode (after a node or onEdge), removeNode, addEdge, updateEdge, removeEdge, updateConfig, addTrigger, updateTrigger, removeTrigger. Node and edge ids change on every save, so patch again from the response’s graph. Takes expectedUpdatedAt and acknowledgeGraphWeakening like updateWorkflowGraph.
renameWorkflow Rename a workflow version and optionally update its description.
promoteWorkflowDraft Promote the current DRAFT to ACTIVE (live) and pause the previously-active version. inFlightRunsStrategy decides what happens to that version’s in-flight runs: MIGRATE (the default) moves them onto the new version — a run sitting on a step the draft removed or replaced finishes, and runs held by a workflow pause wake — while PAUSE parks them on the old version instead. pauseInFlightRuns is no longer accepted.
discardWorkflowDraft Discard the current DRAFT, if any. Idempotent.
restoreWorkflowVersionAsDraft Roll back: copy an older, paused version (workflowAutomationId from getWorkflow.versions) into the DRAFT, then publish it with promoteWorkflowDraft. Replaces the current draft; nothing goes live. The most recently published version is refused — resume it with resumeWorkflowVersion.
rewriteWorkflowDraftTextStep The publish dialog’s “Rewrite for me”: reword one DRAFT text step to lower its carrier-filter risk. Pass a getWorkflow.draftValidation.carrierRisk row’s nodeId and its body as expectedBody (medium or high band only). Returns REWRITTEN, NO_BETTER_REWRITE or STEP_CHANGED. Never publishes.
duplicateWorkflow Duplicate a workflow into a fresh, non-running group with the given name, preserving its system category and returning channels. Appointment reminders, integration lead-handling workflows, and AI-employee event routers cannot be duplicated. Create reminders through the Appointments API.
updateWorkflowPriority Set a workflow’s priority against the organization’s other workflows: 1.25 Low, 1.5 Normal, 1.75 High; any other value is refused. Applies immediately, to a live workflow too.
listWorkflowGraphRevisions A workflow’s save history (by group id), newest first: when, who, the version written, and what changed against the previous save. limit defaults to 25, capped at 100. Requires READ.
previewWorkflowAiText Preview what an AI text step’s systemPrompt would write to 1 to 5 contactIds, through the real generation path. Each result carries generatedText or a declineReason. Sends and saves nothing; it makes one model call per contact.
deleteWorkflow Archive the group, pause the active version, and permanently stop its in-flight runs. AI-employee event routers cannot be deleted. Confirm before deleting; restoring the workflow does not restore those runs.
restoreWorkflow Undo a group’s archive so it can be edited again. Does not activate versions or restart stopped runs; use resumeWorkflow when ready. EVENT workflows and appointment reminders cannot be restored this way.
updateWorkflowSendWindow Change the group’s live sending schedule immediately, without a draft or promotion. Every run reads it on its next send, regardless of version. Explicit null restores the platform default.
applyTaskTemplate Apply one TASK template to a contact, creating a task assigned to the acting user and linked to any supplied dealIds. Renders merge fields and spintax; resolves relative timing from now. Optional overrides change this task without editing the template.
applyTaskSet Apply a TASK_SET to a contact in one call. Creates tasks in due order, assigned to the acting user and linked to the contact and optional dealIds. Returns created tasks plus failedStep if a step fails; earlier tasks are kept.

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.

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.

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:

Mode Clock used
BUSINESS The organization’s current timezone, falling back to New York when unset.
RECIPIENT The text recipient’s phone-derived timezone; email and numbers without a derivable zone use the organization’s clock.
CONFIGURED The window’s explicit timezone, independent of later organization changes.

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)

operationId What it does
setSkipTraceDailyCap Set the organization’s shared daily cap for all PROPERTY_SIGNAL_SKIP_TRACE workflows. dailyCap must be a whole number from 1 to 1,000,000,000. Read the current cap and usage with getWorkflowThrottleUsage; a raise reaches workflows already capped today within a few minutes.

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:

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)

operationId What it does
pauseWorkflow Pause the ACTIVE version — no new runs. pauseInFlightRuns: true also pauses runs already in flight; false lets them finish. AI-employee event routers cannot be paused.
resumeWorkflow Resume the most-recently-paused version and wake runs paused by the workflow’s pause, continuing at their saved step. Runs paused by hand, a publication, failed send, or reply stay paused. If a version is already active, its workflow-paused runs are still woken.
resumeWorkflowVersion Resume the most recently published version if it is PAUSED; pauses an active sibling first. A superseded version is refused. Restoring an older version as a draft and republishing it happens in the app’s version history.
resumeRunsPausedWithWorkflow Wake the runs a workflow pause parked, on a workflow that is live again; their queued messages go out. It never turns the workflow itself on — resume or promote it first. Confirm before calling: this is a live send path.

Run control (WRITE scope)

operationId What it does
triggerWorkflowRun Manually start one run of the ACTIVE version against a record (contact/property/deal/task id per the workflow’s domain).
enrollInWorkflow Bulk-enroll contacts into a CONTACT workflow’s ACTIVE version. Pass contactIds, or a saved filterId / inline filterTree with required selectCount: N. Async: returns mode and bulkTaskId; poll getBulkTask. See selection rules below.
pauseWorkflowRun Pause one in-flight run (PENDING/RUNNING → PAUSED).
resumeWorkflowRun Resume one paused run (PAUSED → PENDING).
stopWorkflowRun Permanently stop one PENDING, RUNNING, or PAUSED run, cancelling queued sends. Already STOPPED is a no-op; COMPLETED, FAILED, and NOT_ENROLLED are refused.
bulkPauseWorkflowRuns Pause many runs at once by explicit id (cap 500); ineligible ids are counted in skippedCount.
bulkResumeWorkflowRuns Resume many paused runs at once by explicit id (cap 500).

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.

Workflow Tables (READ + WRITE scope)

A Workflow Table is a lookup a run reads values from: each row is matched on a key (keyKind such as COUNTY_FIPS, STATE, ZIP or FIELD_PATH, with optional fallback levels), and each column has a tokenKey a message uses as a merge field. A DEFAULT row answers when no key matches.

operationId Scope What it does
listWorkflowTables READ The organization’s tables (not archived), by name: key, fallback levels (keyShape), columns with their tokenKeys and SELECT options, and rowCount. No rows. Paged with limit (default 20) and offset; a short page is the last.
getWorkflowTable READ One table with its rows, paged by rowLimit (default 100, at most 500) and rowOffset: each row’s key parts, its fallback keyLevel, whether it is the DEFAULT row, and its cells by columnId. Reading rows needs an org admin; returns null for a table outside your organization.
listWorkflowTableChanges READ A table’s change history, newest first — who saved what and when. rowId narrows it to one row; page with after = nextCursor (at most 100 per page).
previewWorkflowTableLookup READ Which row a run on one record would use, and each column’s value, resolved exactly as a workflow run resolves it (fallback levels, ranges, composite keys). Pass workflowTableId and one of contactId, propertyId, dealId or appointmentId. match: DEFAULT means the default row answered; NONE means nothing did.
saveWorkflowTable WRITE Create a table (omit workflowTableId) or replace an existing table’s name, description and columns. The key is fixed at creation; fallback levels can be added later.
saveWorkflowTableRows WRITE Create or replace rows (rows, each replaced whole, never merged) and delete rows (deleteRowIds) in one write. Pass expectedUpdatedAt from getWorkflowTable so a save over a newer edit is refused (CONFLICT).
deleteWorkflowTable WRITE Archive a table. Refused while a workflow, message or form names one of its columns; the refusal lists them.

Event workflow payload samples (READ + WRITE scope)

An EVENT workflow starts from a JSON payload your system posts. Save an example payload first so its trigger fields can be mapped before anything arrives.

operationId Scope What it does
listEventWorkflowPayloadSamples READ The example payloads an EVENT workflow has received or been given. Empty for other workflow domains. A sample whose id starts with run: came from a real run and cannot be deleted.
recordEventWorkflowPayloadSample WRITE Save an example payload (a JSON object) on an EVENT workflow. Starts no run, sends nothing and leaves the graph alone. Refused on a workflow the key owner cannot edit.
deleteEventWorkflowPayloadSample WRITE Permanently delete one saved example (sampleId from listEventWorkflowPayloadSamples). A run: sample returns NOT_FOUND.

Content library files (READ + WRITE scope)

The files templates can attach (Automations → Templates → Files). Folders are the content-template folders from listContentTemplateFolders.

operationId Scope What it does
listLibraryFiles READ The library’s files — id, name, description, folderId, contentType — optionally narrowed by folderId or a name search.
addLibraryFileFromArtifact WRITE Add a file to the library by its artifactId, under a name, with an optional description and folderId. Templates can attach it once added.
updateLibraryFile WRITE Rename a library file or change its description (null clears it).
moveLibraryFile WRITE Move a file into a folder, or pass folderId: null for the root.
deleteLibraryFile WRITE Take a file out of the library for good. The underlying upload stays, so templates that already attach it keep working.

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.

operationId Scope What it does
listWorkflowFolders READ List the organization’s workflow-folder tree — id, name, parentFolderId, displayOrder, itemCount.
createWorkflowFolder WRITE Create an empty folder, optionally nested under parentFolderId; returns its id.
moveWorkflowToFolder WRITE Move a workflow (by its group id) into a folder by folderId (null to ungroup).
moveWorkflowFolder WRITE Move a folder under another folder, or pass parentFolderId: null to move it to the root.
renameWorkflowFolder WRITE Rename a folder by folderId.
deleteWorkflowFolder WRITE Delete a folder by folderId; its direct workflows and child folders move to its parent, or to the root if it had no parent. Contents are not deleted.
reorderWorkflowFolders WRITE Persist a new display order for the folders that share one parent — pass parentFolderId (null for the root) and the ordered folderIds.

Example

Trigger the live version of a workflow against one contact:

curl -X POST https://server.goliathdata.com/api/v1/call \
  -H "Authorization: Bearer gsk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "operationId": "triggerWorkflowRun", "variables": { "workflowGroupId": "GROUP_ID", "recordId": "CONTACT_ID" } }'

Full variable schemas, response shapes, and worked examples for every operation are available from the discovery endpoint.