REST API Reference
The SDK is a thin wrapper over this HTTP API. Use it directly from any language.
- Base URL:
https://api.sendit-whenever.com - Auth:
Authorization: Bearer sw_live_…(or a session cookie for dashboard actions). See Authentication. - Content type:
application/jsonfor request bodies.
All endpoints are under /v1.
Schedules
| Method | Path | Description |
|---|---|---|
POST |
/v1/schedules |
Create one schedule. |
POST |
/v1/schedules/bulk |
Create a batch of schedules, up to your plan's max batch size (partial success). |
GET |
/v1/schedules |
List schedules. Query: status, q, limit, offset. |
GET |
/v1/schedules/:id |
Get one schedule. |
GET |
/v1/schedules/:id/attempts |
Delivery-attempt log for one schedule (within the plan retention window). |
PATCH |
/v1/schedules/:id |
Reschedule (only while scheduled). |
DELETE |
/v1/schedules/:id |
Cancel. |
POST |
/v1/schedules/:id/replay |
Re-send the same payload as a new schedule. |
POST |
/v1/schedules/:id/clone |
Re-send with a new payload. |
POST /v1/schedules
POST /v1/schedules
Authorization: Bearer sw_live_xxx
Content-Type: application/json
| Field | Type | Required | Notes |
|---|---|---|---|
targetUrl |
string | yes | HTTPS only, port 80/443, no embedded credentials. Subject to SSRF checks. |
fireAt |
string | yes | ISO-8601 with timezone offset. Must be in the future. |
payload |
string | no | UTF-8 string. Encrypted at rest. Size capped per plan. |
method |
enum | no | GET/POST/PUT/PATCH/DELETE. Default POST. |
headers |
object | no | Custom headers. Host, Content-Length, X-SendIt-Signature are reserved. |
idempotencyKey |
string | no | 1–255 chars. Re-using a key returns the existing schedule. |
earlyFire |
boolean | no | Opt in to Early Firing for this schedule. Defaults to your account setting (off unless changed). |
Over REST,
payloadis a string. To send JSON, JSON-encode your object into the string and set aContent-Type: application/jsonentry inheaders. (The SDK does this for you when you pass an object.)
Returns 201 with { id, fire_at, status, over_limit } for a new schedule (over_limit is true while you're in the 10% grace over your monthly quota). Re-using an idempotencyKey returns the existing schedule with 200.
The schedule object
GET /v1/schedules/:id, GET /v1/schedules items, PATCH, and DELETE return the full schedule:
{
"id": "sch_…",
"target_url": "https://api.example.com/hook",
"method": "POST",
"status": "succeeded",
"fire_at": "2026-06-17T09:00:00.000Z",
"fired_at": "2026-06-17T09:00:00.180Z",
"offset_ms": 180,
"early_fire": false,
"attempts": 1,
"payload_size_bytes": 42,
"has_payload": true,
"created_at": "2026-06-17T08:00:00.000Z",
"updated_at": "2026-06-17T09:00:00.180Z"
}
fired_at/offset_msare the measured punctuality of the first delivery attempt —nulluntil the schedule fires.offset_ms = fired_at − fire_at(positive = late, negative = early-fire only). See Punctuality.- The payload plaintext is never returned — only
has_payloadandpayload_size_bytes.
POST /v1/schedules/bulk
Create a batch of schedules in one call, up to your plan's max batch size (Free 100, Indie 500 — see Plans & Limits). Each item uses the same fields as POST /v1/schedules. The call succeeds partially — valid items are created, invalid ones are reported — and returns 200 with:
{
"accepted": [{ "index": 0, "id": "sch_…", "fire_at": "…", "status": "scheduled" }],
"rejected": [{ "index": 1, "code": "VALIDATION", "message": "fireAt must be in the future" }]
}
index is the item's position in your request array. Duplicate idempotencyKeys within one batch are rejected as CONFLICT.
Filtering GET /v1/schedules
?status=scheduled&q=billing&limit=50&offset=0 → { items, total, limit, offset }.
Delivery log
| Method | Path | Description |
|---|---|---|
GET |
/v1/logs |
Delivery attempts across all your schedules, newest first. |
A unified, time-ordered feed of every delivery attempt (one row per attempt, including retries) — joined with its schedule's target and method.
?outcome=failed&q=billing&limit=50&offset=0 → { items, total, limit, offset, retention_days }.
outcome—succeeded(HTTP 2xx) orfailed(non-2xx, including network/blocked attempts with no status code).- Only attempts within your plan's log retention window are returned (
retention_days: Free 7, Indie 30). Older attempts are filtered out — the same window also applies toGET /v1/schedules/:id/attempts. - Each row:
schedule_id,target_url,method,status_code,latency_ms,error_text,attempted_at.error_textis a capped snippet of the target's response — never your payload.
Overview & usage
| Method | Path | Description |
|---|---|---|
GET |
/v1/overview |
Dashboard summary for your account. Accepts API key or session. |
GET |
/v1/usage |
Current month's schedule usage against your plan quota. |
GET |
/v1/status/punctuality |
Public, no auth — anonymized service-wide punctuality. |
GET /v1/overview?period=24h|7d|30d (default 24h) returns active count, success rate, the punctuality offset distribution, recent volume, a bucketed time series, and an attention summary — all clamped to your plan's retention window so it never disagrees with the delivery log:
{
"period": "24h",
"active_schedules": 12,
"success_rate": 0.991,
"offset_ms": { "p50": 140, "p95": 380, "min": 12, "max": 910 },
"recent_volume": 1840,
"series": [{ "t": "2026-06-17T08:00:00.000Z", "count": 80, "p50": 130, "p95": 360 }],
"needs_attention": { "failed": 2, "due_soon": 5, "dead": 0 }
}
GET /v1/usage returns your monthly schedule count against the quota:
{ "period": "202606", "plan": "free", "scheduled": 1840, "limit": 2000, "grace_limit": 2200, "over_limit": false }
GET /v1/status/punctuality is public and anonymized — no account, target, or payload data — and is short-TTL cached:
{ "window": "24h", "sent": 5231, "on_time_rate": 0.987, "offset_ms": { "p50": 150, "p95": 410 }, "updated_at": "2026-06-17T09:00:00.000Z" }
offset_ms here floors early-fire offsets at zero, so an intentional early send never flatters the average; per-schedule values from GET /v1/schedules/:id remain the raw measured truth.
Webhook signing
| Method | Path | Description |
|---|---|---|
GET |
/v1/signing-secrets |
Get current and next secrets. |
POST |
/v1/signing-secrets/rotate |
Promote next to current, generate a new next. |
Account & keys (session-only)
| Method | Path | Description |
|---|---|---|
GET |
/v1/me |
Profile + plan. Accepts API key or session. |
PATCH |
/v1/me |
Update display name. |
DELETE |
/v1/me |
Delete the account. |
GET |
/v1/api-keys |
List keys (never the raw value). |
POST |
/v1/api-keys |
Create a key (raw value shown once, 201). |
POST |
/v1/api-keys/:id/rotate |
Issue a replacement key and revoke the old one (new raw value shown once, 201). |
POST |
/v1/api-keys/:id/revoke |
Revoke a key (204). |
Relay settings (session-only)
Per-account delivery knobs — a downthrottled concurrency rate (CPS) and custom retry behaviour, each capped by your plan. See Reliability.
| Method | Path | Description |
|---|---|---|
GET |
/v1/me/relay-settings |
Current settings (your saved values; null = inherit plan default), effective (what actually applies after clamping/inheritance), limits, and defaults. |
PATCH |
/v1/me/relay-settings |
Update cpsLimit, retryAttempts, retryBackoffMs, retryStrategy. Omit a field to leave it unchanged; send null to clear it back to the plan default. Values are clamped to your plan's limits. |
Auth & billing (session)
| Method | Path | Description |
|---|---|---|
POST |
/v1/auth/magic/start |
Request a magic link (202). |
GET |
/v1/auth/magic/verify |
Verify a magic link, start a session. |
GET |
/v1/auth/oauth/:provider/start |
Begin OAuth (provider = google or github); redirects to the provider. |
GET |
/v1/auth/oauth/:provider/callback |
OAuth callback; starts a session on success. |
POST |
/v1/auth/logout |
End the session (204). |
POST |
/v1/billing/checkout |
Get an upgrade checkout URL. |
POST |
/v1/billing/portal |
Get a subscription-management URL. |
Health
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
Liveness. |
GET |
/readyz |
Readiness (database + queue). |
GET |
/metricsz |
Operational metrics (worker heartbeat, queue depth). |
Responses & errors
Successful reads and writes return JSON objects mirroring the SDK types. Errors return a structured body — { error: { code, message } } — with an appropriate HTTP status. Note the code/message are nested under error. See Error Codes.