REST API Reference
The WEXTL REST v1 API — versioned, frozen-additive, and machine-readable via a single OpenAPI document.
v1 overview
The REST API lets you create workflows, run them, and inspect run history from any HTTP client. Every route lives under a single versioned prefix and returns a consistent JSON envelope — see Errors & rate limits for the shape.
The full per-resource reference below — every endpoint, its parameters, request/response schemas, and a runnable curl + TypeScript SDK sample — renders directly from the machine-readable OpenAPI document at the path below, so it can never drift from what actually ships.
Machine-readable spec
The full contract is published as an OpenAPI document once the v1 surface ships, at the path below — unauthenticated, so your tooling (or an AI agent) can fetch it with no token.[3]
GET /api/v1/openapi.jsonAuthenticating requests
Every v1 request carries a bearer Personal Access Token or an OAuth 2.1 access token. See the Authentication guide for the full scope model and a key-rotation recipe.
References
- WEXTL® REST v1 API — our own source implementation.
- Our users' real API integrations and automations.
- The machine-readable OpenAPI specification for the REST API.
Download the full OpenAPI 3.1 spec (JSON)
Common error responses
| Status | Description |
|---|---|
401 | No valid bearer token. |
421 | Wrong regional endpoint — follow error.base_url. |
429 | Rate limited — retry after error.retry_after_s seconds. |
Workflows
13endpoints
GET/workflowsAuth requiredList workflows (bounded, cursor-paginated).
Parameters
Name In Type Description cursor querystringOpaque continuation token from a previous response. limit queryintegerPage size. Default 50. q querystringFree-text search on name + description. status querystring— folderId querystringRestrict to this folder and its descendants. sortBy querystring— sortDir querystring— tagIds querystringComma-separated tag ids. tagMatch querystring— Responses
200A page of workflow summaries.403Missing workflow:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<WorkflowSummary>— next_cursor* — string | null— data[]item (WorkflowSummary)Name In Type Description id* — string— name* — string— active* — boolean— folderId — string | null— nodeCount — integer— tagIds — array<string>— createdAt — integerEpoch milliseconds. updatedAt — integerEpoch milliseconds. Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "active": true, "folderId": "fld_01HXEXAMPLE", "nodeCount": 3, "tagIds": [ "..." ], "createdAt": 1719792000000, "updatedAt": 1719792000000 } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/workflows \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: workflows, nextCursor } = await client.workflows.list({ cursor, limit: 50 });POST/workflowsAuth requiredCreate a new, empty (draft) workflow. Build its graph via POST .../ops afterwards.
Request body (required)
Name In Type Description name* bodystring— folderId bodystring | nullMust reference an accessible folder. teamId bodystring | nullMust belong to the caller's org. Omit to use the org's default team. Responses
201The created (empty) draft workflow.400Invalid body — missing/oversized name, or an unresolvable folderId/teamId.403Missing workflow:write scope, or the plan lacks API access.- Common error responses
Name In Type Description data — WorkflowCreateResult— datashape (WorkflowCreateResult)Name In Type Description id* — string— name* — string— teamId* — string— folderId — string | null— active* — boolean— createdAt* — integerEpoch milliseconds. Example response
json { "data": { "id": "abc123", "name": "My workflow", "teamId": "team_01HXEXAMPLE", "active": true, "createdAt": 1719792000000, "folderId": "fld_01HXEXAMPLE" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "folderId": "fld_01HXEXAMPLE", "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const workflow = await client.workflows.create({ name: 'My workflow' }); // Build its graph via applyOps, then deploy() when ready.GET/workflows/{id}Auth requiredRead a workflow's full DRAFT graph, or its deployed snapshot via ?view=deployed.
Parameters
Name In Type Description id* pathstring— view querystringOmit for the current draft. `deployed` reads the last Go Live snapshot; 404 workflow_not_deployed if never published. Responses
200The workflow (draft or deployed, per ?view).403Missing workflow:read scope, or the plan lacks API access.404Workflow not found (or, with ?view=deployed, never deployed — workflow_not_deployed).- Common error responses
Name In Type Description data — one of 2 shapes— Example response
json { "data": { "id": "abc123", "name": "My workflow", "active": true, "nodes": [ { "id": 12, "appId": "slack", "name": "Send a message", "moduleSlug": "sendMessage", "x": 420, "y": 180, "params": { "channel": "#general", "text": "Deployment finished." }, "connectionId": "oauth2", "credentialId": "cred_01HXEXAMPLE" } ], "edges": [ { "id": "e_12_18", "from": 12, "to": 18, "type": "normal", "order": 0 } ], "description": "...", "folderId": "fld_01HXEXAMPLE", "teamId": "team_01HXEXAMPLE", "schedule": { "recurrence": { "kind": "interval", "every": 15, "unit": "min", "timezone": "Europe/London", "skipIfRunning": true }, "executionTimeoutMs": 0 }, "createdAt": 1719792000000, "updatedAt": 1719792000000, "deployed": { "deployedAt": 1719792000000, "versionNo": 0, "contentHash": "..." } } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const draft = await client.workflows.get(workflowId); // Or the last "Go Live" snapshot: const deployed = await client.workflows.get(workflowId, { view: 'deployed' });PATCH/workflows/{id}Auth requiredCurated update: name, active, schedule (recurrence), and/or folderId.
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description name bodystring— active bodybooleanActivate or deactivate the workflow. queuedEvents bodystringOnly with active: true. Webhook events that arrived while the workflow was off: 'all' (default) processes every one, 'latest' keeps only the newest, 'none' drops them so only new events run. recurrence bodyScheduleRecurrenceSchedule recurrence object, or null to clear. Setting a schedule does not activate. folderId bodystring | nullMove into folder; null to unassign. Responses
200Fields that were updated.400Invalid body.403Missing workflow:write scope.404Workflow not found.409Conflict — re-read and retry.503queuedEvents could not drop the waiting events; the workflow is still off. Retry.- Common error responses
Name In Type Description data* — object— datashape (object)Name In Type Description id — string— updated — array<string>— name — string— active — boolean— folderId — string | null— Example response
json { "data": { "id": "abc123", "updated": [ "..." ], "name": "My workflow", "active": true, "folderId": "fld_01HXEXAMPLE" } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "active": true, "queuedEvents": "...", "recurrence": { "kind": "interval", "every": 15, "unit": "min", "timezone": "Europe/London", "skipIfRunning": true }, "folderId": "fld_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Fields omitted from the input are left untouched; pass null to explicitly clear folderId/recurrence. const updated = await client.workflows.update(workflowId, { name: 'Renamed', active: true });DELETE/workflows/{id}Auth requiredPermanently delete a workflow. Refused while active or deployed.
Parameters
Name In Type Description id* pathstring— Responses
200Deleted.403Missing workflow:write scope.404Workflow not found.409Workflow is active or deployed — deactivate/undeploy first.- Common error responses
Name In Type Description data — object— datashape (object)Name In Type Description ok — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Refused (409 workflow_active) while active or deployed — deactivate/undeploy first. await client.workflows.delete(workflowId);POST/workflows/{id}/opsAuth requiredApply a batch of graph operations to a workflow.
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description ops* bodyarray<object>Batch of GraphOp objects. One of: add_node, update_params, update_config, connect, disconnect, remove_node, rename_node, set_credential, set_edge_filter. expectedUpdatedAt* bodyintegerREQUIRED optimistic-concurrency precondition — the workflow `updatedAt` this batch was computed against (take it from a prior GET /workflows/{id}). Mismatch → 409 workflow_modified. Without it two agents editing the same workflow silently overwrite each other, since a batch replaces the whole nodes/edges set; omitting it is rejected with 400 VALIDATION_ERROR. Responses
200The batch result — best-effort; a non-empty errors[] does not mean the whole batch was rejected.400Invalid body, or the resulting graph fails the topology gate.403Missing workflow:write scope, or the plan lacks API access.404Workflow not found.409workflow_modified — expectedUpdatedAt no longer matches; reload and retry.- Common error responses
Name In Type Description data — GraphOpsResult— datashape (GraphOpsResult)Name In Type Description applied* — objecttempId → real node id, for every add_node that landed. errors* — array<string>Best-effort per-op failures — the rest of the batch still applies. touched* — array<integer>Node ids added or param/config-updated by this batch, including hub-scaffolded satellite/placeholder ids. updatedAt — integerThe workflow's new updatedAt — pass it as expectedUpdatedAt on the next ops call to avoid conflicts. Example response
json { "data": { "applied": { "tmp_1": "42", "tmp_2": "43" }, "errors": [ "..." ], "touched": [ 0 ], "updatedAt": 1719792000000 } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/ops \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "ops": [ { "op": "..." } ], "expectedUpdatedAt": 1719792000000 }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const draft = await client.workflows.get(workflowId); const result = await client.workflows.applyOps( workflowId, [{ op: 'rename_node', nodeId: '1', label: 'Step 1' }], { expectedUpdatedAt: draft.updatedAt }, ); // result.errors is best-effort — a non-empty array does not mean the whole batch failed. // result.updatedAt is the workflow's NEW updatedAt — thread it into the next call's expectedUpdatedAt.POST/workflows/{id}/deployAuth requiredGo Live — publish the current draft as the deployed snapshot (idempotent).
Parameters
Name In Type Description id* pathstring— Request body
Name In Type Description expectedUpdatedAt bodyintegerOptimistic-concurrency precondition — the draft updatedAt this deploy was computed against. Mismatch → 409 workflow_modified. Responses
200The new deployed snapshot summary.403Missing workflow:write or run scope (deploying arms future runs), or the plan lacks API access.404Workflow not found.409workflow_modified — expectedUpdatedAt no longer matches; reload and retry.- Common error responses
Name In Type Description data — DeployResult— datashape (DeployResult)Name In Type Description workflowId* — string— contentHash* — string— versionNo* — integer— deployedAt* — integerEpoch milliseconds. Example response
json { "data": { "workflowId": "wf_01HXEXAMPLE", "contentHash": "...", "versionNo": 0, "deployedAt": 1719792000000 } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/deploy \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "expectedUpdatedAt": 1719792000000 }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const deployed = await client.workflows.deploy(workflowId); // "Go Live"POST/workflows/{id}/validateAuth requiredValidate a workflow's structure (duplicate ids, orphan edges, cycles, trigger placement). Run this before declaring a workflow correct.
Parameters
Name In Type Description id* pathstring— Responses
200The validation result.403Missing workflow:read scope, or the plan lacks API access.404Workflow not found.- Common error responses
Name In Type Description data — ValidateWorkflowResult— datashape (ValidateWorkflowResult)Name In Type Description valid* — boolean— issues* — array<string>Human-readable validation problems. Empty when valid is true. Example response
json { "data": { "valid": false, "issues": [ "Node 7 has no incoming edges and is not the trigger." ] } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/validate \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Run this before declaring a workflow correct — checks duplicate ids, orphan edges, cycles, trigger placement. const { valid, issues } = await client.workflows.validate(workflowId);POST/workflows/{id}/resolve-expressionAuth requiredResolve a {{expr}} template against a node's available input data from the latest (or a specific) run — validates references like {{4.user_id}} before wiring them.
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description expression* bodystringThe expression template, e.g. '{{3.rows[0].name}}' or bare '3.rows[0].name'. fromNodeId bodystringThe node id that uses this expression — helps locate its upstream nodes from the latest run. runId bodystringOptional run id to use for sample data. Defaults to the most recent run. Responses
200The resolution attempt — resolved is null with error set when it could not be resolved (e.g. no runs yet).400expression is required.403Missing workflow:read scope, or the plan lacks API access.404Workflow not found.- Common error responses
Name In Type Description data — ResolveExpressionResult— datashape (ResolveExpressionResult)Name In Type Description resolved* — anyThe resolved value (any JSON type), or null when resolution failed or no run data exists yet. resolvedType — stringtypeof the resolved value, e.g. "string", "number", "object". Absent when resolved is null. truncated — booleantrue when a large resolved string value was cut to fit the response budget. error* — string | nullPresent when resolution failed, or when no runs exist yet for this workflow. Example response
json { "data": { "resolved": "user_42", "resolvedType": "string", "error": null } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/resolve-expression \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "expression": "{{3.rows[0].name}}", "fromNodeId": "4" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // resolved is null with error set when it can't be resolved yet (e.g. no runs exist) — not a thrown error. const { resolved, error } = await client.workflows.resolveExpression(workflowId, { expression: '{{3.rows[0].name}}', fromNodeId: '4' });POST/workflows/{id}/webhookAuth requiredMint (or return the existing) inbound webhook URL for a workflow trigger node.
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description nodeId* bodystringThe webhook trigger node id (numeric id as a string). Responses
200The webhook URL (idempotent — alreadyBound:true when a live one already existed).400Missing nodeId, or the node is not a webhook trigger node.403Missing webhook:write scope, or the plan lacks API access.404Workflow or node not found.409The node already has a DISABLED webhook record bound to it.- Common error responses
Name In Type Description data — Webhook— datashape (Webhook)Name In Type Description webhookId* — string— url* — stringThe inbound URL to POST trigger payloads to. alreadyBound — boolean— Example response
json { "data": { "webhookId": "wh_01HXEXAMPLE", "url": "https://eu1.wextl.com/hooks/wh_01HXEXAMPLE", "alreadyBound": true } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/webhook \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "nodeId": "abc123" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Idempotent — a live webhook already bound to this node returns the same url with alreadyBound: true. const webhook = await client.workflows.createWebhook(workflowId, nodeId);POST/workflows/{id}/runsAuth requiredStart a run of the workflow's current draft (billing and structure checks apply).
Parameters
Name In Type Description id* pathstring— Idempotency-Key headerstringOptional but strongly recommended when the client may retry. A caller-chosen unique string; retrying with the same key returns the SAME run rather than starting another. Without it a timeout-and-retry starts a second run, double-charging credits and repeating any external side effects the workflow performs. Request body
Name In Type Description nodeId bodystringOptional node id (numeric id as a string) to run from — a scoped run. Omit for a full-graph run. upstreamOutputs bodyobjectOptional nodeId→payload map to seed as upstream inputs. Mutually exclusive with seedFromRun. seedFromRun bodyobjectSeed from a past run node output (server-resolved). Mutually exclusive with upstreamOutputs. Responses
201The run was created and enqueued.400Invalid nodeId/inputs, or the workflow fails the topology gate (e.g. no runnable path).402Payment required — a payment problem or an inactive subscription is blocking runs.403Missing run scope, no active plan, or out of credits (PLAN_LIMIT_EXCEEDED).404Workflow not found.- Common error responses
Name In Type Description data — StartRunResult— datashape (StartRunResult)Name In Type Description runId* — string— Example response
json { "data": { "runId": "run_01HXEXAMPLE" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/runs \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "nodeId": "abc123", "upstreamOutputs": { "12": { "message": "Hello world", "channel": "#general" } }, "seedFromRun": { "runId": "run_01HXEXAMPLE", "nodeId": 0 } }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // idempotencyKey is strongly recommended if you may retry — without it a // timeout-and-retry starts a SECOND run, double-charging credits. const { runId } = await client.workflows.startRun(workflowId, { idempotencyKey: 'my-retry-safe-key-1' });PUT/workflows/{id}/tagsAuth requiredReplace the full tag set on a workflow.
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description tagIds* bodyarray<string>— Responses
200Tags assigned.400Invalid body.403Missing workflow:write scope.404Workflow not found.- Common error responses
Name In Type Description data — SetWorkflowTagsResult— Name In Type Description ok* — boolean— tagIds* — array<string>— Example response
json { "data": { "ok": true, "tagIds": [ "tag_01HXEXAMPLE" ] } }Example request
POST/workflows/{id}/test-moduleAuth requiredExecute a single app node with its current parameters (module test).
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description nodeId* bodyone of 2 shapes— Responses
200Module test result.400Invalid body or non-app node.403Missing workflow:write scope.- Common error responses
Name In Type Description data — objectThe node's module output on success. Shape depends on the module — the same shape a real run's node output would have. Example response
json { "data": { "ok": true, "ts": "1719792000.000100", "channel": "#general" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/workflows/wf_01HXEXAMPLE/test-module \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "nodeId": "abc123" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Executes ONE app node with its current parameters — same output shape a real run would produce. const output = await client.workflows.testModule(workflowId, nodeId);
Variables
4endpoints
GET/variablesAuth requiredList variable names and metadata for the organization (up to 500). Secret values are always returned as null.
Responses
200Every variable visible to this key.403Missing variable:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<Variable>— data[]item (Variable)Name In Type Description id* — string— name* — string— isSecret* — boolean— value* — string | null— teamId* — string | null— createdAt* — integerEpoch milliseconds. updatedAt* — integerEpoch milliseconds. Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "isSecret": false, "value": null, "teamId": "team_01HXEXAMPLE", "createdAt": 1719792000000, "updatedAt": 1719792000000 } ] }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/variables \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: variables } = await client.variables.list(); // secret values always nullGET/variables/{id}Auth requiredRead one variable by id. A secret variable's value is always returned as null.
Parameters
Name In Type Description id* pathstring— Responses
200The variable (value is null when isSecret is true).403Missing variable:read scope, or the plan lacks API access.404Variable not found.- Common error responses
Name In Type Description data — Variable— datashape (Variable)Name In Type Description id* — string— name* — string— isSecret* — boolean— value* — string | null— teamId* — string | null— createdAt* — integerEpoch milliseconds. updatedAt* — integerEpoch milliseconds. Example response
json { "data": { "id": "abc123", "name": "My workflow", "isSecret": false, "value": null, "teamId": "team_01HXEXAMPLE", "createdAt": 1719792000000, "updatedAt": 1719792000000 } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/variables/var_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const variable = await client.variables.get(variableId);PUT/variables/{id}Auth requiredCreate or update a variable by name (upsert) — the {id} path segment IS the variable name, not a database id. Secrets are write-only.
Parameters
Name In Type Description id* pathstringThe variable name (letters/numbers/underscores, must start with a letter or underscore) — this call upserts by name. Request body (required)
Name In Type Description value* bodystringRequired for a new non-secret variable. For an existing secret, a blank value keeps the current stored value. isSecret bodybooleanWhether this variable is a secret. Only takes effect on create — updating an existing variable never changes its stored secret status. teamId bodystring | nullMust belong to the caller's org. Omit for an org-level variable. Responses
200An existing variable was updated.201A new variable was created.400Invalid body — bad name, missing value, a secret shorter than 6 characters, or an unresolvable teamId.403Missing variable:write scope, or the plan lacks API access.- Common error responses
Name In Type Description data — SetVariableResult— datashape (SetVariableResult)Name In Type Description id* — string— name* — string— isSecret* — boolean— teamId* — string | null— updatedAt* — integerEpoch milliseconds. created* — booleantrue when this call created a new variable, false when it updated an existing one. Example response
json { "data": { "id": "abc123", "name": "My workflow", "isSecret": false, "teamId": "team_01HXEXAMPLE", "updatedAt": 1719792000000, "created": true } }Example request
bash curl -X PUT https://eu1.wextl.com/api/v1/variables/var_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "value": null, "isSecret": false, "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Upsert by NAME (the argument is the variable name, not a database id). const result = await client.variables.set('MY_VARIABLE', { value: 'hello', isSecret: false });DELETE/variables/{id}Auth requiredSoft-delete a variable by database id (not name).
Parameters
Name In Type Description id* pathstringThe variable database id from list_variables / get_variable. Responses
200Deleted.403Missing variable:write scope.404Variable not found.- Common error responses
Name In Type Description data — object— datashape (object)Name In Type Description ok — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/variables/var_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // {id} here IS the database id (from list()/get()), not the name. await client.variables.delete(variableId);
Runs
6endpoints
GET/runsAuth requiredList runs of a workflow (bounded, cursor-paginated). workflowId is REQUIRED in v1 — org-wide listing is out of scope.
Parameters
Name In Type Description workflowId* querystringREQUIRED — scope the listing to this workflow. cursor querystring— limit queryintegerPage size. Default 50. q querystringFree-text search over status + id. Responses
200A page of run summaries.400workflowId query parameter is required.403Missing workflow:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<RunSummary>— next_cursor* — string | null— data[]item (RunSummary)Name In Type Description id* — string— workflowId* — string | null— status* — string— createdAt* — string | nullISO-8601 timestamp. finishedAt — string | nullISO-8601 timestamp; null while the run is non-terminal. creditsUsed* — number— Example response
json { "data": [ { "id": "abc123", "workflowId": "wf_01HXEXAMPLE", "status": "succeeded", "createdAt": "2024-07-01T12:00:00.000Z", "creditsUsed": 50, "finishedAt": null } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/runs \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // workflowId is REQUIRED — org-wide listing is out of scope in v1. const { data: runs, nextCursor } = await client.runs.list({ workflowId, limit: 50 });GET/runs/{id}Auth requiredGet a run report: status, timestamps, error, and per-node status/error/output.
Parameters
Name In Type Description id* pathstring— Responses
200The run report.403Missing workflow:read scope, or the plan lacks API access.404Run not found.- Common error responses
Name In Type Description data — RunReport— datashape (RunReport)Name In Type Description id* — string— workflowId — string— status* — string— createdAt — string | null— finishedAt — string | null— error — string | null— nodes — array<RunNode>— Example response
json { "data": { "id": "abc123", "status": "succeeded", "workflowId": "wf_01HXEXAMPLE", "createdAt": "2024-07-01T12:00:00.000Z", "finishedAt": null, "error": null, "nodes": [ { "nodeId": 12, "label": "Send a message", "status": "completed", "output": { "ok": true, "ts": "1719792000.000100" } } ] } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/runs/run_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const report = await client.runs.get(runId); // status + per-node status/error/outputPOST/runs/{id}/cancelAuth requiredCancel an in-flight workflow run.
Parameters
Name In Type Description id* pathstring— Responses
200The run was cancelled.400The run is already in a terminal state.403Missing run scope, or the plan lacks API access.404Run not found.- Common error responses
Name In Type Description data — CancelRunResult— datashape (CancelRunResult)Name In Type Description ok* — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/runs/run_01HXEXAMPLE/cancel \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const result = await client.runs.cancel(runId);POST/runs/{id}/rerunAuth requiredRe-queue a finished run as a new attempt.
Parameters
Name In Type Description id* pathstring— Responses
201New attempt enqueued.403Missing run scope.404Run not found.- Common error responses
Name In Type Description data — RerunResult— datashape (RerunResult)Name In Type Description runId* — string— status* — string— attemptNumber* — integer— updatedAt* — integerEpoch milliseconds. Example response
json { "data": { "runId": "run_01HXEXAMPLE", "status": "queued", "attemptNumber": 2, "updatedAt": 1719792000000 } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/runs/run_01HXEXAMPLE/rerun \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const attempt = await client.runs.rerun(runId); // re-queues as a new attemptPOST/runs/{id}/retry-nodeAuth requiredResume a run from one failed node (same attempt).
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description nodeId* bodyinteger— attemptNumber bodyinteger— Responses
200Resume enqueued.400Invalid body.403Missing run scope.404Run not found.- Common error responses
Name In Type Description data — RetryNodeResult— datashape (RetryNodeResult)Name In Type Description runId* — string— status* — string— attemptNumber* — integer— nodeId* — integer— updatedAt* — integerEpoch milliseconds. Example response
json { "data": { "runId": "run_01HXEXAMPLE", "status": "running", "attemptNumber": 1, "nodeId": 12, "updatedAt": 1719792000000 } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/runs/run_01HXEXAMPLE/retry-node \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "nodeId": 0, "attemptNumber": 0 }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const result = await client.runs.retryNode(runId, { nodeId: 12 });POST/runs/{id}/nodes/{nodeId}/respondAuth requiredSubmit a human-in-the-loop response and resume (or complete) the run.
Parameters
Name In Type Description id* pathstring— nodeId* pathstring— Request body (required)
Name In Type Description response* bodyobjectThe HITL response fields — shape depends on the node's configured response fields. attemptNumber bodyinteger— Responses
200Response recorded.400Invalid body.402Payment required for mid-flow resume.403Missing run scope.404Run not found.409Conflict — resume already in flight or run terminal.- Common error responses
Name In Type Description data — RespondNodeResult— datashape (RespondNodeResult)Name In Type Description ok* — boolean— resumed* — booleantrue when the run resumed execution past this node. terminal* — booleantrue when this response completed the run (no further nodes to run). Example response
json { "data": { "ok": true, "resumed": true, "terminal": false } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/runs/run_01HXEXAMPLE/nodes/node_1/respond \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "response": { "approved": true, "comment": "Looks good, ship it." }, "attemptNumber": 0 }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const result = await client.runs.respondNode(runId, nodeId, { response: { approved: true } });
Credentials
6endpoints
GET/credentialsAuth requiredList the organization's stored credentials (names and metadata only; secret payloads are never returned).
Parameters
Name In Type Description cursor querystring— limit queryintegerPage size. Default 50. appId querystringOptional app id to filter by. Responses
200A page of credential summaries.403Missing credential:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<Credential>— next_cursor* — string | null— data[]item (Credential)Name In Type Description id* — string— name* — string— appId* — string— type* — stringThe connection type id. status* — string— folderId — string | null— createdAt* — integerEpoch milliseconds. updatedAt* — integerEpoch milliseconds. Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "appId": "slack", "type": "oauth2", "status": "succeeded", "createdAt": 1719792000000, "updatedAt": 1719792000000, "folderId": "fld_01HXEXAMPLE" } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/credentials \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: credentials, nextCursor } = await client.credentials.list({ appId: 'slack' });POST/credentialsAuth requiredCreate a credential when safe (no-auth or secrets already supplied). OAuth or missing secrets return status credential_required with a connect_url for a logged-in human.
Request body (required)
Name In Type Description appId* bodystring— connectionId* bodystring— name* bodystring— data bodyobjectSecret field values when the caller already has them — keyed by the connection's parameter names (from get_module_spec / the connection spec), e.g. an api-key connection's `apiKey`. Never invent placeholders when calling this for real — the value shown here is illustrative only. teamId bodystring | null— Responses
200Created credential or credential_required handoff.400Invalid body.403Missing credential:write scope, or the plan lacks API access.- Common error responses
Name In Type Description data — CredentialCreateResult— datashape (CredentialCreateResult)Name In Type Description credentialId — string— status — string— appId — string— connectionId — string— connect_url — string— Example response
json { "data": { "credentialId": "abc123", "status": "succeeded", "appId": "slack", "connectionId": "abc123", "connect_url": "..." } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/credentials \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "appId": "slack", "connectionId": "abc123", "name": "My workflow", "data": { "apiKey": "sk_live_your_api_key_here" }, "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // OAuth or missing secrets return { status: 'credential_required', connectUrl } instead — // relay connectUrl to a human, then poll list() until the credential appears. const result = await client.credentials.create({ appId: 'slack', connectionId: 'oauth2', name: 'Slack' });PATCH/credentials/{id}Auth requiredRename a credential or move its folder (never returns secrets).
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description name bodystring— folderId bodystring | null— Responses
200Updated credential summary.400Invalid body.403Missing credential:write scope.404Credential not found.- Common error responses
Name In Type Description data — Credential— datashape (Credential)Name In Type Description id* — string— name* — string— appId* — string— type* — stringThe connection type id. status* — string— folderId — string | null— createdAt* — integerEpoch milliseconds. updatedAt* — integerEpoch milliseconds. Example response
json { "data": { "id": "abc123", "name": "My workflow", "appId": "slack", "type": "oauth2", "status": "succeeded", "createdAt": 1719792000000, "updatedAt": 1719792000000, "folderId": "fld_01HXEXAMPLE" } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/credentials/cred_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "folderId": "fld_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const credential = await client.credentials.update(credentialId, { name: 'Renamed' });DELETE/credentials/{id}Auth requiredSoft-delete a credential.
Parameters
Name In Type Description id* pathstring— Responses
200Deleted.403Missing credential:write scope.404Credential not found.- Common error responses
Name In Type Description data — object— datashape (object)Name In Type Description ok — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/credentials/cred_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); await client.credentials.delete(credentialId);POST/credentials/{id}/verifyAuth requiredTest whether a stored credential is accepted by the third-party service (never returns the secret).
Parameters
Name In Type Description id* pathstring— Responses
200Verification outcome.403Missing credential:use scope.- Common error responses
Name In Type Description data — one of 2 shapes— Example response
json { "data": { "status": "ok" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/credentials/cred_01HXEXAMPLE/verify \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const result = await client.credentials.verify(credentialId); // never returns the secretPOST/credentials/dynamic-optionsAuth requiredFetch live RPC-backed field options (tables, channels, sheets, …).
Request body (required)
Name In Type Description appId* bodystring— rpcId* bodystring— credentialId bodystring— params bodyobjectOptional dependency parameters for nested RPCs, e.g. { "tableId": "users" } to get columns for a table. Responses
200Option items.400Invalid body.403Missing credential:use scope.- Common error responses
Name In Type Description data — DynamicOptionsResult— datashape (DynamicOptionsResult)Name In Type Description items* — array<object>— truncated — booleantrue when the result was capped at 200 items. total — integerPresent only when truncated. Example response
json { "data": { "items": [ { "value": "C0123ABCDE", "label": "#general" }, { "value": "C0456FGHIJ", "label": "#engineering" } ] } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/credentials/dynamic-options \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "appId": "slack", "rpcId": "abc123", "credentialId": "abc123", "params": { "tableId": "users" } }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { items } = await client.credentials.getDynamicOptions({ appId: 'slack', rpcId: 'listChannels', credentialId });
Databases
6endpoints
GET/databasesAuth requiredList the organization's built-in database tables.
Parameters
Name In Type Description limit queryinteger— Responses
200Database table summaries.403Missing database:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<DatabaseSummary>— truncated* — boolean— data[]item (DatabaseSummary)Name In Type Description id* — string— name* — string— columnCount* — integer— rowCount* — integer— Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "columnCount": 3, "rowCount": 3 } ], "truncated": true }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/databases \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: databases, truncated } = await client.databases.list();POST/databasesAuth requiredCreate a built-in database table (DDL). Row writes are not available on the public API.
Request body (required)
Name In Type Description name* bodystring— description bodystring— columns* bodyarray<DatabaseColumnInput>— teamId bodystring | null— Responses
201Created table with generated column ids.400Invalid body.403Missing database:write scope, or the plan lacks API access.- Common error responses
Name In Type Description data — DatabaseSchema— datashape (DatabaseSchema)Name In Type Description id* — string— name* — string— columns* — array<object>— rowCount* — integer— uniqueKeyColumnId — string— Example response
json { "data": { "id": "abc123", "name": "My workflow", "columns": [ { "id": "abc123", "name": "My workflow", "type": "action", "required": true, "indexed": true } ], "rowCount": 3, "uniqueKeyColumnId": "abc123" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/databases \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "columns": [ { "name": "Status", "type": "select", "required": false, "indexed": true } ], "description": "...", "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const table = await client.databases.create({ name: 'Customers', columns: [{ name: 'Name', type: 'text' }] });GET/databases/{id}Auth requiredRead one database table's column schema.
Parameters
Name In Type Description id* pathstring— Responses
200Schema projection.403Missing database:read scope, or the plan lacks API access.404Database not found.- Common error responses
Name In Type Description data — DatabaseSchema— datashape (DatabaseSchema)Name In Type Description id* — string— name* — string— columns* — array<object>— rowCount* — integer— uniqueKeyColumnId — string— Example response
json { "data": { "id": "abc123", "name": "My workflow", "columns": [ { "id": "abc123", "name": "My workflow", "type": "action", "required": true, "indexed": true } ], "rowCount": 3, "uniqueKeyColumnId": "abc123" } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/databases/db_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const schema = await client.databases.getSchema(databaseId);PATCH/databases/{id}Auth requiredReplace a table's column definitions with the full desired list (declarative).
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description columns* bodyarray<DatabaseColumnInput>— Responses
200Updated schema.400Invalid body.403Missing database:write scope, or the plan lacks API access.404Database not found.- Common error responses
Name In Type Description data — DatabaseSchema— datashape (DatabaseSchema)Name In Type Description id* — string— name* — string— columns* — array<object>— rowCount* — integer— uniqueKeyColumnId — string— Example response
json { "data": { "id": "abc123", "name": "My workflow", "columns": [ { "id": "abc123", "name": "My workflow", "type": "action", "required": true, "indexed": true } ], "rowCount": 3, "uniqueKeyColumnId": "abc123" } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/databases/db_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "columns": [ { "name": "Status", "type": "select", "required": false, "indexed": true } ] }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Declarative: pass the FULL desired column list — an existing column left out is dropped. const schema = await client.databases.updateColumns(databaseId, [{ id: existingColumnId, name: 'Status', type: 'select' }]);DELETE/databases/{id}Auth requiredSoft-delete a built-in database table.
Parameters
Name In Type Description id* pathstring— Responses
200Deleted.403Missing database:write scope.404Database not found.- Common error responses
Name In Type Description data — object— datashape (object)Name In Type Description ok — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/databases/db_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); await client.databases.delete(databaseId);GET/databases/{id}/rowsAuth requiredRead rows from a built-in database table (bounded). No public DML row writes.
Parameters
Name In Type Description id* pathstring— filters querystringJSON-encoded filter array. sortColumnId querystring— sortDir querystring— limit queryinteger— cursor querystring— Responses
200A page of rows keyed by column name.400Invalid filters or query shape.403Missing database:read scope, or the plan lacks API access.404Database not found.- Common error responses
Name In Type Description data* — array<object>— count* — integer— next_cursor* — string | null— Example response
json { "data": [ { "id": "row_01HXEXAMPLE", "Name": "Ada Lovelace", "Status": "active", "createdAt": 1719792000000, "updatedAt": 1719792000000 } ], "count": 3, "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/databases/db_01HXEXAMPLE/rows \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // No public DML row-write endpoint exists in v1 — this is read-only. const { data: rows, count, nextCursor } = await client.databases.queryRows(databaseId, { limit: 50 });
Structures
4endpoints
GET/structuresAuth requiredList the organization's structures.
Parameters
Name In Type Description limit queryinteger— Responses
200Structure summaries.403Missing structure:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<StructureSummary>— truncated* — boolean— data[]item (StructureSummary)Name In Type Description id* — string— name* — string— fieldCount* — integer— Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "fieldCount": 3 } ], "truncated": true }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/structures \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: structures, truncated } = await client.structures.list();POST/structuresAuth requiredCreate a structure. Honours the structures_created plan limit (no bypass).
Request body (required)
Name In Type Description name* bodystring— description bodystring— fields bodyarray<StructureField>— folderId bodystring | null— teamId bodystring | null— Responses
201Created structure.400Invalid body.403Missing structure:write scope, or plan structure limit reached.- Common error responses
Name In Type Description data — Structure— datashape (Structure)Name In Type Description id* — string— name* — string— fields* — array<object>— Example response
json { "data": { "id": "abc123", "name": "My workflow", "fields": [ { "name": "My workflow", "type": "action", "required": true } ] } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/structures \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "description": "...", "fields": [ { "name": "email", "type": "text", "required": true } ], "folderId": "fld_01HXEXAMPLE", "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const structure = await client.structures.create({ name: 'Order', fields: [{ name: 'email', type: 'string', required: true }] });GET/structures/{id}Auth requiredRead one structure's field list.
Parameters
Name In Type Description id* pathstring— Responses
200Structure fields.403Missing structure:read scope, or the plan lacks API access.404Structure not found.- Common error responses
Name In Type Description data — Structure— datashape (Structure)Name In Type Description id* — string— name* — string— fields* — array<object>— Example response
json { "data": { "id": "abc123", "name": "My workflow", "fields": [ { "name": "My workflow", "type": "action", "required": true } ] } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/structures/struct_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const structure = await client.structures.get(structureId);PATCH/structures/{id}Auth requiredUpdate a structure name, description, fields, or folderId.
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description name bodystring— description bodystring— fields bodyarray<StructureField>— folderId bodystring | null— Responses
200Updated structure.400Invalid body.403Missing structure:write scope.404Structure not found.- Common error responses
Name In Type Description data — Structure— datashape (Structure)Name In Type Description id* — string— name* — string— fields* — array<object>— Example response
json { "data": { "id": "abc123", "name": "My workflow", "fields": [ { "name": "My workflow", "type": "action", "required": true } ] } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/structures/struct_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "description": "...", "fields": [ { "name": "email", "type": "text", "required": true } ], "folderId": "fld_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const structure = await client.structures.update(structureId, { name: 'Renamed' });
Functions
4endpoints
GET/functionsAuth requiredList the organization's custom functions (metadata only).
Parameters
Name In Type Description cursor querystring— limit queryinteger— q querystring— Responses
200Function summaries.403Missing function:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<FunctionSummary>— next_cursor* — string | null— data[]item (FunctionSummary)Name In Type Description id* — string— name* — string— description — string | null— Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "description": "..." } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/functions \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: functions, nextCursor } = await client.functions.list();POST/functionsAuth requiredCreate a custom function. Requires the custom_functions plan feature (no bypass).
Request body (required)
Name In Type Description name* bodystringcamelCase name. code* bodystring— description bodystring— parameters bodyarray<FunctionParameter>— folderId bodystring | null— teamId bodystring | null— Responses
201Created function.400Invalid body.403Missing function:write scope, or custom_functions not on plan.- Common error responses
Name In Type Description data — Function— datashape (Function)Name In Type Description id* — string— name* — string— description — string | null— code* — string— Example response
json { "data": { "id": "abc123", "name": "My workflow", "code": "...", "description": "..." } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/functions \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "code": "...", "description": "...", "parameters": [ { "name": "amount", "type": "number", "description": "Order total in cents.", "required": true } ], "folderId": "fld_01HXEXAMPLE", "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const fn = await client.functions.create({ name: 'formatTotal', code: 'return amount / 100;' });GET/functions/{id}Auth requiredRead one custom function including user-owned code.
Parameters
Name In Type Description id* pathstring— Responses
200Function with code.403Missing function:read scope, or the plan lacks API access.404Function not found.- Common error responses
Name In Type Description data — Function— datashape (Function)Name In Type Description id* — string— name* — string— description — string | null— code* — string— Example response
json { "data": { "id": "abc123", "name": "My workflow", "code": "...", "description": "..." } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/functions/fn_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const fn = await client.functions.get(functionId); // includes user-owned codePATCH/functions/{id}Auth requiredUpdate a custom function. Requires the custom_functions plan feature (no bypass).
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description name bodystring— code bodystring— description bodystring— parameters bodyarray<FunctionParameter>— folderId bodystring | null— Responses
200Updated function.400Invalid body.403Missing function:write scope, or custom_functions not on plan.404Function not found.- Common error responses
Name In Type Description data — Function— datashape (Function)Name In Type Description id* — string— name* — string— description — string | null— code* — string— Example response
json { "data": { "id": "abc123", "name": "My workflow", "code": "...", "description": "..." } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/functions/fn_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "code": "...", "description": "...", "parameters": [ { "name": "amount", "type": "number", "description": "Order total in cents.", "required": true } ], "folderId": "fld_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const fn = await client.functions.update(functionId, { code: 'return amount / 1000;' });
Teams
6endpoints
GET/teamsAuth requiredList the organization's teams (discovery for create_workflow; requires workflow:read).
Responses
200Team summaries.403Missing workflow:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<Team>— data[]item (Team)Name In Type Description id* — string— name* — string— createdAt* — integerEpoch milliseconds. Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "createdAt": 1719792000000 } ] }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/teams \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: teams } = await client.teams.list(); // discovery for workflows.create's teamIdPATCH/teams/{teamId}Auth requiredRename a team. Requires team:write; the key's creator must be an org owner/admin or an admin of this team.
Parameters
Name In Type Description teamId* pathstring— Request body (required)
Name In Type Description name* bodystringThe new team name. Responses
200The renamed team.400Missing or too long name.403Missing team:write scope, or the key's creator may not manage this team.404Team (or team member) not found in this organization.- Common error responses
Name In Type Description data* — TeamRenameResult— datashape (TeamRenameResult)Name In Type Description id* — string— name* — string— Example response
json { "data": { "id": "abc123", "name": "My workflow" } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/teams/id_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const team = await client.teams.rename(teamId, 'Customer success');GET/teams/{teamId}/membersAuth requiredList one team's members, one keyset page at a time (requires workflow:read; any org member may view a team).
Parameters
Name In Type Description teamId* pathstring— cursor querystring— limit queryinteger— Responses
200One page of team members; next_cursor is null on the last page.400Invalid cursor or limit.403Missing workflow:read scope, or the plan lacks API access.404Team not found in this organization.- Common error responses
Name In Type Description data* — array<TeamMember>— next_cursor* — string | null— data[]item (TeamMember)Name In Type Description userId* — string— role* — stringThe team role (admin, member, developer, accountant; older rows may say team_member). createdAt* — string | nullISO 8601, when the member joined the team. Example response
json { "data": [ { "userId": "abc123", "role": "...", "createdAt": "2024-07-01T12:00:00.000Z" } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/teams/id_01HXEXAMPLE/members \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: members, nextCursor } = await client.teams.listMembers(teamId, { limit: 100 });POST/teams/{teamId}/membersAuth requiredAdd organization members to a team (1 to 100 per request; all or nothing). Requires team:write.
Parameters
Name In Type Description teamId* pathstring— Request body (required)
Name In Type Description userIds* bodyarray<string>Organization member user ids (from list_members), 1 to 100 per request. Responses
200Added and already-present members.400Invalid userIds, or a user who is not a member of the organization.403Missing team:write scope, or the key's creator may not manage this team.404Team (or team member) not found in this organization.- Common error responses
Name In Type Description data* — TeamMembersAdded— datashape (TeamMembersAdded)Name In Type Description added* — array<string>User ids added to the team. alreadyMembers* — array<string>User ids that were already in the team (nothing written). Example response
json { "data": { "added": [ "..." ], "alreadyMembers": [ "..." ] } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/teams/id_01HXEXAMPLE/members \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "userIds": [ "..." ] }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // All or nothing: every user must already be a member of the organization. Needs team:write. const { added, alreadyMembers } = await client.teams.addMembers(teamId, [userId1, userId2]);POST/teams/{teamId}/members/removeAuth requiredRemove members from a team (1 to 100 per request). Requires team:write; removing an accountant needs an org owner/admin.
Parameters
Name In Type Description teamId* pathstring— Request body (required)
Name In Type Description userIds* bodyarray<string>Organization member user ids (from list_members), 1 to 100 per request. Responses
200Removed members and the ids that were not in the team.400Invalid userIds.403Missing team:write scope, or the key's creator may not manage this team.404Team (or team member) not found in this organization.- Common error responses
Name In Type Description data* — TeamMembersRemoved— datashape (TeamMembersRemoved)Name In Type Description removed* — array<string>User ids removed from the team. notMembers* — array<string>User ids that were not in the team (nothing written). Example response
json { "data": { "removed": [ "..." ], "notMembers": [ "..." ] } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/teams/id_01HXEXAMPLE/members/remove \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "userIds": [ "..." ] }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { removed, notMembers } = await client.teams.removeMembers(teamId, [userId]);PATCH/teams/{teamId}/members/{userId}Auth requiredChange one member's team role. Requires team:write; setting or removing accountant needs an org owner/admin.
Parameters
Name In Type Description teamId* pathstring— userId* pathstring— Request body (required)
Name In Type Description role* bodystringThe new team role. accountant is set or removed by org owners and admins only. Responses
200The new team role.400Invalid role.403Missing team:write scope, or the key's creator may not manage this team.404Team (or team member) not found in this organization.- Common error responses
Name In Type Description data* — TeamMemberRoleResult— datashape (TeamMemberRoleResult)Name In Type Description teamId* — string— userId* — string— role* — string— Example response
json { "data": { "teamId": "team_01HXEXAMPLE", "userId": "abc123", "role": "..." } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/teams/id_01HXEXAMPLE/members/id_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "role": "..." }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // accountant is set or removed by org owners and admins only. const member = await client.teams.setMemberRole(teamId, userId, 'developer');
Folders
4endpoints
GET/foldersAuth requiredList the organization's folder tree (discovery for create_workflow; requires workflow:read).
Responses
200Folder summaries.403Missing workflow:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data* — array<Folder>— data[]item (Folder)Name In Type Description id* — string— name* — string— parentId* — string | null— Example response
json { "data": [ { "id": "abc123", "name": "My workflow", "parentId": "abc123" } ] }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/folders \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: folders } = await client.folders.list(); // discovery for workflows.create's folderIdPOST/foldersAuth requiredCreate a folder (requires workflow:write).
Request body (required)
Name In Type Description name* bodystring— parentId bodystring | null— Responses
201Created folder.400Invalid name or parentId.403Missing workflow:write scope.- Common error responses
Name In Type Description data* — Folder— datashape (Folder)Name In Type Description id* — string— name* — string— parentId* — string | null— Example response
json { "data": { "id": "abc123", "name": "My workflow", "parentId": "abc123" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/folders \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "parentId": "abc123" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const folder = await client.folders.create({ name: 'Marketing' });PATCH/folders/{id}Auth requiredRename or reparent a folder (requires workflow:write).
Parameters
Name In Type Description id* pathstring— Request body (required)
Name In Type Description name bodystring— parentId bodystring | null— Responses
200Updated folder.400Invalid name or parentId.403Missing workflow:write scope.404Folder not found.- Common error responses
Name In Type Description data* — Folder— datashape (Folder)Name In Type Description id* — string— name* — string— parentId* — string | null— Example response
json { "data": { "id": "abc123", "name": "My workflow", "parentId": "abc123" } }Example request
bash curl -X PATCH https://eu1.wextl.com/api/v1/folders/fld_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "name": "My workflow", "parentId": "abc123" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const folder = await client.folders.update(folderId, { name: 'Renamed' });DELETE/folders/{id}Auth requiredDelete a folder (requires workflow:write). Contents are not cascade-deleted.
Parameters
Name In Type Description id* pathstring— Responses
200Deleted.403Missing workflow:write scope.404Folder not found.- Common error responses
Name In Type Description data* — object— datashape (object)Name In Type Description ok — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/folders/fld_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Contents are NOT cascade-deleted. await client.folders.delete(folderId);
Activity
3endpoints
GET/activityAuth requiredList the organization's activity feed (audit log). Requires activity:read.
Parameters
Name In Type Description teamId querystringOnly rows of this team (from list_teams). actorUserId querystringOnly rows this user did. targetType querystringOnly rows about this kind of entity. targetId querystringOnly rows about this entity id. action querystringFull action, e.g. workflow.created. via querystringOne exact surface: api_key:<keyId>, oauth:<grantId>, assistant:<chatId>, web or system. viaKind querystringOnly rows that came through this kind of surface. since queryintegerEpoch ms lower bound (inclusive). Without it the first page covers the last 90 days. until queryintegerEpoch ms upper bound (exclusive). limit queryintegerPage size (default 25). cursor querystringThe next_cursor of the previous page. Responses
200Activity page with labeled events, the actor map and what you can see.400Invalid filter, cursor or limit.403Missing activity:read scope, or not a member of the organization.- Common error responses
Name In Type Description data* — ActivityPage— datashape (ActivityPage)Name In Type Description events* — array<ActivityEvent>— actors* — objectactorUserId → actor, for every distinct actor referenced in events. next_cursor* — string | null— visibility* — ActivityVisibility— Example response
json { "data": { "events": [ { "key": "abc123", "id": "abc123", "ts": 1719792000000, "action": "...", "label": "My workflow", "targetType": "...", "outcome": "...", "targetId": "abc123", "actorUserId": "abc123", "realAdminId": "abc123", "teamId": "team_01HXEXAMPLE", "via": "...", "viaLabel": "...", "op": "...", "saveCount": 3, "firstAt": 1719792000000, "lastAt": 1719792000000, "metadata": "..." } ], "actors": { "usr_01HXEXAMPLE": { "id": "usr_01HXEXAMPLE", "name": "Ada Lovelace", "email": "ada@example.com" } }, "next_cursor": null, "visibility": { "tier": "...", "teams": [ "..." ], "canExport": true } } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/activity \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Your own rows; team admins also their teams'; owners/admins everything. const { events, nextCursor, visibility } = await client.activity.list({ limit: 50 });GET/activity/exportAuth requiredDownload the activity you can see as CSV or JSON Lines. Requires activity:read.
Parameters
Name In Type Description format querystringFile format: csv (default) or jsonl (one JSON event per line). teamId querystringOnly rows of this team (from list_teams). actorUserId querystringOnly rows this user did. targetType querystringOnly rows about this kind of entity. targetId querystringOnly rows about this entity id. action querystringFull action, e.g. workflow.created. via querystringOne exact surface: api_key:<keyId>, oauth:<grantId>, assistant:<chatId>, web or system. viaKind querystringOnly rows that came through this kind of surface. until queryintegerEpoch ms upper bound (exclusive). since queryintegerEpoch ms lower bound (inclusive). Without it the export covers the whole retained activity log. Responses
200The file (an attachment). CSV columns: time, key, action, label, actor_user_id, actor_name, actor_email, real_admin_id, team_id, via, via_label, op, outcome, target_type, target_id, save_count, metadata. JSON Lines: one ActivityEvent plus its `actor` per line.400Invalid filter or format.403Missing activity:read scope, or not a member of the organization.500The export could not be recorded, so nothing was exported. Retry.- Common error responses
Example request
bash curl -X GET https://eu1.wextl.com/api/v1/activity/export \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const res = await client.activity.export({ format: 'csv' }); // the rows you can see, streamed const csv = await res.text();GET/activity/{key}Auth requiredGet one activity event by its key, including every save of a merged workflow autosave. Requires activity:read.
Parameters
Name In Type Description key* pathstringThe event key ("{ts}_{id}") from GET /activity. Responses
200The event, its actors and what you can see.400Malformed key.403Missing activity:read scope, or not a member of the organization.404No such event, or one you may not see.- Common error responses
Name In Type Description data* — ActivityDetail— datashape (ActivityDetail)Name In Type Description event* — object— actors* — object— visibility* — ActivityVisibility— Example response
json { "data": { "event": { "saves": [ { "ts": 0, "nodesAdded": 0, "nodesRemoved": 0, "nodesEdited": 0, "edgesAdded": 0, "edgesRemoved": 0, "changes": [ { "nodeId": "n_2", "label": "Slack › Send message", "kinds": [ "mapping" ] } ], "moreChanges": 0 } ], "overflow": { "count": 3, "firstTs": 0, "lastTs": 0, "nodesAdded": 0, "nodesRemoved": 0, "nodesEdited": 0 } }, "actors": { "usr_01HXEXAMPLE": { "id": "usr_01HXEXAMPLE", "name": "Ada Lovelace", "email": "ada@example.com" } }, "visibility": { "tier": "...", "teams": [ "..." ], "canExport": true } } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/activity/id_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { event } = await client.activity.get(events[0].key); // event.saves for a merged autosave
Invitations
4endpoints
GET/invitationsAuth requiredList organization invitations, oldest first. Without cursor or limit: up to 500 in one response, no next_cursor (the original form). With cursor or limit: one keyset page (default 100) plus next_cursor. Requires invitation:read (owner/admin).
Parameters
Name In Type Description status querystring— cursor querystring— limit queryinteger— Responses
200Invitations (each with invite_url). next_cursor is present only when cursor or limit was sent; null on the last page.400Invalid status, cursor or limit.403Missing invitation:read scope, or not an org admin.- Common error responses
Name In Type Description data* — array<Invitation>— next_cursor — string | nullOnly with cursor or limit. Pass it as cursor for the next page. data[]item (Invitation)Name In Type Description id* — string— email* — string— role — string | null— teamId — string | null— status* — string— expiresAt* — integerEpoch milliseconds. createdAt* — integerEpoch milliseconds. inviterId* — string— invite_url* — stringAbsolute URL for the invitee; relay if email delivery fails. Example response
json { "data": [ { "id": "abc123", "email": "...", "status": "succeeded", "expiresAt": 1719792000000, "createdAt": 1719792000000, "inviterId": "abc123", "invite_url": "...", "role": "...", "teamId": "team_01HXEXAMPLE" } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/invitations \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: invitations, nextCursor } = await client.invitations.list({ status: 'pending' }); // owner/admin onlyPOST/invitationsAuth requiredInvite people by email with one org role (member, admin, billing, viewer; owner only by an owner), optionally into a team. Send email (one address) for one invitation object, or emails (1 to 100) for { invitations, alreadyMembers, alreadyInvited }. Requires invitation:write; a team-limited key invites only into its teams. Returns each invite_url for headless relay.
Request body (required)
one of 2 shapes
Responses
200The emails form only - nothing created: every address was already a member or already invited.201Created. The email form: the invitation. The emails form: the bulk result (addresses already members or already invited are listed, not invited).400Invalid emails, role or teamId.403Missing invitation:write, not an org admin, an owner invite by a non-owner, a team outside the key, or the seat cap.409The email form only: a pending invitation already exists for this email, or it already belongs to a member.- Common error responses
Name In Type Description data* — InvitationsCreated— datashape (InvitationsCreated)Name In Type Description invitations* — array<Invitation>— alreadyMembers* — array<string>Addresses that already belong to a member (not invited). alreadyInvited* — array<string>Addresses with a pending invitation already (use resend_invitation). Example response
json { "data": { "invitations": [ { "id": "abc123", "email": "...", "status": "succeeded", "expiresAt": 1719792000000, "createdAt": 1719792000000, "inviterId": "abc123", "invite_url": "...", "role": "...", "teamId": "team_01HXEXAMPLE" } ], "alreadyMembers": [ "..." ], "alreadyInvited": [ "..." ] } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/invitations \ -H "Authorization: Bearer wextl_..." \ -H "Content-Type: application/json" \ -d '{ "email": "...", "role": "...", "teamId": "team_01HXEXAMPLE" }'ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const invitation = await client.invitations.create({ email: 'ana@example.com', role: 'billing', // member | admin | billing | viewer | owner (owners only) }); // Several at once (up to 100): members and pending invitees are reported, not invited again. const { invitations, alreadyMembers, alreadyInvited } = await client.invitations.createMany({ emails: ['li@example.com', 'sam@example.com'], role: 'viewer', }); // inviteUrl on each invitation — relay it if email delivery fails.DELETE/invitations/{id}Auth requiredCancel a pending invitation. Requires invitation:write.
Parameters
Name In Type Description id* pathstring— Responses
200Cancelled.403Missing invitation:write scope, or not an org admin.404Invitation not found.409Invitation is not pending.- Common error responses
Name In Type Description data* — object— datashape (object)Name In Type Description ok — boolean— Example response
json { "data": { "ok": true } }Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/invitations/inv_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); await client.invitations.cancel(invitationId);POST/invitations/{id}/resendAuth requiredSend a pending invitation's email again with a fresh 48-hour link. Requires invitation:write (decided with the invitation's own role and team).
Parameters
Name In Type Description id* pathstring— Responses
200The invitation with its new expiry.403Missing invitation:write scope, not an org admin, or an owner invitation resent by a non-owner.404Invitation not found.409Invitation is not pending.- Common error responses
Name In Type Description data* — Invitation— datashape (Invitation)Name In Type Description id* — string— email* — string— role — string | null— teamId — string | null— status* — string— expiresAt* — integerEpoch milliseconds. createdAt* — integerEpoch milliseconds. inviterId* — string— invite_url* — stringAbsolute URL for the invitee; relay if email delivery fails. Example response
json { "data": { "id": "abc123", "email": "...", "status": "succeeded", "expiresAt": 1719792000000, "createdAt": 1719792000000, "inviterId": "abc123", "invite_url": "...", "role": "...", "teamId": "team_01HXEXAMPLE" } }Example request
bash curl -X POST https://eu1.wextl.com/api/v1/invitations/inv_01HXEXAMPLE/resend \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const invitation = await client.invitations.resend(invitationId); // fresh 48-hour link
Webhooks
2endpoints
GET/webhooksAuth requiredList inbound webhooks (requires workflow:read).
Parameters
Name In Type Description cursor querystring— limit queryinteger— q querystring— Responses
200Webhook page.403Missing workflow:read scope.- Common error responses
Name In Type Description data* — array<WebhookSummary>— next_cursor* — string | null— data[]item (WebhookSummary)Name In Type Description id* — string— name* — string— url* — string— enabled* — boolean— method* — string— workflowId* — string | null— nodeId* — string | null— createdAt* — integerEpoch milliseconds. Example response
json { "data": [ { "id": "wh_01HXEXAMPLE", "name": "Webhook", "url": "https://eu1.wextl.com/hooks/wh_01HXEXAMPLE", "enabled": true, "method": "POST", "workflowId": "wf_01HXEXAMPLE", "nodeId": "12", "createdAt": 1719792000000 } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/webhooks \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: webhooks, nextCursor } = await client.webhooks.list();DELETE/webhooks/{id}Auth requiredDelete a webhook. Pass force=1 if still bound to a workflow.
Parameters
Name In Type Description id* pathstring— force querystring— Responses
204Deleted.403Missing workflow:write scope.404Webhook not found.409Webhook in use — pass force=1.- Common error responses
Example request
bash curl -X DELETE https://eu1.wextl.com/api/v1/webhooks/wh_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // Pass { force: true } if it's still bound to a workflow node, else this is refused (409). await client.webhooks.delete(webhookId, { force: true });
Members
1endpoint
GET/membersAuth requiredList organization members, bounded and cursor-paginated (requires invitation:read / owner-admin).
Parameters
Name In Type Description cursor querystringOpaque continuation token from a previous response. limit queryintegerPage size. Default 100. Responses
200A page of the member roster.403Missing invitation:read scope, or not an org admin.- Common error responses
Name In Type Description data* — array<Member>— next_cursor* — string | null— data[]item (Member)Name In Type Description userId* — string— email* — string | null— name* — string | null— role* — string— createdAt* — integerEpoch milliseconds. Example response
json { "data": [ { "userId": "usr_01HXEXAMPLE", "email": "ada@example.com", "name": "Ada Lovelace", "role": "admin", "createdAt": 1719792000000 } ], "next_cursor": null }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/members \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const { data: members, nextCursor } = await client.members.list({ limit: 100 }); // owner/admin only
Catalog
3endpoints
GET/catalog/appsAuth requiredSearch the app and module catalog by keyword. Use the returned app and module ids when creating or updating workflow nodes.
Parameters
Name In Type Description query* querystringSearch keywords (app or capability name). limit queryintegerMax apps to return. Default 10. Responses
200Matching apps with their modules.400query is required.403Missing workflow:read scope, or the plan lacks API access.- Common error responses
Name In Type Description data — array<CatalogApp>— data[]item (CatalogApp)Name In Type Description appId* — string— name* — string— description — string | null— modules* — array<object>— Example response
json { "data": [ { "appId": "slack", "name": "My workflow", "modules": [ { "id": "abc123", "label": "My workflow", "type": "action" } ], "description": "..." } ] }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/catalog/apps \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const apps = await client.catalog.search({ query: 'slack', limit: 5 });GET/catalog/apps/{appId}/modules/{moduleId}Auth requiredRead a module's parameter schema and metadata. Does not expose internal request implementation details.
Parameters
Name In Type Description appId* pathstring— moduleId* pathstring— Responses
200The module spec (lean projection — meta + mappable/static fields).400appId and moduleId are required, or the module cannot be projected for the public API.403Missing workflow:read scope, or the plan lacks API access.404Module not found.- Common error responses
Name In Type Description data — ModuleSpec— datashape (ModuleSpec)Name In Type Description meta* — objectCompact module metadata — label, description, which connection it needs, etc. Shape varies per module. mappableFields* — array<object>The fields the caller fills at run time (bindable to upstream data). Shape depends on the field type. staticFields* — array<object>Fixed UI-configuration params for this module — often empty. Shape depends on the field type. emitsMultipleBundles — boolean— requiresConnection — boolean— connectionOptional — boolean— Example response
json { "data": { "meta": { "label": "Send a message", "description": "Post a message to a channel.", "connection": "oauth2" }, "mappableFields": [ { "name": "channel", "label": "Channel", "type": "select", "required": true } ], "staticFields": [ { "name": "includeAttachments", "label": "Include attachments", "type": "boolean", "required": false } ], "emitsMultipleBundles": true, "requiresConnection": true, "connectionOptional": true } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/catalog/apps/slack/modules/send_message \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); const spec = await client.catalog.getModuleSpec('slack', 'sendMessage');GET/catalog/nodes/{kind}Auth requiredRead the machine-readable spec for a NATIVE (built-in) workflow node kind — where its data persists, the exact field shape, a copyable example, and its outgoing routes. App/module nodes are not covered here; use GET /catalog/apps/{appId}/modules/{moduleId} for those.
Parameters
Name In Type Description kind* pathstringNative node kind, e.g. 'condition', 'splitter', 'wait', 'js_code'. Responses
200The native node-kind spec.403Missing workflow:read scope, or the plan lacks API access.404Unknown kind — use search_catalog for app/module nodes.- Common error responses
Name In Type Description data — NodeSpec— datashape (NodeSpec)Name In Type Description kind* — string— label* — string— description* — string— insertable* — booleanTrue when this kind appears in the builder palette. persists_under* — stringWhere this kind's data lives on the node. shape* — objectField name -> { type, required, description }. example* — objectA filled config/params object for this kind, ready to copy into update_config / update_params. routes* — array<string>Outgoing edge types this kind can emit, e.g. 'normal', 'error', 'fallback'. expressionGrammar* — stringHow to reference upstream values inside wire strings. edgeModel* — stringEdge topology notes — how to wire this kind's outgoing/incoming edges. Example response
json { "data": { "kind": "condition", "label": "Condition", "description": "Branches execution based on a boolean expression.", "insertable": true, "persists_under": "config", "shape": { "conditions": { "type": "array", "required": true, "description": "Ordered list of condition branches." } }, "example": { "conditions": [ { "expression": "{{1.status}} == \"ok\"", "label": "OK" } ] }, "routes": [ "normal" ], "expressionGrammar": "{{nodeId.path}} references an upstream node's output.", "edgeModel": "Each condition branch has its own outgoing port, in the order listed." } }Example request
bash curl -X GET https://eu1.wextl.com/api/v1/catalog/nodes/id_01HXEXAMPLE \ -H "Authorization: Bearer wextl_..."ts import { WextlClient } from '@wextl/sdk'; const client = new WextlClient({ apiKey: 'wextl_...' }); // NATIVE (built-in) node kinds only — app/module nodes use get_module_spec instead. const spec = await client.catalog.getNodeSpec('condition');