JavaScript and TypeScript
The Stormedo JavaScript SDK supports Node.js 22 and newer and uses ESM imports. Create one client with a project API key and reuse it throughout your server application.
Install
Section titled “Install”npm install @stormedo/sdkpnpm add @stormedo/sdkyarn add @stormedo/sdkbun add @stormedo/sdkCreate a client
Section titled “Create a client”import { Stormedo } from "@stormedo/sdk";
const token = process.env.STORMEDO_TOKEN;if (!token) throw new Error("Set STORMEDO_TOKEN");const stormedo = new Stormedo(token);Keep STORMEDO_TOKEN in server-side configuration. Do not expose a project API key to browser code.
Send a request
Section titled “Send a request”const request = await stormedo.send("https://your-app.example/webhooks/stormedo", { body: { type: "order.created", orderId: "ord_123", }, delay: { minutes: 5 }, maxAttempts: 5,});
console.log(request.id, request.status);maxAttempts counts the initial attempt.
Forward exact bytes
Section titled “Forward exact bytes”Use rawBody when Stormedo must forward bytes unchanged.
await stormedo.send("https://your-app.example/events", { rawBody: new TextEncoder().encode("event=created"), contentType: "application/x-www-form-urlencoded", headers: { Authorization: "Bearer destination-token", },});The headers you provide are sent to the destination. Stormedo never forwards your project API key.
Send a batch
Section titled “Send a batch”Submit multiple independent requests in one API call:
const requests = await stormedo.sendBatch([ { url: "https://your-app.example/webhooks/orders", body: { type: "order.created", orderId: "ord_123" }, }, { url: "https://your-app.example/webhooks/inventory", body: { type: "inventory.sync", warehouseId: "wh_7" }, delay: { hours: 1 }, },]);
console.log(requests.map((request) => request.id));delay and timeout accept duration objects with days, hours, minutes,
and seconds. deliverAt accepts a Date.
Inspect request history
Section titled “Inspect request history”listRequests supports status, search, method, source, time range, cursor, and limit filters:
const page = await stormedo.listRequests({ status: "failed", method: "POST", limit: 25,});
console.log(page.items);
if (page.nextCursor) { const nextPage = await stormedo.listRequests({ status: "failed", method: "POST", limit: 25, cursor: page.nextCursor, });
console.log(nextPage.items);}Load request detail and completed attempts independently:
const detail = await stormedo.getRequest(request.id);const attempts = await stormedo.listRequestAttempts(request.id);
console.log(detail.status, attempts.length);Cancel and replay
Section titled “Cancel and replay”const cancellation = await stormedo.cancelRequest(request.id);console.log(cancellation.status);Cancellation preserves history. cancellation_requested acknowledges the signal; it is not a final request status. An in-flight attempt can still finish.
Separately, replay a request that you have confirmed is succeeded, failed, or cancelled:
const completedRequestId = "req_33uZSQsVaf8aZjDuzkuAq";const replay = await stormedo.replayRequest(completedRequestId, { idempotencyKey: "order-123-replay-001",});console.log(replay.id);Replace the illustrative ID with your completed request’s ID. Do not cancel and immediately replay without checking state; replay of active work returns 409. Replay has a new request ID, so use a stable payload event ID if repeating the business effect must be prevented.
Manage recurring schedules
Section titled “Manage recurring schedules”Create schedules with cron or fixed-interval timing:
import { cronTiming, intervalTiming } from "@stormedo/sdk";
const schedule = await stormedo.createSchedule({ name: "sync-inventory", timing: cronTiming("0 */6 * * *", "Asia/Kolkata"), url: "https://your-app.example/inventory/sync", body: { fullSync: true }, maxAttempts: 4,});
await stormedo.updateSchedule(schedule.id, { timing: intervalTiming({ hours: 4 }),});
const schedules = await stormedo.listSchedules();const definition = await stormedo.getSchedule(schedule.id);
console.log(schedules.length, definition.status);
await stormedo.pauseSchedule(schedule.id);await stormedo.resumeSchedule(schedule.id);await stormedo.deleteSchedule(schedule.id);cronTiming accepts a five-field cron expression and an IANA timezone.
intervalTiming accepts days, hours, and minutes; the combined duration
must be a whole number of minutes from one minute through 30 days. Pass a future
Date as startsAt to choose the first run time.
Handle API errors
Section titled “Handle API errors”API and network failures throw StormedoError. Use code for program logic and requestId when contacting Stormedo support.
import { StormedoError } from "@stormedo/sdk";
try { await stormedo.getRequest("req_33uZSQsVaf8aZjDuzkuAq");} catch (error) { if (error instanceof StormedoError) { console.error(error.code, error.status, error.requestId, error.details); } throw error;}Send options and defaults
Section titled “Send options and defaults”send(url, options) returns an EnqueuedRequest. JSON sends use /requests; raw sends use /send.
| Option | Type and behavior |
|---|---|
body |
Any JSON value. Omission sends no body; null sends JSON null. |
rawBody |
string or Uint8Array, including Buffer. Mutually exclusive with body and metadata. |
contentType |
String, up to 255 bytes, for raw sends only. |
method |
GET, POST, PUT, PATCH, or DELETE; default POST. |
headers |
Destination string-to-string map; at most 64 entries and 32 KiB combined. |
delay |
Duration object, whole seconds from 1 second through 30 days. |
deliverAt |
Valid Date, within 30 days of server time; cannot be combined with delay. Allowed past times run immediately. |
timeout |
Duration object, whole seconds from 1 through 300; default 50 seconds per destination attempt. |
maxAttempts |
Total attempts, 1 through 20. Default 3 for JSON sends and batch items, 1 for raw sends and schedules. |
queue |
Label, 1 to 64 letters, numbers, dots, hyphens, or underscores; default default. No FIFO or concurrency guarantee. |
metadata |
JSON sends only. Up to 32 string entries; 64-byte keys, 1024-byte values, 16 KiB combined. Not forwarded. |
idempotencyKey |
Stable business-operation key, 1 to 255 bytes; generated per call when omitted. |
signal |
AbortSignal for the API call. Aborting cannot undo work already accepted. |
Duration objects may combine non-negative integer days, hours, minutes, and seconds; the total must satisfy the option’s bounds. Each delivered body is limited to 256 KiB. See reserved headers.
Results and operation options
Section titled “Results and operation options”SDK results unwrap the API’s data and use camelCase. Read request.id, not request.data.id. Dates in results are RFC 3339 strings, except verifier results, which use Date.
| Operation | Result and extra options |
|---|---|
send |
EnqueuedRequest: id, projectId, replayedFromRequestId, status, scheduledAt, createdAt. |
sendBatch(items, options) |
Read-only array of enqueued requests in input order. 1 to 100 JSON items; optional per-item keys. options accepts batch idempotencyKey and signal. |
listRequests(options) |
items, nextCursor, historyRetentionDays, historyCutoffAt, hasHiddenHistory. Filters include from and to as RFC 3339 strings. Default limit 50, maximum 100. |
getRequest(id, options) |
Request definition and completed attempts. Body omitted; header values redacted. |
listRequestAttempts(id, options) |
Read-only array of completed attempts. Empty until the first attempt completes. |
cancelRequest(id, options) |
id and status, either cancellation_requested or cancelled. |
replayRequest(id, options) |
New enqueued request. Options accept idempotencyKey and signal. |
createSchedule(input, options) |
Schedule summary. Options accept idempotencyKey and signal. |
listSchedules(options) |
Read-only array of schedule summaries. |
getSchedule(id, options) |
Schedule detail with request replacing top-level url and maxAttempts. |
updateSchedule(id, input, options) |
Updated schedule summary. |
pauseSchedule(id, options), resumeSchedule(id, options) |
Updated schedule summary. |
deleteSchedule(id, options) |
undefined, from an HTTP 204. |
All operations accept signal in their options. Full result fields are described in API objects with snake-case HTTP names. For example, attempt response_status_code becomes responseStatusCode and retry.next_attempt_at becomes retry.nextAttemptAt.
A schedule input accepts name, timing, url, JSON body, headers, and maxAttempts. Occurrences use POST. In updateSchedule, supply destination changes inside request, with the full desired URL/body/attempt budget. Omitted headers preserve existing values; {} clears them. Omitted or null schedule bodies send no body, unlike explicit null in ordinary JSON sends. Never write [REDACTED] header values back into a schedule.
Transport failures and validation
Section titled “Transport failures and validation”The default API base URL is https://api.stormedo.com. The optional constructor setting baseUrl supports controlled test endpoints. Browser use is rejected because the client holds a secret API key. Node.js is the supported runtime; using Bun as an installer does not imply a Bun runtime compatibility guarantee.
The SDK retries an interrupted transport call once for sends, batches, reads, cancel, replay, schedule creation, pause, and resume. It preserves generated or supplied idempotency keys. Schedule updates and deletions are not automatically retried. HTTP error responses, including 5xx, are not automatically retried. Explicit aborts are not retried.
There is no SDK-level API timeout by default. Use signal: AbortSignal.timeout(30_000) to bound the publisher call. This is separate from timeout, which controls each outbound delivery attempt. A timeout or abort can still leave acceptance unknown; recover with the original key and input.
API failures throw StormedoError; status and requestId may be null for transport failures. Transport codes include network_error and request_aborted. Invalid local input can throw TypeError. Log only the fields needed for investigation, propagate failures, and follow submission recovery.
Next step
Section titled “Next step”Read Delivery verification before processing signed requests at your destination.