> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trycordage.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks & API

> Trigger workflows programmatically from external applications

Webhooks enable you to trigger Cordage workflows via HTTP API, making it easy to integrate AI workflows into your applications, automation pipelines, or external services.

## Overview

A webhook exposes your workflow as an API endpoint:

```mermaid theme={null}
flowchart LR
    A[Your App] -->|HTTP POST| B[Cordage API]
    B --> C[Workflow Execution]
    C --> D[Output URLs]
```

## Setting Up Webhooks

### 1. Add a Webhook Node

1. Drag a **Webhook** node onto your canvas
2. Position it as the entry point of your workflow
3. A unique webhook key is automatically generated

### 2. Configure Inputs

The Webhook node can receive input parameters:

```mermaid theme={null}
flowchart LR
    subgraph Webhook["Webhook Node"]
        P1["prompt (text)"]
        P2["style (text)"]
        P3["image_url (string)"]
    end
    P1 --> Model[Model]
    P2 --> Model
    P3 --> Image[Image Input]
    Image --> Model
```

Map webhook inputs to downstream node parameters.

### 3. Connect to Export

Ensure your workflow ends with an Export node:

```mermaid theme={null}
flowchart LR
    A[Webhook] --> B[Process]
    B --> C[...]
    C --> D[Export]
```

The Export node's output URLs are returned in the API response.

## API Authentication

All webhook API calls require authentication:

```bash theme={null}
Authorization: Bearer cordage_xxxxxxxxxxxx
```

### Getting an API Key

1. Go to **Workspace Settings**
2. Navigate to **API Keys**
3. Click **Create API Key**
4. Copy and store securely (shown only once)

<Warning>
  API keys are workspace-scoped. Keep them secret and never expose in client-side code.
</Warning>

## API Endpoints

### Trigger Workflow

Start a workflow run:

```bash theme={null}
POST https://api.trycordage.com/api/webhooks/{webhook_key}/trigger
```

**Request:**

```bash theme={null}
curl -X POST https://api.trycordage.com/api/webhooks/wh_abc123/trigger \
  -H "Authorization: Bearer cordage_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A beautiful sunset",
    "style": "photorealistic"
  }'
```

**Response:**

```json theme={null}
{
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "running",
  "message": "Workflow started",
  "node_count": 5
}
```

### Get Run Status

Check execution progress:

```bash theme={null}
GET https://api.trycordage.com/api/webhooks/{webhook_key}/runs/{run_id}
```

**Response (running):**

```json theme={null}
{
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "running",
  "created_at": "2024-01-15T10:30:00Z",
  "completed_at": null,
  "outputs": null
}
```

**Response (completed):**

```json theme={null}
{
  "run_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "created_at": "2024-01-15T10:30:00Z",
  "completed_at": "2024-01-15T10:32:15Z",
  "outputs": [
    "https://storage.trycordage.com/outputs/image_001.png"
  ]
}
```

### List Runs

Get recent workflow runs:

```bash theme={null}
GET https://api.trycordage.com/api/webhooks/{webhook_key}/runs?limit=10&offset=0
```

### Cancel Run

Stop a running workflow:

```bash theme={null}
POST https://api.trycordage.com/api/webhooks/{webhook_key}/runs/{run_id}/kill
```

## Polling Pattern

Since workflows execute asynchronously, poll for completion:

```python theme={null}
import requests
import time

API_KEY = "cordage_xxxxxxxxxxxx"
WEBHOOK_KEY = "wh_abc123"
BASE_URL = "https://api.trycordage.com/api/webhooks"
headers = {"Authorization": f"Bearer {API_KEY}"}

# 1. Trigger workflow
response = requests.post(
    f"{BASE_URL}/{WEBHOOK_KEY}/trigger",
    headers=headers,
    json={"prompt": "A mountain landscape"}
)
run_id = response.json()["run_id"]

# 2. Poll for completion
while True:
    status = requests.get(
        f"{BASE_URL}/{WEBHOOK_KEY}/runs/{run_id}",
        headers=headers
    ).json()

    if status["status"] == "completed":
        print("Outputs:", status["outputs"])
        break
    elif status["status"] == "failed":
        print("Error:", status.get("error"))
        break

    time.sleep(2)  # Wait before next poll
```

## Rate Limits

| Operation  | Limit      |
| ---------- | ---------- |
| Trigger    | 60/minute  |
| Get status | 120/minute |
| List runs  | 120/minute |
| Cancel run | 30/minute  |

Rate limit headers are included in responses:

```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 45
```

## Error Handling

### HTTP Status Codes

| Code | Meaning                         |
| ---- | ------------------------------- |
| 200  | Success                         |
| 400  | Bad request (invalid input)     |
| 401  | Unauthorized (invalid API key)  |
| 402  | Insufficient credits            |
| 403  | Forbidden (wrong workspace)     |
| 404  | Not found (invalid webhook/run) |
| 429  | Rate limit exceeded             |
| 500  | Server error                    |

### Error Response Format

```json theme={null}
{
  "error": "Error message here",
  "retry_after": 30  // Only for 429 errors
}
```

## Integration Examples

### Node.js / JavaScript

```javascript theme={null}
async function generateImage(prompt) {
  const API_KEY = process.env.CORDAGE_API_KEY;
  const WEBHOOK_KEY = "wh_abc123";
  const BASE_URL = "https://api.trycordage.com/api/webhooks";

  // Trigger
  const triggerRes = await fetch(`${BASE_URL}/${WEBHOOK_KEY}/trigger`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ prompt })
  });
  const { run_id } = await triggerRes.json();

  // Poll
  while (true) {
    const statusRes = await fetch(
      `${BASE_URL}/${WEBHOOK_KEY}/runs/${run_id}`,
      { headers: { "Authorization": `Bearer ${API_KEY}` } }
    );
    const status = await statusRes.json();

    if (status.status === "completed") return status.outputs;
    if (status.status === "failed") throw new Error("Generation failed");

    await new Promise(r => setTimeout(r, 2000));
  }
}
```

### Zapier / Make / n8n

1. Use HTTP/Webhook module
2. Configure POST request to trigger endpoint
3. Add polling loop to check status
4. Use output URLs in downstream steps

### Serverless Functions

```python theme={null}
# AWS Lambda / Vercel / Cloud Functions
import json
import requests

def handler(event, context):
    prompt = event.get("prompt", "default prompt")

    response = requests.post(
        "https://api.trycordage.com/api/webhooks/wh_abc123/trigger",
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={"prompt": prompt}
    )

    return {
        "statusCode": 200,
        "body": json.dumps(response.json())
    }
```

## Best Practices

<Tip>
  **Use exponential backoff** - When polling, increase delay between requests if the workflow is taking long.
</Tip>

<Tip>
  **Handle all status values** - Check for "failed" and "cancelled" in addition to "completed".
</Tip>

<Tip>
  **Store run IDs** - Keep track of run IDs to check status later or for debugging.
</Tip>

<Tip>
  **Validate inputs** - Check inputs before triggering to avoid wasting credits on invalid requests.
</Tip>

<Tip>
  **Use webhook callbacks** - For long-running workflows, consider implementing callback URLs instead of polling (coming soon).
</Tip>

## Debugging

### Check Webhook Status

Verify your webhook is active in the Cordage UI:

1. Open your canvas
2. Click the Webhook node
3. Check "Active" status in inspector

### View Run History

See all runs for a webhook:

1. Click the Webhook node
2. Open "Run History" in inspector
3. Click individual runs for details

### Test Locally

Use curl or Postman to test your webhook:

```bash theme={null}
# Test trigger
curl -X POST https://api.trycordage.com/api/webhooks/wh_abc123/trigger \
  -H "Authorization: Bearer cordage_xxx" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "test"}'
```
