LoopFour

API Triggers

Trigger workflows programmatically via the REST API

API Triggers

API triggers allow you to start workflows programmatically via the REST API. This is useful for on-demand processing, testing, or integrating with systems that don't support webhooks.

Configuration

API triggers have the simplest configuration:

{
  "trigger": {
    "type": "api"
  }
}
FieldTypeRequiredDescription
typestringYesMust be "api"

That's it! The workflow can now be triggered via the REST API.

Running a Workflow

Basic Request

curl -X POST http://localhost:3000/api/v1/workflows/{WORKFLOW_ID}/run \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": {
      "customerId": "cus_xxx",
      "amount": 9900
    }
  }'

Request Body

{
  "input": {
    // Your workflow input data
  },
  "connectionId": "conn_xxx",
  "idempotencyKey": "unique-key-123"
}
FieldTypeRequiredDescription
inputobjectNoInput data passed to the workflow
connectionIdstringNoSpecific connection to use
idempotencyKeystringNoPrevent duplicate runs

Response

{
  "success": true,
  "data": {
    "runId": "run_xyz789...",
    "status": "pending",
    "workflowId": "wf_abc123...",
    "workflowVersion": 3
  }
}
FieldDescription
runIdUnique identifier for this run
statusInitial status (pending)
workflowIdThe workflow being executed
workflowVersionVersion of the workflow used

Input Data

The input object is available throughout your workflow as {{input}}:

Request:

{
  "input": {
    "customer": {
      "id": "cus_xxx",
      "email": "customer@example.com"
    },
    "items": [
      { "product": "Widget", "quantity": 2, "price": 1999 }
    ]
  }
}

In workflow steps:

{
  "config": {
    "to": "{{input.customer.email}}",
    "subject": "Order for {{input.items.0.product}}"
  }
}

Input Validation

Define expected input with a JSON schema at the workflow level:

{
  "name": "Create Invoice",
  "trigger": {
    "type": "api"
  },
  "inputSchema": {
    "type": "object",
    "required": ["customerId", "amount"],
    "properties": {
      "customerId": {
        "type": "string",
        "pattern": "^cus_[a-zA-Z0-9]+$"
      },
      "amount": {
        "type": "number",
        "minimum": 100
      },
      "currency": {
        "type": "string",
        "enum": ["usd", "eur", "gbp"],
        "default": "usd"
      }
    }
  },
  "steps": [...]
}

If validation fails, the API returns 400 Bad Request:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input validation failed",
    "details": [
      {
        "path": ["customerId"],
        "message": "Required"
      }
    ]
  }
}

Idempotency

Prevent duplicate workflow runs with idempotency keys:

curl -X POST http://localhost:3000/api/v1/workflows/{id}/run \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": { "orderId": "order_123" },
    "idempotencyKey": "process-order-123"
  }'

If the same idempotencyKey is used again within 24 hours:

  • The API returns the existing run instead of creating a new one
  • No duplicate processing occurs

Best practices:

  • Use meaningful keys: process-order-{orderId}, invoice-{invoiceId}
  • Include relevant identifiers from your input
  • Keys expire after 24 hours

Checking Run Status

After triggering a workflow, poll for completion:

curl http://localhost:3000/api/v1/runs/{RUN_ID} \
  -H "x-api-key: YOUR_API_KEY"

Run Status Response

{
  "success": true,
  "data": {
    "id": "run_xyz789...",
    "status": "completed",
    "input": { "customerId": "cus_xxx" },
    "output": { "invoiceId": "inv_123" },
    "startedAt": "2024-01-15T10:00:00.000Z",
    "completedAt": "2024-01-15T10:00:05.000Z",
    "steps": [
      {
        "id": "create-invoice",
        "status": "completed",
        "output": { "id": "inv_123" }
      }
    ]
  }
}

Specifying a Connection

If your workflow uses multiple connections for the same provider, specify which one to use:

{
  "input": { ... },
  "connectionId": "conn_production_stripe"
}

This is useful when:

  • You have separate development and production connections
  • Different customers use different OAuth connections
  • You need to switch between sandbox and live environments

Error Handling

Common Error Responses

Workflow not found (404):

{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Workflow not found"
  }
}

Workflow not active (400):

{
  "success": false,
  "error": {
    "code": "INVALID_STATUS",
    "message": "Workflow must be active to run. Current status: draft"
  }
}

Rate limited (429):

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Limit: 500 requests per minute."
  }
}

Invalid input (400):

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input validation failed",
    "details": [...]
  }
}

Rate Limits

API trigger endpoints have these limits:

CategoryLimit
Execute500 requests/minute per API key

See Authentication for all rate limit details.

Use Cases

On-Demand Processing

Trigger workflows from your application:

// When user clicks "Generate Report"
await triggerWorkflow('wf_report_generator', {
  input: {
    reportType: 'monthly',
    startDate: '2024-01-01',
    endDate: '2024-01-31',
    format: 'pdf'
  }
});

Testing Workflows

Test webhook workflows without setting up actual webhooks:

# Simulate a Stripe webhook
curl -X POST http://localhost:3000/api/v1/workflows/{id}/run \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "input": {
      "type": "invoice.paid",
      "data": {
        "object": {
          "id": "in_test",
          "amount_paid": 9900
        }
      }
    }
  }'

Batch Processing

Trigger multiple workflow runs for batch operations:

const customers = await getCustomersToProcess();
 
const runs = await Promise.all(
  customers.map(customer =>
    triggerWorkflow('wf_customer_sync', {
      input: { customerId: customer.id },
      idempotencyKey: `sync-${customer.id}-${today}`
    })
  )
);
 
console.log(`Started ${runs.length} sync workflows`);

Scheduled via External Scheduler

If you use an external scheduler (cron job, cloud scheduler):

# In your crontab or cloud scheduler
0 9 * * * curl -X POST https://api.example.com/api/v1/workflows/wf_daily_report/run \
  -H "x-api-key: $API_KEY" \
  -d '{"input":{}}'

Complete Example

{
  "name": "Process Customer Order",
  "description": "Create invoice and send confirmation for customer order",
  "trigger": {
    "type": "api"
  },
  "inputSchema": {
    "type": "object",
    "required": ["customerId", "items"],
    "properties": {
      "customerId": { "type": "string" },
      "items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "productId": { "type": "string" },
            "quantity": { "type": "number", "minimum": 1 },
            "price": { "type": "number", "minimum": 0 }
          }
        }
      },
      "notes": { "type": "string" }
    }
  },
  "steps": [
    {
      "id": "calculate-total",
      "type": "transform",
      "name": "Calculate Order Total",
      "config": {
        "mapping": {
          "subtotal": "{{input.items | map: 'quantity * price' | sum}}",
          "tax": "{{input.items | map: 'quantity * price' | sum | times: 0.08}}",
          "total": "{{input.items | map: 'quantity * price' | sum | times: 1.08}}"
        }
      }
    },
    {
      "id": "create-invoice",
      "type": "action",
      "name": "Create Invoice",
      "action": "stripe.createInvoice",
      "config": {
        "customerId": "{{input.customerId}}",
        "amount": "{{steps.calculate-total.output.total}}",
        "metadata": {
          "source": "api-order"
        }
      }
    },
    {
      "id": "send-confirmation",
      "type": "email",
      "name": "Send Confirmation",
      "config": {
        "action": "send",
        "to": "{{input.customerEmail}}",
        "subject": "Order Confirmed - Invoice #{{steps.create-invoice.output.number}}",
        "body": {
          "html": "<p>Thank you for your order!</p><p>Total: ${{steps.calculate-total.output.total}}</p>"
        }
      }
    }
  ]
}

Troubleshooting

Workflow Not Starting

  1. Check workflow status - Must be active
  2. Verify API key - Needs workflows:execute scope
  3. Check rate limits - May be rate limited

Input Not Available

  1. Verify input structure - Check JSON is valid
  2. Check template syntax - Use {{input.field}}
  3. Review inputSchema - May be stripping unknown fields

Run Stuck in Pending

  1. Check Trigger.dev - Ensure dev mode is running
  2. Verify connections - Required connections must be active
  3. Review logs - Check for execution errors

Next Steps