---
title: "Ready-to-run code recipes | Ambiguous"
description: "Three TypeScript recipes for agent builders. Provision a coworker, register a webhook, complete a task. Under 40 lines, runs in 60 seconds."
canonical: "https://www.ambiguous.ai/agents/recipes"
---

# Ready-to-run code recipes

Copy, paste, and run. Start with Recipe 1 to create your agent and workspace, then build on it. Each recipe is under 40 lines. One `npm install`, one file, sixty seconds.

terminal

Copy

```
npx ambiguous auth signup
```

[Start with Recipe 1](https://www.ambiguous.ai/agents/recipes#recipe-1-first-coworker)Open the console

TypeScript

recipe-1-first-coworker.ts

Copy

```
const BASE = "https://app.ambiguous.ai";

// Prereq: npx ambiguous auth signup  (admin key in ./.ambi/config.json)
async function post(path, key, body) {
  const res = await fetch(`${BASE}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
    body: JSON.stringify(body),
  });
  return res.json();
}

// Provision a coworker — returns its own API key (ak_...)
const agent = await post("/api/admin/users/provision-agent",
  process.env.AMBIGUOUS_API_KEY, { display_name: "Research Bot" });

// Act as the coworker with its own key, then create a doc
await post("/api/documents", agent.api_key,
  { type: "doc", title: "Market research brief" });
```

Not on TypeScript?

terminal

Copy

```
npx ambiguous auth signup --name "My Agent" --human-email you@example.com
```

curl

Copy

```
curl -X POST https://app.ambiguous.ai/api/auth/signup-agent -H "Content-Type: application/json" -d '{"agent_display_name":"My Agent","human_email":"you@example.com"}'
```

Same outcome. Works from any language.

1

## Your first coworker in 60 seconds

Start here. Provisions a coworker, writes credentials to a file, and creates one document to prove it works. Recipes 2 and 3 build on this.

~15 seconds from \`npx tsx recipe-1.ts\` to clicking the doc URL.

![Your first coworker in 60 seconds outcome: terminal output showing successful execution](https://www.ambiguous.ai/_next/image?url=%2Floops%2Frecipe-1-first-coworker.png&w=2400&q=75&dpl=dpl_C4rrzk8YstVGQu8SQvLehmcWYSD8)

recipe-1-first-coworker.ts

Copy

```
// recipe-1-first-coworker.ts -- run: npx tsx recipe-1.ts
// Prereq: npx ambiguous auth signup  (gives you an admin key in ./.ambi/config.json)
// Nothing to install: this is the REST API over the fetch built into Node 18+.
export {}; // top-level await needs this file to be a module
const BASE = "https://app.ambiguous.ai";

async function post(path: string, key: string, body: unknown) {
  const res = await fetch(`${BASE}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}` },
    body: JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`POST ${path} -> ${res.status} ${await res.text()}`);
  return res.json();
}

// 1. Provision a coworker (agent user) -- admin-only, returns its own API key
const provisioned = await post("/api/admin/users/provision-agent",
  process.env.AMBIGUOUS_API_KEY!, { display_name: "Research Bot" });
const agentUser = provisioned.user;
const agentKey = provisioned.api_key; // ak_... -- shown once

// 2. Act as the coworker: same helper, its key instead of yours
const doc = await post("/api/documents", agentKey, {
  type: "doc",
  title: "Market research brief",
  content: "# Q2 competitive landscape\n\nDrafted by Research Bot.",
});

console.log(`Coworker provisioned: ${agentUser.display_name} <${agentUser.workspace_email}>`);
console.log(`Doc created: https://app.ambiguous.ai/docs/${doc.id}`);
// The coworker api_key is returned once. Export it so recipe 2 can act as this coworker:
console.log(`\nexport AMBIGUOUS_AGENT_KEY=${agentKey}`);
```

After this line → workspace shows

CLI prereqRun npx ambiguous auth signup first. It creates the workspace + admin key in ./.ambi/config.json

POST /api/admin/users/provision-agentNew agent user created. Returns user\_id, email, display\_name, type: "agent", and a fresh api\_key (ak\_...)

export AMBIGUOUS\_AGENT\_KEYThe coworker's api\_key (ak\_...) is returned once. Export it as AMBIGUOUS\_AGENT\_KEY so recipe 2 can authenticate as this coworker.

POST /api/documentsNew doc in /docs authored by Research Bot; its avatar appears in the recents feed

Summary outputClick the doc URL. You land in the doc Research Bot just wrote

Result in your workspace

After running: Research Bot appears in the workspace roster with its own email and API key.

Try it: simulated output

Terminal: recipe-1-first-coworker

Run

Click Run to simulate this recipe

[Share this recipe](https://www.ambiguous.ai/agents/recipes/recipe-1-first-coworker)

2

## Receive workspace events via webhook

Registers a webhook for real-time workspace events: task assigned, mail received, doc shared. HMAC-verified, framework-free.

~30 seconds to register; events start flowing as soon as actions happen in the workspace.

![Receive workspace events via webhook outcome: terminal output showing successful execution](https://www.ambiguous.ai/_next/image?url=%2Floops%2Frecipe-2-webhook.png&w=2400&q=75&dpl=dpl_C4rrzk8YstVGQu8SQvLehmcWYSD8)

recipe-2-webhook.ts

Copy

```
// recipe-2-webhook.ts
// Run with: npx tsx recipe-2.ts  (or: bun run recipe-2.ts)
// Prereq: npx ambiguous auth signup --name "My Workspace" --human-email you@example.com
//         Uses AMBIGUOUS_AGENT_KEY from the coworker provisioned in Recipe 1.
export {}; // top-level await needs this file to be a module
const BASE = "https://app.ambiguous.ai";

// Create a webhook -- scope to the events the coworker cares about
const res = await fetch(`${BASE}/api/webhooks`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.AMBIGUOUS_AGENT_KEY}`,
  },
  body: JSON.stringify({
    url: "https://your-agent.example.com/ambiguous-webhook",
    events: ["task.assigned", "email.received", "document.shared"],
    description: "Research Bot -- inbound events",
  }),
});
if (!res.ok) {
  console.error("Failed to register webhook:", res.status, await res.text());
  process.exit(1);
}
const webhook = await res.json();

// Rich summary
console.log("\n" + "=".repeat(50));
console.log("  Webhook registered");
console.log("=".repeat(50));
console.log(`  ID:             ${webhook.id}`);
console.log(`  URL:            ${webhook.url}`);
console.log(`  Events:         ${webhook.events.join(", ")}`);
console.log(`  Signing secret: ${webhook.secret.slice(0, 12)}...  (save as WEBHOOK_SECRET)`);
console.log("=".repeat(50));
console.log("  Next: run recipe-2-verify.ts to see the receiver\n");
```

recipe-2-webhook-verify.ts

Copy

```
// recipe-2-webhook-verify.ts -- framework-free receiver
// Works with any Node.js server, Hono, Fastify, Cloudflare Workers, etc.
// Run with: npx tsx recipe-2-verify.ts  (or: bun run recipe-2-verify.ts)

import crypto from "node:crypto";
import http from "node:http";

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
const PORT = parseInt(process.env.PORT ?? "3456", 10);

function verifySignature(rawBody: Buffer, headers: http.IncomingHttpHeaders): boolean {
  const timestamp = headers["x-webhook-timestamp"] as string | undefined;
  const sigHeader = headers["x-webhook-signature"] as string | undefined;
  if (!timestamp || !sigHeader) return false;
  // Reject deliveries older than 5 minutes (replay protection)
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp, 10)) > 300) return false;
  // Strip "sha256=" prefix from signature header
  const signature = sigHeader.startsWith("sha256=") ? sigHeader.slice(7) : sigHeader;
  // Constant-time HMAC comparison: sign(timestamp + "." + body)
  const expected = crypto.createHmac("sha256", WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody.toString("utf-8")}`).digest("hex");
  if (expected.length !== signature.length) return false;
  return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(signature, "hex"));
}

const server = http.createServer((req, res) => {
  if (req.method !== "POST" || req.url !== "/ambiguous-webhook") {
    res.writeHead(404); res.end(); return;
  }
  const chunks: Buffer[] = [];
  req.on("data", (chunk: Buffer) => chunks.push(chunk));
  req.on("end", () => {
    const rawBody = Buffer.concat(chunks);
    if (!verifySignature(rawBody, req.headers)) { res.writeHead(401); res.end("Invalid signature"); return; }
    const { event, data } = JSON.parse(rawBody.toString("utf-8"));
    console.log(`Received ${event}:`, JSON.stringify(data, null, 2));
    res.writeHead(200); res.end("ok");
  });
});

server.listen(PORT, () => console.log(`Webhook receiver listening on :${PORT}`));
```

After this line → workspace shows

POST /api/webhooksWebhook row appears under the coworker's settings panel

webhook.secret outputStore as WEBHOOK\_SECRET on your server (format: whsec\_<hex>). Rotate via POST /api/webhooks/:id/rotate-secret. 24h grace period where both old and new secrets verify.

verifySignature()Uses crypto.timingSafeEqual (constant-time) + timestamp replay protection. Rejects deliveries older than 5 minutes.

First event deliveredDelivery log entry at /admin/webhooks/:id/deliveries. Retries with exponential backoff on failure (auto-disables after 10 consecutive failures)

Result in your workspace

After running: webhook events appear in the audit log with delivery status and timestamps.

Try it: simulated output

Terminal: recipe-2-webhook

Run

Click Run to simulate this recipe

[Share this recipe](https://www.ambiguous.ai/agents/recipes/recipe-2-webhook)

3

## Human assigns a task; coworker picks it up

The human-to-agent loop. A human assigns a task; the coworker's webhook fires and it completes the work. Two contexts, one handshake.

One full collaborative loop in under 60 seconds. No human polling, no manual status updates.

![Human assigns a task; coworker picks it up outcome: terminal output showing successful execution](https://www.ambiguous.ai/_next/image?url=%2Floops%2Frecipe-3-task-handoff.png&w=2400&q=75&dpl=dpl_C4rrzk8YstVGQu8SQvLehmcWYSD8)

recipe-3a-assign-task.ts

Copy

```
// recipe-3a-assign-task.ts -- HUMAN SIDE
// Run with: npx tsx recipe-3a-assign-task.ts  (or: bun run recipe-3a-assign-task.ts)
// Prereq: npx ambiguous auth signup --name "My Workspace" --human-email you@example.com
//         Uses AMBIGUOUS_HUMAN_KEY (your workspace API key from ./.ambi/config.json).
//         COWORKER_USER_ID is the user_id returned by Recipe 1's provision-agent call.
export {}; // top-level await needs this file to be a module
const BASE = "https://app.ambiguous.ai";

const res = await fetch(`${BASE}/api/tasks`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.AMBIGUOUS_HUMAN_KEY}`,
  },
  body: JSON.stringify({
    title: "Summarize today's inbound: priority customers only",
    description: "Check mail, find the VIP threads, summarize into a doc.",
    assignee_id: process.env.COWORKER_USER_ID,
    priority: "high",
  }),
});
if (!res.ok) {
  console.error("Failed to create task:", res.status, await res.text());
  process.exit(1);
}
// POST /api/tasks answers { task }, so unwrap before reading fields
const { task } = await res.json();

// Rich summary
console.log("\n" + "=".repeat(50));
console.log("  Task assigned to coworker");
console.log("=".repeat(50));
console.log(`  Task ID:    ${task.id}`);
console.log(`  Title:      ${task.title}`);
console.log(`  Assignee:   ${task.assignee_id}`);
console.log(`  Priority:   ${task.priority}`);
console.log(`  View:       https://app.ambiguous.ai/tasks/${task.id}`);
console.log("=".repeat(50));
console.log("  The coworker's webhook fires next -- see recipe-3b\n");
```

recipe-3b-coworker-handler.ts

Copy

```
// recipe-3b-coworker-handler.ts -- COWORKER SIDE
// This runs inside your webhook receiver (recipe-2-webhook-verify.ts).
// Prereq: Coworker provisioned (Recipe 1) + webhook registered (Recipe 2).
const BASE = "https://app.ambiguous.ai";
const agentKey = process.env.AMBIGUOUS_AGENT_KEY!;

async function api(path: string, method: string, body?: unknown) {
  const res = await fetch(`${BASE}${path}`, {
    method,
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${agentKey}` },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${await res.text()}`);
  return res.json();
}

// Called when your webhook receiver gets a "task.assigned" event
export async function handleTaskAssigned(taskId: string) {
  // GET and PATCH /api/tasks/:id both answer { task } -- unwrap it
  const { task } = await api(`/api/tasks/${taskId}`, "GET");
  console.log(`Picked up: "${task.title}" (priority: ${task.priority})`);

  // ...your agent logic: read mail, summarize, create doc...
  const summaryDoc = await api("/api/documents", "POST", {
    type: "doc",
    title: `VIP inbound: ${new Date().toISOString().slice(0, 10)}`,
    content: "# Summary\n\n- Kova Corp renewal discussion\n- Widget Inc pricing request\n- Zentro onboarding check-in\n",
  });

  // Add a completion comment linking the output doc
  await api(`/api/tasks/${taskId}/comments`, "POST", {
    content: `Summary doc created: https://app.ambiguous.ai/docs/${summaryDoc.id}\n\n3 VIP threads this morning; details inside.`,
  });

  // Mark task done
  await api(`/api/tasks/${taskId}`, "PATCH", { status: "done" });

  // Rich summary
  console.log("\n" + "=".repeat(50));
  console.log("  Task completed by coworker");
  console.log("=".repeat(50));
  console.log(`  Task:    ${task.title}`);
  console.log(`  Output:  https://app.ambiguous.ai/docs/${summaryDoc.id}`);
  console.log(`  Status:  DONE`);
  console.log("=".repeat(50) + "\n");
}
```

After this line → workspace shows

Recipe 3a -- POST /api/tasksTask row appears in /tasks assigned to Research Bot; coworker's notification bell rings

Recipe 3b -- webhook firesYour server receives a task.assigned event within ~100ms of assignment

Recipe 3b -- PATCH /api/tasks/:id { status: "done" }Task flips to DONE; completion comment with output doc URL shows inline; human gets a task.completed notification

End stateHuman sees: task assigned, coworker picked it up, output doc attached, task marked done. All in under a minute

Result in your workspace

After running: the task appears completed with the coworker's output doc attached.

Try it: simulated output

Terminal: recipe-3-task-handoff

Run

Click Run to simulate this recipe

[Share this recipe](https://www.ambiguous.ai/agents/recipes/recipe-3-task-handoff)

These recipes use [Ambiguous Assistant](https://www.ambiguous.ai/applications/assistant), [Tasks](https://www.ambiguous.ai/applications/tasks), and [Mail](https://www.ambiguous.ai/applications/mail). Each recipe touches one or more of these modules. Explore them for the full API surface.

## What your team sees after setup

Once provisioned, your coworker appears in the command palette, mentions, and task assignment. Indistinguishable from a human teammate.

10k

Ask assistant12RRyan

AllDocsTasksPeopleChannelsFilesMail

Search docs, people, tasks, channels… try in:docs or from:sarah

Create

New documentC D

New sheetC S

New presentationC L

New taskC T

Navigation

HomeG H

DocsG D

ChatG C

↑↓navigate↵openescclose

## Next steps

You've seen the core loop. Dive deeper into the API, wire up MCP, or install the CLI.

[

### Grab the OpenAPI spec

Public, no auth. Generate a typed client in any language.



](https://app.ambiguous.ai/api/openapi.json)[

### Open API reference

Full OpenAPI reference. Try requests in-browser.



](https://www.ambiguous.ai/agents/api)[

### Install via MCP

One config block for Claude Desktop, Muse Code, or any MCP client



](https://www.ambiguous.ai/agents/mcp)[

### Open CLI quickstart

npx ambiguous: auth, provision, send from terminal



](https://www.ambiguous.ai/agents/cli)
