---
title: JavaScript and TypeScript
description: Install the Stormedo SDK, send single or batch requests, inspect delivery history, and manage recurring schedules from Node.js.
---

import InstallTabs from '../../../../components/docs/InstallTabs.astro';

The Stormedo JavaScript SDK supports Node.js 22 and newer. Create one client with a project API key and reuse it throughout your server application.

## Install

<InstallTabs
	syncKey="javascript-package-manager"
	commands={[
		{ label: 'npm', command: 'npm install @stormedo/sdk' },
		{ label: 'pnpm', command: 'pnpm add @stormedo/sdk' },
		{ label: 'yarn', command: 'yarn add @stormedo/sdk' },
		{ label: 'bun', command: 'bun add @stormedo/sdk' },
	]}
/>

## Create a client

```ts
import { Stormedo } from "@stormedo/sdk";

const stormedo = new Stormedo(process.env.STORMEDO_TOKEN!);
```

Keep `STORMEDO_TOKEN` in server-side configuration. Do not expose a project API key to browser code.

## Send a request

```ts
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

Use `rawBody` when Stormedo must forward bytes unchanged.

```ts
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

Submit multiple independent requests in one API call:

```ts
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

`listRequests` supports status, search, method, source, time range, cursor, and limit filters:

```ts
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:

```ts
const detail = await stormedo.getRequest(request.id);
const attempts = await stormedo.listRequestAttempts(request.id);

console.log(detail.status, attempts.length);
```

## Cancel and replay

```ts
await stormedo.cancelRequest(request.id);
const replay = await stormedo.replayRequest(request.id);

console.log(replay.id);
```

Cancellation preserves the original request and attempt history. Replay creates a new request linked to the original.

## Manage recurring schedules

Create schedules with cron or fixed-interval timing:

```ts
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

API and network failures throw `StormedoError`. Use `code` for program logic and `requestId` when contacting Stormedo support.

```ts
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);
  }
}
```

## Next step

Read [Delivery verification](/docs/concepts/delivery-verification/) before processing signed requests at your destination.
