Workflows API
The Workflows API allows you to execute custom workflows via the Public API. Workflows are automated processes defined in the Foir admin that can be triggered externally.
Overview
Workflows provide a way to:
- Process form submissions - Contact forms, newsletter signups
- Trigger integrations - Send data to external services
- Automate content operations - Create/update entities programmatically
- Run custom business logic - Validation, calculations, transformations
Prerequisites
To execute a workflow via API:
- Enable API Touchpoint - In the workflow editor, enable “API” as a touchpoint
- Define Input Schema - Specify the expected input structure
- Set Workflow Key - Unique identifier for API access
Mutations
publicExecuteWorkflow
Execute a workflow with input data.
mutation ExecuteWorkflow($workflowKey: String!, $input: JSON!) {
publicExecuteWorkflow(input: {
workflowKey: $workflowKey
input: $input
}) {
success
executionId
queued
result
error {
code
message
}
}
}Parameters:
| Parameter | Type | Description |
|---|---|---|
workflowKey | String! | Unique workflow identifier |
input | JSON! | Input data matching workflow schema |
Example - Contact Form:
mutation SubmitContactForm {
publicExecuteWorkflow(input: {
workflowKey: "contact-form-handler"
input: {
name: "John Doe"
email: "john@example.com"
message: "Hello, I have a question..."
source: "website"
}
}) {
success
executionId
result
}
}Response (Synchronous):
{
"data": {
"publicExecuteWorkflow": {
"success": true,
"executionId": "exec_abc123",
"queued": false,
"result": {
"ticketId": "TICKET-001",
"status": "created"
}
}
}
}Response (Asynchronous):
{
"data": {
"publicExecuteWorkflow": {
"success": true,
"executionId": "exec_abc123",
"queued": true,
"result": null
}
}
}publicCancelWorkflowExecution
Cancel a running workflow execution.
mutation CancelExecution($id: ID!) {
publicCancelWorkflowExecution(id: $id) {
id
workflowKey
status
}
}Queries
publicWorkflowExecution
Get the status of a workflow execution.
query GetExecution($id: ID!) {
publicWorkflowExecution(id: $id) {
id
workflowKey
status
result
error {
code
message
}
createdAt
completedAt
}
}Execution Status:
| Status | Description |
|---|---|
PENDING | Queued, not yet started |
RUNNING | Currently executing |
COMPLETED | Finished successfully |
FAILED | Finished with error |
CANCELLED | Manually cancelled |
publicWorkflowExecutions
List workflow executions with filters.
query ListExecutions($workflowKey: String, $status: PublicWorkflowExecutionStatus) {
publicWorkflowExecutions(
workflowKey: $workflowKey
status: $status
limit: 20
offset: 0
) {
items {
id
workflowKey
status
createdAt
completedAt
}
total
}
}Response Types
PublicExecuteWorkflowResult
type PublicExecuteWorkflowResult {
success: Boolean! # Whether execution started successfully
executionId: ID # ID for tracking async executions
queued: Boolean # True if async, false if sync
result: JSON # Result data (sync only)
error: PublicWorkflowExecutionError
}PublicWorkflowExecution
type PublicWorkflowExecution {
id: ID!
workflowKey: String!
status: PublicWorkflowExecutionStatus!
result: JSON # Final result (when completed)
error: PublicWorkflowExecutionError
createdAt: DateTime!
completedAt: DateTime # When finished
}PublicWorkflowExecutionError
type PublicWorkflowExecutionError {
code: String! # Error code
message: String! # Human-readable message
}Examples
Newsletter Signup
mutation NewsletterSignup($email: String!) {
publicExecuteWorkflow(input: {
workflowKey: "newsletter-signup"
input: {
email: $email
source: "footer-form"
subscribedAt: "2024-01-15T10:30:00Z"
}
}) {
success
result
error { code message }
}
}Product Inquiry
mutation ProductInquiry($productId: String!, $question: String!) {
publicExecuteWorkflow(input: {
workflowKey: "product-inquiry"
input: {
productId: $productId
question: $question
customerEmail: "customer@example.com"
}
}) {
success
executionId
}
}Polling for Async Results
async function waitForWorkflowResult(executionId: string) {
const MAX_ATTEMPTS = 30;
const POLL_INTERVAL = 2000; // 2 seconds
for (let i = 0; i < MAX_ATTEMPTS; i++) {
const { data } = await client.query({
query: GET_EXECUTION,
variables: { id: executionId },
fetchPolicy: 'network-only'
});
const execution = data.publicWorkflowExecution;
if (execution.status === 'COMPLETED') {
return execution.result;
}
if (execution.status === 'FAILED') {
throw new Error(execution.error?.message || 'Workflow failed');
}
if (execution.status === 'CANCELLED') {
throw new Error('Workflow was cancelled');
}
// Still pending/running, wait and retry
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
}
throw new Error('Workflow execution timeout');
}
// Usage
const result = await client.mutate({
mutation: EXECUTE_WORKFLOW,
variables: {
workflowKey: 'long-running-task',
input: { /* ... */ }
}
});
if (result.data.publicExecuteWorkflow.queued) {
const finalResult = await waitForWorkflowResult(
result.data.publicExecuteWorkflow.executionId
);
console.log('Final result:', finalResult);
}Error Handling
Workflow Not Found
{
"errors": [{
"message": "Workflow not found or not accessible via API",
"extensions": { "code": "NOT_FOUND" }
}]
}Cause: Workflow doesn’t exist or API touchpoint not enabled.
Input Validation Error
{
"data": {
"publicExecuteWorkflow": {
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input: 'email' is required"
}
}
}
}Cause: Input doesn’t match workflow’s input schema.
Execution Error
{
"data": {
"publicWorkflowExecution": {
"id": "exec_123",
"status": "FAILED",
"error": {
"code": "STEP_FAILED",
"message": "Email service returned error: rate limited"
}
}
}
}Cause: Error during workflow execution (e.g., external service failure).
Best Practices
1. Use Idempotency Keys
For critical workflows, include an idempotency key:
mutation CreateOrder($input: JSON!) {
publicExecuteWorkflow(input: {
workflowKey: "create-order"
input: {
...($input)
_idempotencyKey: "order-123-attempt-1"
}
}) {
success
executionId
}
}2. Handle Async Workflows
Always check the queued field:
const result = await executeWorkflow(input);
if (result.queued) {
// Show "Processing..." UI
// Poll for results or use webhooks
} else {
// Use result.result immediately
}3. Validate Input Client-Side
Validate input before sending to reduce API errors:
const schema = z.object({
email: z.string().email(),
name: z.string().min(1),
});
const validated = schema.parse(formData);
await executeWorkflow(validated);4. Set Reasonable Timeouts
For sync workflows, set appropriate timeouts:
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);
try {
const result = await fetch('/graphql', {
signal: controller.signal,
// ...
});
} finally {
clearTimeout(timeout);
}