The two endpoints
Your app uses two Codika public API endpoints:
| Endpoint | Method | Purpose |
|---|
webhook/{processInstanceId}/{workflowId} | POST | Trigger a workflow |
status/{processInstanceId}/{executionId} | GET | Poll execution status |
Both require authentication — an X-API-Key header, webhook signature headers, or a query parameter.
Step 1: Trigger a workflow
POST https://api.codika.io/webhook/{processInstanceId}/{workflowId}
Headers:
Content-Type: application/json
X-API-Key: ck_your_instance_key
Body:
{
"payload": {
"field1": "value1",
"field2": "value2"
}
}
The payload fields must match the workflow’s input schema (defined in config.ts).
Query parameter variant
For platforms that can’t set custom headers (e.g., GCP Pub/Sub push subscriptions):
POST https://api.codika.io/webhook/{processInstanceId}/{workflowId}?api_key=ck_your_key
Headers:
Content-Type: application/json
Body:
{
"payload": { ... }
}
See URL query parameter authentication for details.
If your platform signs outbound webhooks (like Resend, Stripe, or any Standard Webhooks-compatible system), you can use HMAC signature verification instead of API keys. See Webhook Signature Verification.
Response
{
"success": true,
"executionId": "019c8fb3-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"message": "Execution started"
}
Save the executionId — you’ll need it for polling.
The workflowId
This is the workflowTemplateId from the use case’s config.ts. It’s a stable identifier that doesn’t change across versions. Examples: main-workflow, http-direct-messaging, scheduled-report.
If the use case has agent skills, each skill’s workflowTemplateId field tells you the ID to use.
Step 2: Poll for results
GET https://api.codika.io/status/{processInstanceId}/{executionId}
Headers:
X-API-Key: ck_your_instance_key
Response
{
"execution": {
"status": "success",
"resultData": {
"result": "Processed output",
"processedAt": "2025-03-15T10:30:00.000Z"
}
}
}
Status values
| Status | Meaning | Action |
|---|
pending | Workflow is still running | Continue polling |
success | Workflow completed successfully | Read execution.resultData |
failed | Workflow encountered an error | Read execution.errorDetails.message |
Complete trigger + poll implementation
Here’s a reusable implementation in TypeScript:
interface TriggerResult {
executionId: string;
}
interface ExecutionResult<T> {
status: 'success' | 'failed';
data?: T;
error?: string;
}
const TRIGGER_BASE = 'https://api.codika.io/webhook';
const STATUS_BASE = 'https://api.codika.io/status';
/**
* Trigger a workflow and return the execution ID.
*/
async function triggerWorkflow(
processInstanceId: string,
workflowId: string,
payload: Record<string, unknown>,
apiKey: string
): Promise<string> {
const response = await fetch(
`${TRIGGER_BASE}/${processInstanceId}/${workflowId}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
body: JSON.stringify({ payload })
}
);
if (!response.ok) {
const text = await response.text();
throw new Error(`Trigger failed (${response.status}): ${text}`);
}
const data = await response.json();
const executionId = data.executionId ?? data.id;
if (!executionId) {
throw new Error('No executionId in trigger response');
}
return executionId;
}
/**
* Poll until the execution completes or times out.
*/
async function pollExecution<T>(
processInstanceId: string,
executionId: string,
apiKey: string,
parseResult: (resultData: unknown) => T,
options?: { maxWaitMs?: number; pollIntervalMs?: number }
): Promise<ExecutionResult<T>> {
const maxWait = options?.maxWaitMs ?? 120_000;
const interval = options?.pollIntervalMs ?? 1_500;
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
await new Promise(resolve => setTimeout(resolve, interval));
const response = await fetch(
`${STATUS_BASE}/${processInstanceId}/${executionId}`,
{ headers: { 'X-API-Key': apiKey } }
);
if (!response.ok) continue; // Retry on transient errors
const data = await response.json();
const status = data.execution?.status;
if (status === 'pending') continue;
if (status === 'success') {
return {
status: 'success',
data: parseResult(data.execution.resultData)
};
}
if (status === 'failed') {
return {
status: 'failed',
error: data.execution.errorDetails?.message ?? 'Unknown error'
};
}
}
throw new Error(`Execution timed out after ${maxWait / 1000}s`);
}
Usage
// Trigger
const executionId = await triggerWorkflow(
PROCESS_INSTANCE_ID,
'http-direct-messaging',
{ phone_numbers: ['32477123456'], message_content: 'Hello!' },
CODIKA_API_KEY
);
// Poll and parse
const result = await pollExecution(
PROCESS_INSTANCE_ID,
executionId,
CODIKA_API_KEY,
(data) => data as { success: boolean; total_sent: number }
);
if (result.status === 'success') {
console.log(`Sent to ${result.data.total_sent} recipients`);
} else {
console.error(`Failed: ${result.error}`);
}
The trigger endpoint wraps your input in a payload field:
{
"payload": {
"text_input": "Hello world",
"processing_mode": "summarize"
}
}
The keys inside payload must match the workflow’s inputSchema field names.
The payload wrapper is recommended but not required. If no payload key is present, the entire request body is passed through as-is. However, using the wrapper is safer and consistent with how the Codika dashboard sends data.
Some workflows (like scheduled reports with manual triggers) don’t require input. Send an empty body or empty payload:
const executionId = await triggerWorkflow(
PROCESS_INSTANCE_ID,
'scheduled-report',
{},
CODIKA_API_KEY
);