FlareClient
The main entry point for the @zeridion/flare TypeScript/JavaScript SDK. A thin, dependency-free wrapper around the Zeridion Flare REST API using native fetch.
Package: @zeridion/flare · Runtime: Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge, Fastly Compute@Edge JS, or any environment with native fetch and crypto.subtle (or node:crypto in Node).
Installation
npm install @zeridion/flare
Constructor
import { FlareClient } from "@zeridion/flare";
const flare = new FlareClient(opts: FlareClientOptions);
FlareClientOptions
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
apiKey | string | yes | — | Your Zeridion API key (zf_live_sk_... or zf_test_sk_...). |
baseUrl | string | no | https://api.zeridion.com | Override the API base URL (e.g. for local dev). |
maxRetries | number | no | 3 | Maximum retry attempts on 429 / 5xx / network errors. Set 0 to disable. |
retryBaseDelayMs | number | no | 500 | Base delay for exponential backoff schedule. |
retryMaxDelayMs | number | no | 30000 | Upper bound on a single backoff wait; also caps server-supplied Retry-After hints. |
maxResponseBytes | number | no | 10 485 760 (10 MiB) | Hard cap on response body size. Protects against an unbounded body from a misbehaving server. Set 0 to disable. |
RequestOptions
All methods accept an optional RequestOptions argument:
interface RequestOptions {
idempotencyKey?: string; // sent as Idempotency-Key header — used by the server to dedupe a replayed write
requestId?: string; // sent as X-Request-Id header — used for distributed tracing and log correlation
signal?: AbortSignal; // for request cancellation
}
idempotencyKey— semantic deduplication on the server. A secondcreateJobwith the sameidempotencyKeyreturns the original response (status code and body), not a fresh write. See idempotency guide.requestId— pass through your application's trace / correlation ID so it shows up in Zeridion's server logs alongside your own. Omit it and the server generates a ULID and returns it in the responseX-Request-Idheader.
Job methods
createJob(req, opts?)
Enqueue a new background job. Returns the created job's summary.
createJob(req: CreateJobRequest, opts?: RequestOptions): Promise<CreateJobResponse>
const job = await flare.createJob({
job_type: "SendWelcomeEmail",
payload: { email: "alice@example.com", name: "Alice" },
queue: "default",
max_attempts: 3,
});
console.log(job.id, job.state); // "job_abc123", "pending"
See CreateJobRequest and CreateJobResponse for the full type shapes.
getJob(id, opts?)
Retrieve a job by ID. Returns null if the job does not exist (404 — does not throw).
getJob(id: string, opts?: RequestOptions): Promise<JobDetail | null>
const detail = await flare.getJob("job_abc123");
if (detail) {
console.log(detail.state, detail.progress);
}
listJobs(opts?)
List jobs with optional filters. Supports cursor-based pagination.
listJobs(opts?: ListJobsOptions & RequestOptions): Promise<ListJobsResponse>
const { jobs, cursor, has_more } = await flare.listJobs({
state: "failed",
queue: "critical",
limit: 25,
});
See ListJobsOptions for all filter parameters.
cancelJob(id, opts?)
Cancel a pending or scheduled job. Returns null when the job is in a non-cancellable state (409 — does not throw). Throws NotFoundError when the job does not exist (404).
cancelJob(id: string, opts?: RequestOptions): Promise<CancelJobResponse | null>
const result = await flare.cancelJob("job_abc123");
if (result === null) {
console.log("Job is already processing or completed — cannot cancel");
}
retryJob(id, opts?)
Retry a failed or dead-lettered job, resetting it to pending. Returns null when the job is not retryable (409 — does not throw). Throws NotFoundError when the job does not exist (404).
retryJob(id: string, opts?: RequestOptions): Promise<RetryJobResponse | null>
const result = await flare.retryJob("job_abc123");
if (result) {
console.log("Job reset to pending, attempt:", result.attempt);
}
Worker methods
Use these to build a custom worker process that polls for jobs and acknowledges results.
pollWorker(req, opts?)
Poll for available jobs on one or more queues. The server returns up to capacity jobs that are ready for processing.
pollWorker(req: PollRequest, opts?: RequestOptions): Promise<PollResponse>
const { jobs } = await flare.pollWorker({
worker_id: "worker-1",
queues: ["default", "critical"],
capacity: 5,
});
ackWorker(req, opts?)
Acknowledge the result of a job execution. Call this after every job — success, failure, or retry request.
ackWorker(req: AckRequest, opts?: RequestOptions): Promise<AckResponse>
// Success
await flare.ackWorker({
job_id: job.id,
worker_id: "worker-1",
status: "succeeded",
duration_ms: 240,
});
// Failure
await flare.ackWorker({
job_id: job.id,
worker_id: "worker-1",
status: "failed",
error: { type: "Error", message: "Downstream timeout" },
});
Full worker loop example
import { FlareClient, FlareError, RateLimitError } from "@zeridion/flare";
const flare = new FlareClient({ apiKey: process.env.FLARE_API_KEY! });
async function runWorker() {
while (true) {
const { jobs } = await flare.pollWorker({
worker_id: "worker-1",
queues: ["default"],
capacity: 5,
});
for (const job of jobs) {
const start = Date.now();
try {
await dispatch(job.job_type, job.payload);
await flare.ackWorker({
job_id: job.id,
worker_id: "worker-1",
status: "succeeded",
duration_ms: Date.now() - start,
});
} catch (err) {
await flare.ackWorker({
job_id: job.id,
worker_id: "worker-1",
status: "failed",
duration_ms: Date.now() - start,
error: { type: err instanceof Error ? err.name : "Error", message: String(err) },
});
}
}
if (jobs.length === 0) {
await new Promise(r => setTimeout(r, 1000));
}
}
}
async function dispatch(jobType: string, payload: unknown) {
// Map job_type strings to handler functions
const handlers: Record<string, (p: unknown) => Promise<void>> = {
SendWelcomeEmail: async (p) => { /* ... */ },
};
const handler = handlers[jobType];
if (!handler) throw new Error(`Unknown job type: ${jobType}`);
await handler(payload);
}
Outbound webhooks
Verifying an incoming webhook (SDK helper)
The SDK exports verifyWebhook, a Web Crypto-based verifier that works in every supported runtime (Node 20+, Bun, Deno, Cloudflare Workers, Vercel Edge, Fastly Compute@Edge).
The X-Zeridion-Signature header has the form t=<unix_timestamp>,v1=<lowercase_hex_digest> — the signature is HMAC-SHA256 over <unix_timestamp>.<raw_body>. verifyWebhook parses the header, recomputes the digest, and compares in constant time.
import { verifyWebhook } from "@zeridion/flare";
// Express example — note express.raw() so we receive the exact bytes the server signed.
app.post("/hooks/zeridion", express.raw({ type: "*/*" }), async (req, res) => {
const header = req.header("X-Zeridion-Signature") ?? "";
const ok = await verifyWebhook(req.body, header, process.env.WEBHOOK_SECRET!, {
toleranceSeconds: 300, // recommended: reject signatures older than 5 minutes
});
if (!ok) return res.status(400).send("invalid signature");
const event = JSON.parse(req.body.toString());
console.log("Received event:", event.type, event.data.id);
res.sendStatus(200);
});
Subscription CRUD (REST fallback)
The TypeScript SDK does not yet expose webhook subscription management (create / list / update / delete). Use fetch directly against the REST API while SDK support is pending.
const API_KEY = process.env.FLARE_API_KEY!;
const BASE = "https://api.zeridion.com/flare/v1";
// Create
const created = await fetch(`${BASE}/webhooks`, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
url: "https://your-app.example.com/hooks/zeridion",
events: ["job.succeeded", "job.failed", "job.dead_letter"],
}),
}).then(r => r.json());
// { id: "wh_...", url, events, secret: "whsec_...", created_at }
// Store created.secret — that's the value you pass to verifyWebhook().
// List
const list = await fetch(`${BASE}/webhooks`, {
headers: { Authorization: `Bearer ${API_KEY}` },
}).then(r => r.json());
// Update
await fetch(`${BASE}/webhooks/${created.id}`, {
method: "PATCH",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ events: ["job.succeeded"] }),
});
// Delete
await fetch(`${BASE}/webhooks/${created.id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${API_KEY}` },
});
Audit log export (REST fallback)
The TypeScript SDK does not yet expose an exportAuditLog method. Use fetch with streaming while SDK support is pending.
async function exportAuditLog(opts: {
projectId: string;
from: string; // ISO 8601, e.g. "2026-01-01T00:00:00Z"
until: string; // ISO 8601, e.g. "2026-04-01T00:00:00Z"
format?: "jsonl" | "csv";
outputPath: string;
jwt: string; // Audit-log export requires admin/owner JWT, not an API key
}) {
const { projectId, from, until, format = "jsonl", outputPath, jwt } = opts;
const params = new URLSearchParams({ from, until, format });
const response = await fetch(
`https://api.zeridion.com/flare/v1/projects/${projectId}/audit-log/export?${params}`,
{ headers: { Authorization: `Bearer ${jwt}` } },
);
if (!response.ok) {
const err = await response.json();
throw new Error(`Export failed: ${err.code} — ${err.message}`);
}
// Stream to disk (Node 18+ ReadableStream → fs.WriteStream)
const { createWriteStream } = await import("node:fs");
const { Readable } = await import("node:stream");
const out = createWriteStream(outputPath);
await new Promise<void>((resolve, reject) => {
Readable.fromWeb(response.body as any).pipe(out)
.on("finish", resolve)
.on("error", reject);
});
console.log(`Audit log saved to ${outputPath}`);
}
// Usage
await exportAuditLog({
from: "2026-01-01T00:00:00Z",
until: "2026-04-01T00:00:00Z",
format: "jsonl",
outputPath: "./audit-2026-q1.jsonl",
});
See also
- Types reference — all request/response shapes
- Error hierarchy — typed errors and catch patterns
- SDKs overview — feature parity across all SDKs
- REST API reference — underlying HTTP endpoints