WorkflowSwift β User Guide
WorkflowSwift β User Guide
WorkflowSwift turns a captured lead or form entry into automated, multi-step follow-up: an incoming webhook creates an instance, the engine walks the steps, and each step can pull a dashboard Data Card, call an endpoint you own, notify a webhook, wait, or fork.
Base API: https://workflowswift.com/api/v1 β all non-public endpoints need
Authorization: Bearer <token> from POST /auth/login. Health: GET /api/v1/health.
Getting started
- Register at
https://app.workflowswift.com/register. Your organization (tenant) is created automatically β it carries its tenant slug (account_slug, the NOT NULL UNIQUE identifier), the footer text (footer_year/footer_company) and your Hexomatic key. - You land on a dashboard pre-configured for the industry you picked at signup, with named Data Cards (dashboard widgets) already seeded.
- Invite team members (
POST /users/invite) β they join your tenant, with their own password and role (admin/member).
Tenancy, users and roles
- One tenant = one business account. Users, workflows, clients, leads, API keys and provider keys all belong to the tenant; reads are scoped to it server-side.
GET /usersreturns only your tenant's users;GET /users/teamlists team members.- The Admin area is only reachable by a super admin. Everyone else gets
403from/api/v1/admin/*, enforced at the API (not just hidden in the UI).
Workflows
- Create: from scratch or from the template gallery β
Templates Gallery(system) andMy Workflows(your own / cloned). - Steps: add, edit (name, description, config), reorder, delete. A step's type is fixed when you create it β to change the type, delete the step and add a new one.
- Step 1 is a Data Card step: it picks a dashboard widget by name (e.g. "Orlando Plumbers") from the widgets in your dashboard β nothing is hardcoded. The API refuses any other first step, and it refuses it on every door that decides the order: adding a step, moving a step, and a template (which installs as a workflow β see Templates below).
- Step types: Data Card, AI Action, Notify, Delay/Wait, Fork, HTTP Request, Action,
Render Video/Image/Audio, Condition, Webhook and Manual/Approval β and every one of them
executes. The API refuses any other step type (
POST /workflows/{id}/stepsanswers400), so a step that cannot run can no longer be created. Research is retired for that reason: no path in this app performs a research call. - Export is retired (2026-10-02): the destinations it advertised β CSV download, Resend, SendGrid, CoreSwift CRM β have no sender and no store on either side (this app holds no Resend/SendGrid credential, offers no file download, and CoreSwift is an inbound lead push, not an export contract). Every run used to be POSTed at a platform webhook that is not registered and the step was recorded as completed having exported nothing. The API refuses the type now, the Builder no longer offers it, and an old Export step is skipped with a warning that says so. To get data out, use an HTTP Request step pointing at an endpoint you own.
- Custom-code steps are retired (2026-10-02): the
transformandcodestep types accepted a block of JavaScript, and theformatstep described a transformation, but the only real behaviour any of them had was a JavaScript node inside n8n carrying your code verbatim. That node cannot run on this install (n8n here has no JavaScript task runner configured, and its own Code node fails), and turning the runner on would mean running your code inside the service that holds n8n's own credentials. Nothing here executes tenant code, so the API refuses all three types, the Builder never offered them, and an old step of one of those types is skipped with a warning that names the gap β it is never reported as having transformed anything. If you need custom logic, use an HTTP Request step to call a service you own that runs your code. - Design is retired (2026-10-02): the
designstep type said it would "generate visual assets via configured provider", but nothing in this app renders one β no provider, no route and no stored asset. A run recorded the step as completed while nothing was designed, and the run carried no warning at all. Nothing collected the step'sstyle/dimensionseither: the Builder never offereddesign. The API refuses the type now, and an old Design step is skipped with a warning that names the gap β it is never reported as having produced a design. To generate an asset, use a Render Image/Video/Audio step pointed at a provider endpoint you own, or an HTTP Request step to a service that renders it. - AI Action runs on your own provider key (2026-10-02). In the Builder, pick AI Action, choose the provider β OpenAI, Anthropic, DeepSeek or Gemini β and write the prompt; connect that provider's key once under Provider Keys. The step sends your prompt straight to that provider and the run stores the provider's own reply as the step's output, together with the provider's HTTP status. It costs 0 credits: it runs on your key, never a platform one. The write path refuses a step whose provider is not one of those four, and refuses one that names none. With no key connected the step is skipped with that reason in the run history; a provider error (a rejected key, a rate limit) fails the step and shows the provider's status code. Nothing is ever reported as generated unless the provider generated it. The n8n copy runs it too (2026-10-02): when your plan mirrors a workflow into n8n (Deploy), the mirrored graph's AI Action step calls back into this app, so a run started from the n8n copy's own webhook performs the same AI Action β same provider, same prompt, same key. It used to be a pass-through in that copy: the mirrored step did nothing, silently.
- Notify: a step can post to a webhook, send email, or send sms β set it up for either, both, or just the webhook. What each channel does:
- Webhook POSTs
{message, data}to the URL you put in the step's Recipient field, so the receiving end gets the run's own item. Its URL goes through the same destination check as an HTTP Request step, and a destination that answers non-2xx (or cannot be reached) fails the step β the run history records the status and the reason, so a notification that did not go out is never reported as sent. - Email sends a message from the platform, and SMS sends a text. Both reach your account's own people only: your account's users and its billing contact for email, and the phone numbers on those same people's user records for SMS. There is deliberately no box for typing an arbitrary address or number β the platform will not send mail or texts to people who are not on your account. Pick an audience in the step instead: All account users, Billing contact, or One named account user.
- A channel appears in the step's Channel list only when the platform has a sender configured for it (an admin sets that up under Settings β Email Provider / SMS Provider). If nothing is configured for email or SMS, the channel is not offered and the step is refused rather than silently built to do nothing.
- Every send is recorded: sent, failed, refused, or throttled. A refusal (for example a person with no phone number on file) names the reason in the run, and your account has an hourly sending cap so a looping workflow cannot flood a mailbox. Past the cap the send is refused and the run says so.
- If your plan mirrors the workflow into n8n (Deploy), the mirrored copy sends through this app's own provider β no credential is copied into n8n, and the mirrored graph stays activatable.
- HTTP Request / Action / Render Video/Image/Audio: an HTTP Request or Action step calls the
URL you configure, with the method you pick, and the run history records the status and the reply.
A Render step calls your provider's
endpointand logs the result under Renditions (provider, asset id and URL come from the provider's own response). Both refuse a destination that resolves inside the platform's own network β loopback, private or link-local addresses are never called β so a step can not be used to read an internal service. - Deploy to n8n (plan-gated) and run manually. A manual run creates an instance with traceable state, a step-by-step history and full execution logs.
- The n8n copy is what an external caller triggers:
POSTthe webhook path the deploy response returns (webhook_path, andwebhook_methodnames the verb βPOST; the same path answers aGETwith a 404 that names POST). n8n answers200 "Workflow was started"as soon as it accepts the trigger, so that caller never waits for the run. A run that then fails reports itself: it appears in Instances asfailed, and its run history names the failing step and the reason the step gave. A run that succeeds is visible in n8n's own execution record and does not create an instance of its own. The n8n copy goes through the same destination check as the in-process run: a step whose URL resolves inside the platform's own network (loopback, private or link-local), is empty, or is an n8n run-time expression is not deployed β the deploy (or the run that mirrors it) reports the step and the reason and writes nothing to n8n, so n8n never calls a destination the app itself would refuse. - Credits: charged per execution, and the amount comes from the workflow's tier
(
simple= 2,medium= 3,complex= 5,ai_enhanced= 7 β the full table is in thedeductresponse). Running out is refused by the API, not just warned about in the UI. CheckGET /credits/balance.
Downloading your run history. The Instances screen lists every run your workspace has had, and its Export CSV button downloads the whole record as a spreadsheet you can keep: one row per run with the run's name, the workflow it belongs to, its status, when it started and finished, how many minutes a finished run took, and the error text if it failed. The file is built from your own workspace only β the account comes from your sign-in, never from the request β so it is safe to hand to a client, an auditor or your bookkeeper. A failed download says so in words rather than saving an empty file.
Templates
- A template is a starting point for a workflow:
Install as Workflow(POST /templates/{id}/install) copies its steps into a new workflow of yours, which you can then edit in the Builder.Export JSONdownloads the template as a file,Import JSONcreates a new private template from one. - Every template step is a step type this app can actually run, and install copies it as-is. That is now enforced on the way in: a template carrying a step type with no executor is refused β on create, on import and on install β with the offending step named and the valid types listed, so an install can no longer produce a workflow made of steps that do nothing.
- A template's step 1 is the installed workflow's step 1, so the Data-Card rule holds here too:
a template whose first step (the lowest
sort_order) is not a Data Card is refused β on create, on import and on install β with the offending step and its type named, because the Builder would refuse to build that workflow. (Workflows this platform created itself with another first step β the inbound-captureintegrationβ keep running and stay editable; the rule governs what the API lets you build.) - Every template in the gallery is a checklist of the same shape. Government Contracting Lifecycle is ten stages; Client Onboarding, Lead Generation, Newsletter Campaign and Project Delivery are six or seven each. The first step is the Data Card every workflow opens with (pick your dashboard widget for it), each stage after it is a Manual / Approval step β the run parks at the stage and you Approve or Reject it from the instance's run history to move on β and a blueprint that waits on a clock carries a Delay / Wait step, which the background worker advances when its due time arrives.
- A template that carries no steps cannot be installed, so the gallery shows it as
0 stepsand draws no install button (the API refuses it either way:Template has no steps. Cannot install an empty template.). - A template that is not included in your plan is locked β the lock is enforced server-side, so the padlock badge is not just decoration.
Surfaces
A Surface is a named frontend/deployment target inside your tenant (website, mobile app,
chatbot, Alexa skill, kiosk): GET/POST /surfaces (name, slug, description).
- Surfaces are defined only in the admin panel β there is no standalone Surfaces page for regular users.
- Users instead get a surface selector / filter inside the editors, and workflows and leads
can be tagged with a surface (
surface_id). - Today the surface filter is applied by workflows and leads; content, templates, exports and prospecting do not filter by surface yet.
Dashboard: Data Cards, Brand Monitor, Competitor Watch
- Data Cards are named widgets. Workflow: Add Widget β name β type (prospecting / partners / contracts) β fill with data (search, import, or connect a source).
- Competitor Watch is real: a competitor needs its website, and Check fetches that page, fingerprints it and compares it with the previous check. The first check records a baseline and says so; the next one says either "no change since <time>" or that the page changed, with both sizes. A page that cannot be reached, or that resolves to a private address, is reported as not checked β never as "no change".
- Brand Monitor needs a web-mention provider, which this app does not have connected yet. Scan for mentions therefore tells you it is not connected and returns nothing. It does not show illustrative mentions any more β the previous behaviour invented platforms, URLs, snippets and sentiment scores.
- Data flow is
extension / API / n8n β dashboard display β optionally feeds a workflow. Custom metrics come in throughPOST /dashboard/push-widget-datawith a metric key. - Your tenant has one tab per industry you have selected; the dashboard also exposes live stats, an activity timeline and per-workspace views.
Keys: two different things
| Where it comes from | What it is for | |
|---|---|---|
| API Keys | POST /api-keys β generated by WorkflowSwift, shown once, stored hashed |
Calling WorkflowSwift programmatically |
| Provider Keys | POST /provider-keys β yours, from OpenAI / Resend / SendGrid / social etc. |
Letting WorkflowSwift call your third-party providers |
- Bring your own keys (BYOK). Every third-party key is entered by you in the app, at tenant level; it is never a global paste by an admin and never env-var-only.
- Provider keys are returned masked (
sk-β¦161) by every read endpoint β the raw value is never sent back. - API keys authenticate. Send the raw key exactly as it was shown once, as
Authorization: Bearer workflowswift_...(the sameAuthorizationheader the login JWT uses). It is verified server-side (argon2) and resolved to the account and user that own it, so every call is tenant-scoped βGET /bridge/statusechoes the account the key belongs to. The login JWT keeps working unchanged; a client may use either. - A key honours the row it was minted with:
is_active = falseor anexpires_atin the past is refused with401;permissions([]= full account scope,["read"]= read-only,"*"= everything) is enforced per request, so a read-only key gets403on a write;last_used_atis stamped on every successful use. Minting is gated by your plan (api_access,max_api_keys->402without them). - This is the credential the Chrome extension uses (see Chrome extension - Swift Market Intel below): extension Options -> API token.
Plans and limits
Plan limits are enforced at the API (402 Payment Required when you exceed them) and can be
set per plan by an admin: max_workflows, max_templates, max_instances, max_users,
max_automations, max_integrations, max_api_keys, max_clients, max_portfolio,
max_tags, max_industries, retention_days, plus the on/off features enforced by a gate β
n8n_deploy, api_access. -1 (or unlimited) means unlimited.
Three more on/off entries β priority_support ("Priority email and chat support"),
dedicated_support ("Dedicated account manager") and sla_guarantee ("Service level agreement
guarantee") β are not enforced by code and are not meant to be. They record which plan promises
what to the support team, and they are honoured by the support process, not by an API check.
Retired plan features (2026-10-02). csv_export, webhook_export and google_sheets are no
longer plan features. They were listed as included on every plan, but nothing in this app ever
produced a CSV, pushed an export over a webhook, or could reach Google Sheets β so they promised
something you would never have received. Export itself was retired as a workflow step on the same
day (see Export is retired above). custom_branding ("White-label branding options"),
audit_logs ("Access to audit log history") and custom_reports ("Custom report builder access")
were retired in the same pass for the same reason: no branding renderer, no audit history (the
audit table is empty and nothing writes to it) and no report builder exist anywhere in the product.
A plan feature now exists only where the app actually enforces it.
Your tier today:
| Workflows | Users | Instances | API access | n8n deploy | |
|---|---|---|---|---|---|
| Free | 3 | 2 | 10 | β | β |
| Starter | 15 | 10 | 100 | β | β |
| Professional | unlimited | 25 | 1000 | β | β |
| Enterprise | unlimited | unlimited | unlimited | β | β |
Changing plan
Plan & Billing (in the sidebar) shows the plan you are on today, what it includes, and your current usage against its allowance β the same numbers the API enforces.
Press the button on the plan you want. If self-serve checkout is open on this install you are taken to the secure checkout page and the plan changes when the payment completes. If checkout is not open yet, that same press files the request for you (a βRequest the <plan> planβ button stays on screen only if sending failed β pressing it retries): nothing is charged, the request goes straight to our team, and it also appears on your Tickets screen so you can see it was filed.
Right there on Plan & Billing, a Your plan requests card lists every request this workspace has made β which plan, the day it was asked for, and where it stands. Withdraw takes back a request you no longer want: nothing was charged, and you can ask again at any time. Once our team has made the switch, the same card says the day it happened.
Either way you get an email receipt confirming what you asked for, and a second email when the team has made the switch. Your plan name on the dashboard updates at the same time.
If no plan has ever been chosen for this workspace, the page says that plainly β No plan chosen yet β and names the free limits it is already running on, instead of presenting a tier as the plan you hold. Nothing is blocked while it says that: pick a tier below and it becomes your plan.
Your account and sign-in
Available to every user. The π€ Profile screen in the console is your own account, not the workspace's settings. It holds:
- Profile picture β PNG, JPEG, WebP or GIF up to 2 MB. Remove picture puts your initials back.
- Name and company β shown beside your messages and in team lists.
- Level β the plan your account actually runs on, read from the server each time the screen opens (never a cached copy, never the role word).
- Sign-in email β the address you sign in with. Changing it is confirmed, not immediate: type the new address and press Send confirmation link, and we mail that new address a link that expires in 2 hours and works once. Your current address keeps working until you open that link, so a typo can never lock you out of your own account.
- Change password β your new password plus the one you signed in with. A wrong current password is named beside the box instead of signing you out.
- Signed-in devices β Sign out of every other device ends every other session on your account: an old phone you sold, a laptop you shared. It does not change your password, and the browser you press it from stays signed in, so anyone still holding that old session is out at their next click.
Email branding
Available, per account. Open the console and go to π€ Profile & Account β Email Branding, then set:
- Logo β upload a PNG, JPEG, WebP or GIF up to 2 MB. Once one is set there is a Replace and a Remove button too.
- Brand display name β up to 60 characters, printed above the message.
- Brand colour β a hex colour such as
#4f46e5; it draws the rule under your logo.
Under the fields, Preview shows the header every email from this account opens with, and it changes as you type: a brand name appears centred in bold, a logo replaces that name line, and the brand colour draws the rule underneath. Nothing is saved until you press Save branding, and the preview is the same header the mail itself carries, so what you see is what a recipient gets.
Under that preview, Send me a preview email sends the same sample to your own account address. The request names no recipient, so the button can never mail anybody else. Save your branding first — what arrives is exactly what the next real email from this account looks like in an inbox. The line under the button is the server's own answer and says plainly when nothing was sent, for example when the account address is on a reserved test domain that cannot receive mail.
Below it, Show the exact email answers the other question without sending anything: press it and a frame opens with the message our server renders for this account — your header at the top, every merge field filled in (your name, your sign-in address), and the exact wording a recipient receives. It needs no inbox and mails nobody. The box above the button is drawn by your browser; this frame is the real thing, so if the two ever disagree, the frame is what people get. Save your branding first — an unsaved logo or name is not in the frame yet.
Press Save branding. From then on every transactional email this account sends β the welcome /
login-credentials mail, team invitations, password resets, purchase confirmations and workflow
notifications β opens with your logo and name. The logo is served from your own account's public URL
(GET /api/v1/branding/logo/{account_id}), which is what lets a mail client load it. Until
you set a name or a logo the mail carries the WorkflowSwift wordmark and name instead, because
branding is per account.
footer_year / footer_company) is
real and settable through PUT /api/v1/accounts.Integrations
- CoreSwift spoke:
GET /integrations/coreswift/statusreports whether your CoreSwift key is connected,GET /integrations/coreswift/listsproxies the CoreSwift list catalogue, andPOST /integrations/coreswift/pushpushes captured leads manually. The key itself is stored per account through the provider-key surface (GET/POST /api/v1/provider-keys) β the credential store every delivery path in this app reads. - Integration targets (
/integration-targets) are the configured destinations a run can push to. A target routes by EITHER a literalwebhook_urlOR aprovider_presetchosen from the provider catalogue (GET /provider-presets); the admin console's Integrations β Integration Targets panel sets either, and the dispatch uses the preset'sbase_urlwhen the target has no webhook URL. What is still NOT wired is a step's binding to a target: the Builder has no target picker and the steps API accepts no binding field, so a workflow you build in the app does not dispatch to a target. - Retired 2026-10-02: the per-user "Integration Center" store
and its routes.
GET/POST /api/v1/integrations,GET /api/v1/integrations/native,POST /api/v1/integrations/native/{provider},DELETE /api/v1/integrations/{provider}andPOST /api/v1/integrations/health-checkare gone (404); provider credentials live only in the account's provider-key store, the one every delivery path reads. - Incoming webhook:
POST /api/v1/incomingis what other Swift tools push leads to. It is protected by an internal key.
Tag β free account
When a lead is given one of this app's <App> β Free system tags in FunnelSwift (for example
WorkflowSwift β Free), FunnelSwift asks WorkflowSwift to create a free account for that lead,
using the lead's own name and email. WorkflowSwift then emails the new user their login details, so
they can sign in and upgrade here whenever they are ready.
The switch is in the admin console under Admin β Settings β Tag β free account
(admin_settings.provision_from_tags_enabled). It ships ON, so a tagged lead gets an account
immediately; turn it off and the receiver refuses the request
(403 refused / provisioning_disabled) and creates nothing. Tagging the same lead twice does not
create a duplicate account β the second attempt is recognised by email and ignored. The entry plan is
chosen next to the switch. The account is created here, in the destination app, so the lead signs in
at the app they actually want and upgrades on this app's own plans.
Chrome extension β Swift Market Intel
- Download:
https://workflowswift.com/swift-market-intel-extension-1.2.0.zip(rolling alias/swift-market-intel-extension.zip; pin the versioned file β the alias can lag behind the CDN cache). Install guide:/swift-market-intel-extension.html. - Install: unpack the zip, then
chrome://extensionsβ Developer mode β Load unpacked β pick the folder containingmanifest.json. Not on the Chrome Web Store, so updates are manual. - Connect: extension Options β paste your API token β leave API Base URL at
https://workflowswift.com/api/v1β Test Connection β Save. - Collect: open a supported listing β extension icon β Scrape Current Page β Send to WorkflowSwift. Supported: Etsy, Amazon, eBay, Facebook Marketplace, Shopify, Alibaba, AliExpress, Craigslist, Pinterest, TikTok, Instagram, Yelp, Google Maps.
- Sending costs 1 credit per run (charged to the account the key belongs to). A trigger aimed at
a workflow that belongs to a different account is refused with
403, an unknown one with404β the target is resolved against your own workflows first, so one tenant cannot fire another's. - Endpoints it uses (all under
/api/v1,Authorization: Bearer <token>):GET /bridge/commands,GET /bridge/status,POST /workflows/trigger,POST /bridge/ingest, andPOST /bridge/commands/ackβ after the extension runs a command it polled, it reports the outcome back ({command_id, status: "completed"|"failed", result}) and the server stamps that commandacknowledged_atwith the result, scoped to your own account (an id belonging to another tenant is answered404). - History: 1.1.0 shipped
API Base URL = https://workflowswift.com/api(no/v1), so every call returned 404 while Test Connection still reported success. Fixed in 1.2.0, which also drops the<all_urls>host permission (it was listed next to the explicit marketplace origins) and moves the base URL into one sharedconfig.js.
Prospecting β finding businesses to reach out to
Prospecting searches for real businesses (by industry, city and state) and can add what the provider knows about one of them. It is not a demo and it does not invent anything: if it cannot reach a real data source it says so instead of showing you a made-up list.
- It needs your own Google Places key. Connect it once under Provider Keys and pick Google Places API. The key is stored encrypted, is only ever shown back masked, and is used on this account alone.
- Without a key the screen tells you so. Searching shows a notice that names the missing key rather than returning companies β before this change the screen returned twenty invented businesses with made-up websites, and a made-up contact email and phone under Enrich. Those rows are gone for good.
- Results are what Google Places returned: business name, website, type, address and phone number where Google has one. If Google does not publish a field, it stays blank β the app will not guess a headcount, an email address or a social profile.
- Enrich looks the business up by name and fills only the fields that came back. A business it cannot find is reported as no match, not padded out with filler.
Support
Contact David via Telegram. Tickets: GET/POST /tickets.