JustPaid Workflows
Triggers

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
    }
  }'
const response = await fetch(
  `http://localhost:3000/api/v1/workflows/${workflowId}/run`,
  {
    method: 'POST',
    headers: {
      'x-api-key': process.env.API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      input: {
        customerId: 'cus_xxx',
        amount: 9900
      }
    })
  }
);

const { data } = await response.json();
console.log('Run ID:', data.runId);
import requests

response = requests.post(
    f'http://localhost:3000/api/v1/workflows/{workflow_id}/run',
    headers={
        'x-api-key': api_key,
        'Content-Type': 'application/json'
    },
    json={
        'input': {
            'customerId': 'cus_xxx',
            'amount': 9900
        }
    }
)

data = response.json()['data']
print(f"Run ID: {data['runId']}")

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"
async function waitForCompletion(runId, maxWait = 60000) {
  const start = Date.now();

  while (Date.now() - start < maxWait) {
    const response = await fetch(
      `http://localhost:3000/api/v1/runs/${runId}`,
      { headers: { 'x-api-key': API_KEY } }
    );

    const { data } = await response.json();

    if (data.status === 'completed') {
      return data.output;
    }

    if (data.status === 'failed') {
      throw new Error(data.error?.message || 'Workflow failed');
    }

    // Wait 1 second before polling again
    await new Promise(r => setTimeout(r, 1000));
  }

  throw new Error('Timeout waiting for workflow');
}

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

On this page