Client Overview
The workflows-client library provides an easy way to interact with the Workflows API.
Initialization
To use the client, you need to create an instance of WorkflowsClient.
import { WorkflowsClient } from '@picsart/workflows-client';
const workflows = new WorkflowsClient({
apiKey: 'Bearer <YOUR_API_KEY>'
})
Client Options
| Option | Type | Description |
|---|---|---|
apiKey | string | User access token. Sent as Authorization: Bearer <apiKey> |
identityToken | string | Client/app token. Sent as x-app-authorization: Bearer <identityToken>. |
baseUrl | string | API base URL. Defaults to https://api.picsart.com/. |
fetch | typeof fetch | Custom fetch implementation. Useful in environments like the Miniapp SDK where requests must go through a sandboxed transport. |
getRemoteSettings | (name, tag?) => Promise<ApiSettings> | Resolver for remote settings (e.g. executionMode, configId) — see Settings Integration. |
headers | HeadersInit | Default headers applied to every request. A plain object, a Headers instance, or an array of entries. |
socket | SocketLike | An existing socket.io socket for ExecutionMode.SOCKET — you own its lifecycle. See Socket Events. |
socketConnection | SocketConnectionOptions | Connection config to let the client create + own a socket (needs socket.io-client). See Socket Events. |
If neither apiKey nor identityToken is provided and no custom fetch is configured, requests throw a WorkflowsError (reason: 'invalid_state', message apiKey is not provided). When you pass a custom fetch, auth headers can be injected there instead.
Example in Miniapp SDK context
import { WorkflowsClient } from '@picsart/workflows-client';
const workflows = new WorkflowsClient({
baseUrl: apiprocess.env.REACT_APP_API_URL,
fetch: getContext().utils.api.fetch,
getRemoteSettings: getContext().handlers.getRemoteSettings
})
Basic Usage
The below example will execute the example-task workflow and return its result
const params = {}; // request body
const { result, status, usage } = await workflows.run<MyResponseType>('example-task', params);
run() resolves to the task record (WorkflowResponse):
| Field | Description |
|---|---|
result | The workflow's response. |
status | The terminal status the task reached. In ASYNC mode a task that ends FAILED resolves (it doesn't reject) — check status. |
usage | Credit-charging details when credits are configured: the tool that was charged, the credits per single use, the number of uses charged, the total credits, and an optional per-sub-task breakdown. |
id, updated, progress, events | The task id, last update time, progress, and emitted events. Filled in ASYNC and SYNC modes; STREAM and SOCKET return only result, usage, and status. |
Type-safe execution
Generated workflow types are experimental — their shape may change.
run() infers params and result types from the workflow name for every workflow that has generated types in
@picsart/workflows-types,
a peer dependency of @picsart/workflows-client. Update it periodically to stay in sync with the latest generated types.
// params and result are inferred from the "example-task" workflow name
const { result } = await workflows.run('example-task', {
name: 'Joe',
});
Workflows without generated types take unknown params and return the result type you pass (run<MyResponseType>(...)).
Passing the type argument explicitly also turns inference off for a workflow that does have generated types.
Execution Modes
The service supports four execution modes:
Asynchronous Mode (Default)
Async is the default mode, where the task executed asynchronously with polling to check for completion.
const result = await workflows.run<MyResponseType>(
'example-task',
params,
{
mode: ExecutionMode.ASYNC, // Optional: Execution mode
pollingInterval: 300, // Optional: Custom polling interval in ms
retriesCount: 150, // Optional: Number of max polling attempts
onAccepted: (taskId) => console.log('Task accepted:', taskId), // optional
onPartialResult: (response) => console.log('Progress:', response) // optional
}
);
Synchronous Mode
Use sync mode if the task is expected to complete quickly (typically under 5 seconds).
const result = await workflows.run<MyResponseType>(
'example-task',
params,
{mode: ExecutionMode.SYNC}
);
Stream Mode
Stream mode maintains a persistent connection and provides intermediate results in real-time. This mode is ideal when updates need to be delivered with minimal delay
import { WorkflowEvent } from '@picsart/workflows-client';
const result = await workflows.run<MyResponseType>(
'example-task',
params,
{
mode: ExecutionMode.STREAM,
onEvent: (event: WorkflowEvent) => console.log('Event received: ', event)
},
);
Here onEvent is triggered whenever the task fires custom event (it is not triggered by framework-native events)
Consequently, the redundant event. prefix is omitted from the event.type
Please note that the return value might differ from the one returned in sync/async modes as due to specifics of streaming implementations, some tasks don't compute the final aggregated result and return empty result instead.
Socket Mode
Socket mode runs the task asynchronously but delivers its events pushed over the socket gateway
(low latency, no polling) instead of polled. It requires a socket or socketConnection on the
client. run() resolves with the result just like async.
import { ExecutionMode, WorkflowEvent } from '@picsart/workflows-client';
const { result } = await workflows.run<MyResponseType>('example-task', params, {
mode: ExecutionMode.SOCKET,
onProgress: (p) => console.log('progress:', p),
onEvent: (e: WorkflowEvent) => console.log('event:', e),
});
See Socket Events for setup (the socket + tokens), watching an existing task
with workflows.subscribe(...), closing the socket with workflows.disconnect(), and reconnection
behavior.
Execution Options
Every option accepted as the third argument of run():
| Option | Type | Description |
|---|---|---|
mode | ExecutionMode | ASYNC (default), SYNC, STREAM, or SOCKET (async submit + pushed events — see Socket Events). Any of these may be overridden by remote settings — see Settings Integration. |
remoteSettingName | string | Override the default remote-settings lookup name. Defaults to ${taskName}_api. |
pollingInterval | number | ASYNC only. Delay between polls in ms. Defaults to 300. |
retriesCount | number | ASYNC only. Maximum number of poll attempts before timing out. Defaults to 1000. |
onAccepted | (taskId) => void | ASYNC/SOCKET. Fires after the task is submitted, with the assigned task id. |
onProgress | (progress) => void | Receives { percent, estimatedSecondsLeft? } progress updates. |
onPartialResult | (partial) => void | Receives the most recent partial result emitted by the workflow. |
onEvent | (event) => void | Receives custom events emitted via flow.emitEvent(). Required for STREAM. |
notificationConfig | object | ASYNC/SOCKET. Forwarded as the request body's notification field — see Async API. |
headers | HeadersInit | Per-call headers, merged on top of the client-level headers. In ASYNC mode they are sent with the submit request, not with the polls. |
abortSignal | AbortSignal | Cancels the in-flight call when aborted. |
Cancellation
Pass an AbortSignal via abortSignal to cancel an in-flight call:
const controller = new AbortController();
const promise = workflows.run<MyResponseType>('example-task', params, {
abortSignal: controller.signal,
});
// later
controller.abort();
Aborting rejects the run() promise with a DOMException named AbortError. For ASYNC the polling loop stops before its next poll; for SOCKET the run stops listening and leaves the channel; for SYNC and STREAM the underlying fetch is cancelled.
Submitting Without Waiting
submit() starts a task and resolves with its id, without waiting for the result. Only headers, notificationConfig, and remoteSettingName apply:
const taskId = await workflows.submit('example-task', params);
Consume the result later with runPolling(), result(), or subscribe().
Resuming a Submitted Task
If you already have a task id from a previous submission (e.g. saved before a page reload), you can poll for its result without re-submitting:
const { result, status, usage } = await workflows.runPolling<MyResponseType>(
'example-task',
taskId,
{ pollingInterval: 500 },
);
runPolling accepts the same pollingInterval, retriesCount, onProgress, onPartialResult, onEvent, and abortSignal options as run().
Polling rides out short connection drops: a poll that doesn't reach the server is retried with backoff, up to 10 times in a row (about 30 seconds at the default interval), before the run fails with reason: 'connection_error'.
Checking a Task Once
result() fetches the task's current state with a single request — no polling. result may still be empty or partial, so read status:
const { status, progress, result } = await workflows.result<MyResponseType>('example-task', taskId);
Previewing Options
options() returns what the workflow would cost the current user for the given input, before running it — see the Options API:
const { credits, originalCredits, monetization } = await workflows.options('example-task', params);
Dismissing a Notification
disableNotification(taskId) marks the task's notification as seen. Socket-mode runs do this automatically; call it yourself when you consume results another way (e.g. submit() + subscribe()).
Execution History
executionsHistory(taskName, offset?, limit?, isGrouped?) fetches past executions of a workflow for the current user:
const history = await workflows.executionsHistory<MyResponseType>(
'example-task',
0, // offset, defaults to 0
10, // limit, defaults to 10
false // isGrouped, defaults to false
);
Each entry includes the task id, created timestamp, status, submitted params, and an array of result values (the array shape supports grouped responses).
Error Handling
Every client method rejects with a single error class, WorkflowsError, exported from @picsart/workflows-client. Aborting is the only exception — it rejects with a DOMException (see Cancellation).
| Field | Description |
|---|---|
message | The error text, as returned by the API when the server answered. |
reason | Machine-readable code, e.g. the API's own reason or one of the client reasons below. |
httpStatusCode | The HTTP status. undefined when the request never got an answer (e.g. connection_error). |
Reasons set by the client itself:
reason | When |
|---|---|
request_failed | The server returned an error response without its own reason. |
client_timeout | ASYNC polling used up retriesCount before the task finished (408). |
connection_error | Polling couldn't reach the server after repeated retries. |
invalid_response | The server returned a body that isn't JSON. |
workflow_failed | The workflow failed during a STREAM or SOCKET run. |
invalid_state | The client isn't set up for the call, e.g. no auth or no socket for SOCKET mode (400). |
unknown_error | Any other unexpected failure. |
import { WorkflowsError } from '@picsart/workflows-client';
try {
const { result } = await workflows.run<MyResponseType>('example-task', params);
} catch (err) {
if (err instanceof WorkflowsError) {
if (err.httpStatusCode === 402) {
// not enough credits
} else if (err.httpStatusCode === undefined || err.httpStatusCode >= 500) {
// connection or server failure — retry-friendly
}
}
throw err;
}
Settings Integration
The run method supports an optional executionOptions.remoteSettingName parameter, which allows you to
specify the name of the Remote Setting where your workflow configuration is defined.
If not provided, the default setting name will be: ${taskName}_api
By default, the execution options defined in the Remote Setting will override the options explicitly passed to the
run method.