API Reference
Complete documentation for QueueBear v1 API endpoints and TypeScript SDK.
TypeScript SDK
Installation
npm install queuebear
Quick Start
import { QueueBear, serve } from "queuebear";
const qb = new QueueBear({
apiKey: "qb_live_xxx",
projectId: "proj_xxx",
});
API Overview
The SDK provides access to all QueueBear APIs:
| API | Description |
|---|---|
qb.messages
|
Publish and manage webhook messages |
qb.schedules
|
Create and manage cron-based recurring jobs |
qb.dlq
|
Manage failed messages in the dead letter queue |
qb.workflows
|
Trigger and manage durable workflows |
Base URL
https://api.queuebear.com
All API routes are prefixed with
/v1/projects/:projectId.
Authentication
Include your API key in the
Authorization header:
Authorization: Bearer qb_live_xxxxxxxxxxxxx
API Key Types
| Prefix | Environment | Usage |
|---|---|---|
qb_live_
|
Production | Live traffic |
qb_test_
|
Testing | Development/testing |
Permissions
| Permission | Description |
|---|---|
publish
|
Create messages, schedules; modify/delete resources |
read
|
Read messages, schedules, DLQ entries |
Messages
/v1/projects/:projectId/publish
Send a message to be delivered to a destination URL.
Request Body
{
"destination": "https://api.example.com/webhook",
"body": { "event": "user.created", "userId": "123" },
"delay": "30s",
"retries": 5,
"method": "POST",
"callbackUrl": "https://api.example.com/callback",
"failureCallbackUrl": "https://api.example.com/failure",
"deduplicationId": "unique-123",
"headers": {
"X-API-Key": "secret"
}
}
Body Parameters
| Parameter | Required | Description |
|---|---|---|
destination
|
Yes | URL to deliver the message to |
body
|
No | Payload to deliver (any JSON value) |
delay
|
No |
Delay before delivery: 30s, 5m,
2h, 1d
|
retries
|
No | Number of retry attempts (default: 3, max: 5) |
method
|
No | HTTP method: GET, POST, PUT, PATCH, DELETE (default: POST) |
callbackUrl
|
No | URL to call after successful delivery |
failureCallbackUrl
|
No | URL to call after all retries exhausted |
deduplicationId
|
No | Unique ID for message deduplication (max 128 chars) |
headers
|
No | Headers to forward to destination |
cURL Example
curl -X POST "https://api.queuebear.com/v1/projects/proj_xxx/publish" \
-H "Authorization: Bearer qb_live_xxxxx" \
-H "Content-Type: application/json" \
-d '{
"destination": "https://api.example.com/webhook",
"body": {"event": "user.created", "userId": "123"},
"delay": "30s",
"retries": 5,
"callbackUrl": "https://api.example.com/callback",
"headers": {"X-Custom-Header": "value"}
}'
SDK Example
const { messageId } = await qb.messages.publish(
"https://api.example.com/webhook",
{ event: "user.created", userId: "123" },
{
delay: "30s",
retries: 5,
method: "POST",
headers: { "X-API-Key": "secret" },
callbackUrl: "https://...",
failureCallbackUrl: "https://...",
deduplicationId: "unique-id",
}
);
Response
{
"messageId": "msg_a1b2c3d4e5f6..."
}
If deduplication ID matches an existing message:
{
"messageId": "msg_existing...",
"deduplicated": true
}
/v1/projects/:projectId/messages/{messageId}
Retrieve message details and delivery history.
Response
{
"messageId": "msg_a1b2c3d4e5f6...",
"destination": "https://api.example.com/webhook",
"status": "completed",
"retries": 3,
"retryCount": 0,
"createdAt": "2024-01-15T10:00:00Z",
"completedAt": "2024-01-15T10:00:31Z",
"deliveryLogs": [...]
}
Message Status Values
| Status | Description |
|---|---|
pending
|
Queued, waiting for scheduled time |
active
|
Currently being processed |
completed
|
Successfully delivered |
failed
|
Delivery failed, may be retrying |
cancelled
|
Manually cancelled |
dlq
|
Moved to dead letter queue |
SDK Example
const message = await qb.messages.get(messageId);
console.log(message.status); // "pending" | "completed" | "failed"
console.log(message.deliveryLogs); // Delivery attempt history
/v1/projects/:projectId/messages
List messages with optional filtering.
Query Parameters
| Parameter | Description |
|---|---|
status
|
Filter by status |
limit
|
Max results (1-100, default: 50) |
offset
|
Pagination offset |
SDK Example
const { messages, pagination } = await qb.messages.list({
status: "pending",
limit: 20,
offset: 0,
});
/v1/projects/:projectId/messages/{messageId}
Cancel a pending or active message.
SDK Example
await qb.messages.cancel(messageId);
qb.messages.publishAndWait()
Publish a message and wait for it to complete. Useful for synchronous workflows.
SDK Example
const message = await qb.messages.publishAndWait(
"https://api.example.com/webhook",
{ event: "user.created" },
{ timeoutMs: 30000 }
);
console.log(message.status); // "completed"
Schedules
/v1/projects/:projectId/schedules
Create a cron-based recurring job.
Request Body
{
"destination": "https://api.example.com/cron-job",
"cron": "0 9 * * *",
"timezone": "America/New_York",
"method": "POST",
"body": "{\"type\": \"daily-report\"}",
"headers": { "Content-Type": "application/json" },
"retries": 3
}
Cron Expression Examples
| Expression | Description |
|---|---|
* * * * *
|
Every minute |
0 * * * *
|
Every hour |
0 9 * * *
|
Daily at 9:00 AM |
0 9 * * 1-5
|
Weekdays at 9:00 AM |
0 0 1 * *
|
First day of each month |
0 */6 * * *
|
Every 6 hours |
SDK Example
const schedule = await qb.schedules.create({
destination: "https://api.example.com/cron-job",
cron: "0 9 * * *", // Daily at 9 AM
timezone: "America/New_York",
method: "POST",
body: JSON.stringify({ type: "daily-report" }),
headers: { "Content-Type": "application/json" },
retries: 3,
metadata: { jobName: "daily-report" },
});
/v1/projects/:projectId/schedules
List all schedules for the project.
Query Parameters
| Parameter | Description |
|---|---|
limit
|
Max results (1-100, default: 50) |
offset
|
Pagination offset |
SDK Example
const { schedules } = await qb.schedules.list();
/v1/projects/:projectId/schedules/{scheduleId}
Get details of a specific schedule.
Response
{
"scheduleId": "sched_a1b2c3d4e5f6...",
"destination": "https://api.example.com/cron-job",
"cron": "0 9 * * *",
"timezone": "America/New_York",
"method": "POST",
"retries": 3,
"isActive": true,
"isPaused": false,
"lastExecutedAt": "2024-01-15T14:00:00Z",
"nextExecutionAt": "2024-01-16T14:00:00Z",
"executionCount": 42,
"failureCount": 2
}
/v1/projects/:projectId/schedules/{scheduleId}/pause
/v1/projects/:projectId/schedules/{scheduleId}/resume
Pause or resume a schedule.
SDK Example
await qb.schedules.pause(scheduleId);
await qb.schedules.resume(scheduleId);
/v1/projects/:projectId/schedules/{scheduleId}
Delete a schedule.
SDK Example
await qb.schedules.delete(scheduleId);
Dead Letter Queue
Messages that fail all retry attempts are moved to the DLQ for manual review and recovery.
/v1/projects/:projectId/dlq
List all DLQ entries.
Response
{
"entries": [
{
"id": "uuid...",
"originalMessageId": "msg_a1b2c3d4e5f6...",
"destination": "https://api.example.com/webhook",
"failureReason": "Connection timeout",
"totalAttempts": 4
}
]
}
SDK Example
const { entries } = await qb.dlq.list();
for (const entry of entries) {
console.log(`${entry.id}: ${entry.failureReason}`);
}
/v1/projects/:projectId/dlq/{dlqId}
Get details of a specific DLQ entry.
Response
{
"id": "uuid...",
"originalMessageId": "msg_a1b2c3d4e5f6...",
"destination": "https://api.example.com/webhook",
"method": "POST",
"body": "{\"event\": \"user.created\"}",
"failureReason": "Connection timeout",
"lastStatusCode": 504,
"totalAttempts": 4,
"recoveredAt": null,
"recoveryMessageId": null
}
SDK Example
const entry = await qb.dlq.get(dlqId);
console.log(entry.body); // Original message body
console.log(entry.totalAttempts); // Number of failed attempts
/v1/projects/:projectId/dlq/{dlqId}/retry
Retry a failed message. Creates a new message from the DLQ entry.
Response
{
"dlqId": "uuid...",
"newMessageId": "msg_new123...",
"recovered": true
}
SDK Example
const result = await qb.dlq.retry(dlqId);
console.log(result.newMessageId); // New message created
/v1/projects/:projectId/dlq/{dlqId}
Permanently delete a DLQ entry without retry.
SDK Example
await qb.dlq.delete(dlqId);
/v1/projects/:projectId/dlq/purge
Delete all DLQ entries for the project.
SDK Example
await qb.dlq.purge(); // Delete all entries
qb.dlq.retryAll()
Retry all failed messages in the DLQ at once.
SDK Example
const results = await qb.dlq.retryAll();
console.log(`Retried ${results.length} entries`);
Workflows
Durable, fault-tolerant workflows with automatic step caching and resumption.
/v1/projects/:projectId/workflows/{workflowId}/trigger
Start a new workflow run.
Request Body
{
"workflowUrl": "https://your-app.com/api/workflows/onboarding",
"input": { "userId": "123", "email": "[email protected]" },
"metadata": { "source": "signup" },
"idempotencyKey": "onboarding-user-123",
"maxDuration": 604800
}
Body Parameters
| Parameter | Required | Description |
|---|---|---|
workflowUrl
|
Yes | URL of your workflow endpoint |
input
|
No | Input data passed to the workflow |
metadata
|
No | Custom metadata for your use |
idempotencyKey
|
No | Prevents duplicate runs with same key |
maxDuration
|
No | Timeout in seconds (default: 86400 = 1 day) |
Response
{
"runId": "run_a1b2c3d4e5f6..."
}
SDK Example
const { runId } = await qb.workflows.trigger(
"user-onboarding",
"https://your-app.com/api/workflows/onboarding",
{ userId: "123", email: "[email protected]" },
{
idempotencyKey: "onboarding-user-123",
maxDuration: 60 * 60 * 24 * 7, // 7 day timeout
}
);
/v1/projects/:projectId/workflows/{workflowId}/runs
List all runs for a specific workflow.
Query Parameters
| Parameter | Description |
|---|---|
status
|
Filter by status |
limit
|
Max results (1-100, default: 50) |
offset
|
Pagination offset |
/v1/projects/:projectId/workflows/runs/{runId}
Get workflow run details with all steps.
Workflow Run Status Values
| Status | Description |
|---|---|
pending
|
Queued, not yet started |
running
|
Currently executing |
sleeping
|
Paused in a sleep step |
waiting_event
|
Waiting for external event |
completed
|
Finished successfully |
failed
|
Failed |
cancelled
|
Manually cancelled |
timed_out
|
Exceeded max duration |
SDK Example
const status = await qb.workflows.getStatus(runId);
console.log(status.status); // "running" | "sleeping" | "completed"
console.log(status.steps); // Array of step details
/v1/projects/:projectId/workflows/events/{eventName}
Send an event to workflows waiting with
waitForEvent().
Request Body
{
"eventKey": "order-123",
"payload": { "status": "approved" }
}
SDK Example
// In workflow: await context.waitForEvent("order-approved", "order.approved")
// From external code:
await qb.workflows.sendEvent("order.approved", {
eventKey: "order-123",
payload: { status: "approved" },
});
/v1/projects/:projectId/workflows/runs/{runId}
Cancel a running workflow.
/v1/projects/:projectId/workflows/runs/{runId}/retry
Retry a failed workflow. Resumes from the last completed step.
SDK Example
await qb.workflows.cancel(runId);
await qb.workflows.retry(runId); // Resume from last completed step
qb.workflows.waitForCompletion()
Poll a workflow until it completes, fails, or times out.
SDK Example
const result = await qb.workflows.waitForCompletion(runId, {
pollIntervalMs: 2000,
timeoutMs: 60000,
});
qb.triggerAndWait()
Trigger a workflow and wait for it to complete in one call.
SDK Example
const result = await qb.triggerAndWait(
"user-onboarding",
"https://your-app.com/api/workflows/onboarding",
{ userId: "123" },
{ timeoutMs: 120000 }
);
console.log(result.result); // Workflow output
Error Responses
All errors follow this format:
{
"error": "Error message describing the issue",
"details": "Additional context (optional)"
}
HTTP Status Codes
| Code | Description |
|---|---|
200
|
Success |
400
|
Bad Request - Invalid parameters or body |
401
|
Unauthorized - Invalid or missing API key |
403
|
Forbidden - API key lacks required permission |
404
|
Not Found - Resource doesn't exist |
500
|
Internal Server Error |
Workflows SDK
Build durable, fault-tolerant workflows with automatic step caching.
Workflows consist of two parts: a workflow endpoint created with
serve(), and a client
that uses
qb.workflows to
trigger and manage runs.
serve() Function
The serve() function
creates an HTTP handler for your workflow. It receives requests
from QueueBear, executes your workflow code, and manages step
caching automatically.
import { serve } from "queuebear";
export const POST = serve<InputType>(async (context) => {
// Your workflow logic here
return result;
}, options);
Options
| Option | Type | Description |
|---|---|---|
signingSecret
|
string |
Secret to verify requests come from QueueBear |
Framework Integration
Next.js (App Router)
// app/api/workflows/my-workflow/route.ts
import { serve } from "queuebear";
export const POST = serve(async (context) => {
await context.run("step-1", async () => { /* ... */ });
return { success: true };
});
Express
import express from "express";
import { serve } from "queuebear";
const app = express();
app.use(express.json());
const handler = serve(async (context) => {
await context.run("step-1", async () => { /* ... */ });
return { success: true };
});
app.post("/api/workflows/my-workflow", async (req, res) => {
const response = await handler(
new Request(req.url, {
method: "POST",
headers: req.headers as HeadersInit,
body: JSON.stringify(req.body),
})
);
res.status(response.status).json(await response.json());
});
Hono
import { Hono } from "hono";
import { serve } from "queuebear";
const app = new Hono();
const handler = serve(async (context) => {
await context.run("step-1", async () => { /* ... */ });
return { success: true };
});
app.post("/api/workflows/my-workflow", async (c) => {
return await handler(c.req.raw);
});
Context Methods
Available in
serve() handlers:
context.run(stepName, fn, options?)
Execute a step with automatic caching.
const result = await context.run("fetch-user", async () => {
return await db.users.findById(userId);
});
context.sleep(stepName, seconds)
Pause workflow for specified duration.
await context.sleep("wait-1-hour", 3600);
context.sleepUntil(stepName, date)
Pause until a specific date/time.
await context.sleepUntil("wait-until-tomorrow", new Date("2024-01-15"));
context.call(stepName, config)
Make an HTTP call as a cached step.
const data = await context.call("fetch-api", {
url: "https://api.example.com/data",
method: "POST",
headers: { Authorization: "Bearer xxx" },
body: { key: "value" },
});
context.waitForEvent(stepName, eventName, options?)
Wait for an external event.
const payload = await context.waitForEvent("wait-approval", "order.approved", {
eventKey: "order-123",
timeoutSeconds: 86400, // 1 day
});
context.notify(eventName, payload?)
Send fire-and-forget event.
await context.notify("user.onboarded", { userId: "123" });
context.parallel(steps)
Execute steps in parallel.
const [user, orders, preferences] = await context.parallel([
{ name: "fetch-user", fn: () => fetchUser(userId) },
{ name: "fetch-orders", fn: () => fetchOrders(userId) },
{ name: "fetch-preferences", fn: () => fetchPreferences(userId) },
]);
context.getCompletedSteps()
Get all completed steps for debugging.
const steps = await context.getCompletedSteps();
console.log(`Completed ${steps.length} steps`);
Complete Workflow Example
// app/api/workflows/onboarding/route.ts
import { serve } from "queuebear";
interface OnboardingInput {
userId: string;
email: string;
}
export const POST = serve<OnboardingInput>(
async (context) => {
const { userId, email } = context.input;
// Step 1: Send welcome email (cached if already done)
await context.run("send-welcome", async () => {
await sendEmail(email, "welcome");
});
// Step 2: Wait 3 days
await context.sleep("wait-3-days", 60 * 60 * 24 * 3);
// Step 3: Send tips email
await context.run("send-tips", async () => {
await sendEmail(email, "tips");
});
return { completed: true };
},
{
signingSecret: process.env.QUEUEBEAR_SIGNING_SECRET,
}
);
Security
Signature Verification
Verify that workflow requests come from your QueueBear instance:
export const POST = serve(handler, {
signingSecret: process.env.QUEUEBEAR_SIGNING_SECRET,
});
The signing secret is available in your QueueBear project settings. When configured, requests without a valid signature will be rejected with a 401 error.
Local Development
When developing locally, your webhook endpoints run on
localhost which isn't
accessible from QueueBear's servers. Use
Tunnelmole
to expose your local server - it's free and requires no signup.
Installing Tunnelmole
Linux, macOS, Windows WSL:
curl -O https://install.tunnelmole.com/t357g/install && sudo bash install
Node.js (all platforms, requires Node 16+):
npm install -g tunnelmole
Starting a Tunnel
tmole 3000
# Output: https://xxxx.tunnelmole.com is forwarding to localhost:3000
Using the Tunnel URL
// Use tunnelmole URL instead of localhost
await qb.messages.publish("https://xxxx.tunnelmole.com/api/webhooks", {
event: "user.created",
userId: "123"
});
// Works for workflows too
await qb.workflows.trigger(
"onboarding",
"https://xxxx.tunnelmole.com/api/workflows/onboarding",
{ userId: "123" }
);
Tips
-
Store your tunnel URL in
.envfor easy switching between local and production -
Both
callbackUrlandfailureCallbackUrlneed public URLs for local testing - Tunnel URLs change on restart
Webhooks & Callbacks
When using
callbackUrl or
failureCallbackUrl in
your publish request, QueueBear will POST to those URLs with the
following payloads:
Success Callback
{
"messageId": "msg_a1b2c3d4e5f6...",
"status": "completed",
"destination": "https://api.example.com/webhook",
"response": {
"statusCode": 200,
"body": "{\"success\": true}",
"duration": 150
},
"completedAt": "2024-01-15T10:00:31Z"
}
Failure Callback
{
"messageId": "msg_a1b2c3d4e5f6...",
"status": "failed",
"destination": "https://api.example.com/webhook",
"error": "Connection timeout after 30000ms",
"attempts": 4,
"failedAt": "2024-01-15T10:05:00Z"
}