All docs

SDK Reference

The @sendithq/sdk package (Node 18+, zero runtime dependencies). Every wire field is normalized to camelCase for you.

import { SendIt, SendItError } from "@sendithq/sdk";

Prefer Python? The sendithq package mirrors this surface (sync + async). Method names are snake_case and the relative-time argument is in_. Jump to the Python SDK section.

Constructor

new SendIt(apiKey: string, options?: SendItOptions)

apiKey must look like sw_live_… or sw_test_…; an empty or malformed key throws a SendItError synchronously.

SendItOptions

Option Type Default Description
baseUrl string https://api.sendit-whenever.com API endpoint.
timeoutMs number 30000 Per-request timeout.
maxRetries number 2 Extra retries on idempotent paths.
signingSecret string Default secret for verifySignature.

Methods

schedule(input)ScheduleRef

Create one schedule. Retried automatically only when idempotencyKey is set.

const ref = await sendit.schedule({
  url: "https://api.myapp.com/hook",
  in: "2h",                 // or fireAt: string | Date
  payload: { userId: 42 },  // object → JSON, string → verbatim
  method: "POST",           // default POST
  headers: { "X-Tenant": "acme" },
  idempotencyKey: "trial-end:42",
  earlyFire: true,          // opt in to Early Firing (default: account setting)
});
// ref = { id, fireAt, status }

earlyFire is optional — omit it to use your account default (off unless changed). See Early Firing.

scheduleMany(items)BulkScheduleResult

Create a batch of schedules in one call, up to your plan's max batch size. Partial success: each item succeeds or fails independently.

const { accepted, rejected } = await sendit.scheduleMany([
  { url, in: "1h", payload: a, idempotencyKey: "k1" },
  { url, in: "2h", payload: b, idempotencyKey: "k2" },
]);
// accepted: { index, id, fireAt, status }[]
// rejected: { index, code, message }[]

The batch is retried only if every item carries an idempotencyKey. Duplicate keys within one batch are rejected.

list(options?)ListResult

List schedules with optional filtering and pagination. Idempotent — retried automatically.

const { items, total, limit, offset } = await sendit.list({
  status: "scheduled", // scheduled | firing | succeeded | failed | cancelled | dead
  q: "billing",         // substring match on target URL
  limit: 50,
  offset: 0,
});

get(id)Schedule

Fetch one schedule. Idempotent.

getAttempts(id)DeliveryAttempt[]

Fetch the delivery-attempt log for a schedule. Idempotent. See Reliability.

reschedule(id, input)Schedule

Move a schedule's fire time. Only valid while scheduled; otherwise 409 CONFLICT. Not retried.

await sendit.reschedule("sch_123", { in: "3h" });             // or { fireAt }
await sendit.reschedule("sch_123", { in: "3h", earlyFire: true }); // also toggle Early Firing

cancel(id)Schedule

Cancel a schedule before it fires. Idempotent.

replay(id)ScheduleRef

Re-send the same encrypted payload as a brand-new schedule. Not retried (non-idempotent).

await sendit.replay("sch_123");              // fire now

clone(id, input)ScheduleRef

Create a new schedule from an existing one with a new payload. Not retried.

await sendit.clone("sch_123", { payload: { v: 2 }, in: "1d" }); // optional: earlyFire

verifySignature(req, opts?)boolean

Verify an inbound webhook signature. See Verifying Webhooks.

const ok = sendit.verifySignature(
  { headers, rawBody },          // rawBody must be the raw bytes
  { secret: process.env.SENDIT_SIGNING_SECRET }, // optional if set in constructor
);

signingSecrets.get() / signingSecrets.rotate()SigningSecretPair

Read or rotate your signing secrets. Both return { current, next }.

const { current, next } = await sendit.signingSecrets.get();
await sendit.signingSecrets.rotate();

Types

ScheduleRef — returned by schedule, reschedule, cancel, replay, clone:

{ id: string; fireAt: string; status: ScheduleStatus }

Schedule — returned by get, list items:

{
  id: string;
  targetUrl: string;
  method: HttpMethod;
  status: ScheduleStatus;
  fireAt: string;
  firedAt: string | null;    // actual first-fire time; null until it fires
  offsetMs: number | null;   // firedAt − fireAt (ms); + late, − early-fire only; null until fired
  earlyFire: boolean;        // whether Early Firing was enabled
  attempts: number;
  payloadSizeBytes: number;
  hasPayload: boolean;       // payload plaintext is never returned
  createdAt: string;
  updatedAt: string;
}

firedAt / offsetMs are the measured punctuality of the schedule — see Punctuality. These are raw per-schedule values; the public status numbers floor early-fire offsets at zero.

DeliveryAttempt:

{
  attemptNo: number;
  statusCode: number | null;
  latencyMs: number | null;
  errorText: string | null;  // snippet of the target's response, never your payload
  attemptedAt: string;
}

ScheduleStatus = scheduled | firing | succeeded | failed | cancelled | dead. HttpMethod = GET | POST | PUT | PATCH | DELETE.

Errors

Every failure throws a SendItError with a code and optional status. See Error Codes.

import { SendItError } from "@sendithq/sdk";

try {
  await sendit.get("missing");
} catch (err) {
  if (err instanceof SendItError && err.code === "NOT_FOUND") {
    // handle
  }
}

Python SDK

The sendithq package (Python 3.9+, single dependency httpx) mirrors this surface with both a synchronous SendIt and an asynchronous AsyncSendIt.

pip install sendithq
from sendithq import SendIt, SendItError

sendit = SendIt("sw_live_xxx")
ref = sendit.schedule(
    url="https://api.myapp.com/hook",
    in_="2h",                    # 'in' is reserved in Python → in_
    payload={"user_id": 42},     # dict → JSON, str → verbatim
    idempotency_key="trial-end:42",
)

The async client has the identical surface; every method is a coroutine:

from sendithq import AsyncSendIt

async with AsyncSendIt("sw_live_xxx") as sendit:
    ref = await sendit.schedule(url="https://api.myapp.com/hook", in_="2h")

Naming map (Node → Python)

Node (@sendithq/sdk) Python (sendithq)
new SendIt(key, opts) SendIt(key, **opts)baseUrl→base_url, timeoutMs→timeout (seconds), maxRetries→max_retries, signingSecret→signing_secret
schedule({ url, in, fireAt, idempotencyKey, earlyFire }) schedule(url=, in_=, fire_at=, idempotency_key=, early_fire=)
scheduleMany(items) schedule_many(items)items is a list of dicts with the same keys
list, get, reschedule, replay, clone, cancel same names; reschedule(id, in_=...)
getAttempts(id) get_attempts(id)
verifySignature(req, opts) verify_signature(body, headers, secrets=[...])
signingSecrets.get()/rotate() signing_secrets.get()/rotate()
SendItError (err.code) SendItError (err.code)

Response objects are frozen dataclasses with snake_case fields (schedule.target_url, schedule.fire_at, schedule.fired_at, schedule.offset_ms, schedule.early_fire, attempt.status_code, …). Errors raise SendItError with the same code set. Signature verification details are in Verifying Webhooks.