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

POST /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
}
GET /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
GET /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,
});
DELETE /v1/projects/:projectId/messages/{messageId}

Cancel a pending or active message.

SDK Example

await qb.messages.cancel(messageId);
SDK 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

POST /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" },
});
GET /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();
GET /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
}
PATCH /v1/projects/:projectId/schedules/{scheduleId}/pause
PATCH /v1/projects/:projectId/schedules/{scheduleId}/resume

Pause or resume a schedule.

SDK Example

await qb.schedules.pause(scheduleId);
await qb.schedules.resume(scheduleId);
DELETE /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.

GET /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}`);
}
GET /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
POST /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
DELETE /v1/projects/:projectId/dlq/{dlqId}

Permanently delete a DLQ entry without retry.

SDK Example

await qb.dlq.delete(dlqId);
POST /v1/projects/:projectId/dlq/purge

Delete all DLQ entries for the project.

SDK Example

await qb.dlq.purge(); // Delete all entries
SDK 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.

POST /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
  }
);
GET /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
GET /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
POST /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" },
});
DELETE /v1/projects/:projectId/workflows/runs/{runId}

Cancel a running workflow.

POST /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
SDK 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,
});
SDK 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 .env for easy switching between local and production
  • Both callbackUrl and failureCallbackUrl need 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"
}