Oakstack overview

Oakstack is one API key for the infrastructure every app ends up needing:

Module What it does Status
clock Call a URL in your app on a cron schedule, with retries and a run log Available
hook A permanent inbound URL for webhooks: store every event, forward it, retry, replay Available
print HTML in, PDF out, with hosted templates Coming soon
post Transactional email, plus inbound email delivered to a hook Coming soon

It's built for coding agents. The SDK, the MCP server, and these docs are designed so an agent can set everything up from a one-line request like "run my cleanup every night at 3am Chicago time."

Setup

  1. Create an account and an API key at https://oakstack.dev/dashboard/api-keys.
  2. Put the key in your app's environment as OAKSTACK_API_KEY. Keys start with ok_live_ or ok_test_; never commit them.
  3. Pick one or more ways in:

SDK (TypeScript/JavaScript):

npm install oakstack
import { Oakstack } from "oakstack";
const oakstack = new Oakstack(); // reads OAKSTACK_API_KEY

The SDK has zero dependencies and runs in Node 18+, Bun, Deno, Vercel Edge, and Cloudflare Workers.

MCP (lets your coding agent manage Oakstack directly):

# Hosted, nothing to install:
claude mcp add --transport http oakstack https://oakstack.dev/mcp --header "Authorization: Bearer $OAKSTACK_API_KEY"

# Or local over stdio:
claude mcp add oakstack -e OAKSTACK_API_KEY=ok_live_... -- npx -y oakstack-mcp

REST: every endpoint is under https://oakstack.dev/api/v1/ and takes Authorization: Bearer <key>.

Quick examples

// Call your app every weekday at 9:00 Chicago time.
const job = await oakstack.clock.jobs.create({
  name: "Morning digest",
  schedule: "0 9 * * 1-5",
  timezone: "America/Chicago",
  url: "https://yourapp.com/api/cron/digest",
});

// A permanent webhook URL for Stripe that never loses events.
const endpoint = await oakstack.hook.endpoints.create({
  name: "Stripe",
  forwardUrl: "https://yourapp.com/api/webhooks/stripe",
});
// Give endpoint.url to Stripe.

Requests Oakstack sends to your app are signed. Verify them with verifySignature (see signatures).

Errors

Every error response is JSON with a machine-readable name and a message that says what to fix:

{
  "statusCode": 400,
  "name": "invalid_request",
  "message": "schedule: must be a 5-field cron expression ..."
}

The SDK throws these as OakstackError (statusCode, errorName, message).

Status name Meaning What to do
400 invalid_request A field is missing or invalid Fix the field named in message
400 invalid_idempotency_key Bad Idempotency-Key header Use 1-255 printable characters, such as a UUID
401 missing_api_key / invalid_api_key No key, or the key is wrong, revoked, or expired Check OAKSTACK_API_KEY
403 restricted_api_key A standard key used on a full-access endpoint Use a full-access key
403 workspace_paused / workspace_suspended The workspace has been stopped See the dashboard
404 not_found No such object in your workspace Check the id
409 idempotency_in_progress The same Idempotency-Key is still being processed Retry shortly (the SDK does this)
422 idempotency_key_reused The same key was sent with a different request Use a new key
429 rate_limited Too many requests this minute Wait for Retry-After (the SDK does this)
429 usage_limit_reached Your plan's limit is used up Upgrade, free up capacity, or wait for the monthly reset. Don't retry.
500 internal_error Something failed on Oakstack's side Retry (the SDK does this)

Retries and idempotency

Send an Idempotency-Key header on any POST, PATCH, or DELETE. If you retry with the same key, you get the original response back instead of a second job or endpoint. Keys are remembered for 24 hours per workspace.

The SDK does this for you: every write gets a fresh key that's reused across its automatic retries. To make a write safe across process restarts too (for example, a setup script), pass your own key:

await oakstack.clock.jobs.create(params, { idempotencyKey: "setup-nightly-cleanup-v1" });

Plans and limits

Limits are hard caps. When you reach one, Oakstack stops with usage_limit_reached instead of charging you more.

Free Builder ($20/mo) Studio ($49/mo)
Scheduled jobs 3 25 100
Webhook events per month 1,000 50,000 250,000
PDFs per month (soon) 25 1,000 5,000
Emails per month (soon) 100, test mode 10,000 50,000
API requests per minute 60 600 1,200
Log retention 7 days 7 days 30 days

Monthly limits reset on the 1st (UTC). Check usage any time:

const { plan, usage } = await oakstack.usage();

Every API response also carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers.

What Oakstack will and won't call

Cron jobs and webhook forwarding only call public https:// URLs. Localhost, private networks, and cloud metadata addresses are refused, including hostnames that resolve to them. Redirects are not followed. Your endpoint should answer 2xx within 15 seconds; for longer work, answer 202 and continue in the background.

To test locally, expose your dev server with a tunnel (for example cloudflared tunnel or ngrok) and use its https URL.

More