Demo video 2: https://youtu.be/SqdW-1NShvU

Nod is an approval API for AI agents, scripts, and workflows.

Your code asks for permission. A human approves in Slack or web. Nod sends back a signed webhook so your system can continue safely.

The simple idea is:

AI agent / workflow
        ↓
asks Nod for approval
        ↓
human approves in Slack or web
        ↓
Nod sends signed callback
        ↓
workflow continues

Inspiration

AI agents and automated workflows are starting to take real potentially destructive actions like deploying code, issuing refunds, changing customer data, and triggering infrastructure. But the moment those actions become sensitive, teams fall back to ad hoc Slack messages, manual checklists, or custom one-off approval flows.

I wanted a simple way for code to ask: “Can I do this?”

What it does

Imagine an agent trying to refund a customer that shouldn't be allowed. With Nod, the development team can install the SDK with few lines of code start and start receiving approval requests right where the team lives like Slack, Email, Discord etc.

The team creates a policy. Adds channels like Slack/Email to that policy. And then development team can integrate Nod easily using the SDK.

const approval = await nod.approvals.create({
  idempotencyKey: "deploy:abc123",
  policyId: "pol_prod_deploy",
  title: "Deploy to production?",
})

Once an approval request comes, Nod notifies the channels in that policy and once a decision is received it emits the webhook event which can be listened easily using the SDK.

const event = nod.webhooks.verify({
  rawBody,
  headers: request.headers,
  secret: process.env.NOD_WEBHOOK_SECRET!,
})

if (event.type === "approval.approved") {
  await continueWorkflow(event.data)
}

How I built it

I started with v0. I wanted to nail the app design. I taught it my taste and iterated with it a lot. And once we came up with components and design system from then onwards it was pretty easy to create new pages.

I built Nod as a TypeScript monorepo with a React Router web app, an Effect Platform API, a worker service containing 3 lambda workers, a public SDK, and shared protocol contracts. The data model uses Drizzle and PostgreSQL, with tables for workspaces, roles, policies, channels, approval requests, decisions, notification deliveries, callbacks, and audit events.

I designed the backend around production concerns from the beginning: idempotency keys, immutable approval requests, policy versioning, Slack signature verification, HMAC-signed webhooks, delivery attempts, callback retries, expiration workers, and append-only audit events.

Challenges I ran into

The hardest design challenge was deciding what Nod should not do. I deliberately avoided becoming a workflow runtime. Nod is not a replacement to workflow runtimes like Inngest, Temporal but it complements them.

There are couple of decisions:

  • Slack approval sounds simple, but it needs signature checks, channel checks, stale button handling, and audit records.
  • Webhooks also needed retries, signing, timestamps, and duplicate protection.
  • I had to design for race conditions, like two people clicking approve/reject at the same time.

Accomplishments that I am proud of

I built a schema and architecture that can support real operational use: workspaces, custom roles, policy versions, reusable channels, Slack integration, callback endpoints, signed webhook delivery, and a TypeScript SDK.

It has a real approval lifecycle: pending, approved, rejected, expired, canceled, with decision records, delivery attempts, callbacks, and audit events.

What did I learn

  • A good approval system needs auth, audit logs, retries, expiration, and secure callbacks.
  • Developers want simple APIs, but teams need strong controls.
  • Automation becomes safer when humans can review the right data at the right moment.

What's next for Nod

Next, I want to finish the end-to-end production approval loop: richer policy management, verified Slack installs and interactions, callback delivery hardening, queue monitoring, and a smoother hosted approval experience.

The biggest feature would be to make actions first class. Developers will define actions in code with schemas and display metadata, sync them to Nod, and let admins create policies like “refunds over $5,000 require Finance approval” without code changes. This lets auto approval.

export const RefundCreate = nod.action("refund.create", {
  schema: RefundSchema,
  display: {
    title: ({ amount }) => `Approve refund of $${amount}?`,
  },
});

Then it become stupidly in the dashboard to create conditions for auto approvals, auto rejection and human approvals based on the schema.

Share this project:

Updates

Submission history