Skip to Content
API ReferenceWorkflows API

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:

  1. Enable API Touchpoint - In the workflow editor, enable “API” as a touchpoint
  2. Define Input Schema - Specify the expected input structure
  3. 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:

ParameterTypeDescription
workflowKeyString!Unique workflow identifier
inputJSON!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:

StatusDescription
PENDINGQueued, not yet started
RUNNINGCurrently executing
COMPLETEDFinished successfully
FAILEDFinished with error
CANCELLEDManually 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); }
Last updated on