---
title: BOTCHA Integration Guide
description: Add BOTCHA AI agent verification to your API. Fully agentic — a coding agent can complete every step (account creation, SDK install, route protection) without human intervention. No dashboard, no manual signup.
version: 1.0.0
url: https://api.botcha.xyz/integration.md
---

# BOTCHA — Integrate Agency Verification Into Your API

BOTCHA gates your API to verified AI agents. Agents solve a timed reasoning challenge, get a signed receipt (JWT), and pass it in a header. Your server validates the receipt. Unverified requests get a 403 that points agents to instructions — you write zero agent-facing docs.

**You are a coding agent? You can complete every step below yourself.** There is no dashboard, no manual signup, no human required. Account creation uses the CLI and one HTTP call — you have everything you need. Create the account, install the SDK, protect the routes, and test it.

Follow these steps in order. You'll be done in under 10 minutes.

---

## Step 1: Create an Account

No human needed — you do this yourself. You solve a BOTCHA challenge (proving you're a genuine agent), then sign up with the receipt. This is the same flow every agent on the platform you're building will experience.

**Install the CLI:**

```bash
command -v botcha >/dev/null 2>&1 || npm install -g @botcha/cli@latest
```

**1a. Get a challenge:**

```bash
botcha challenge --key pk_live_botcha_bootstrap
```

Returns JSON with a `challengeId` and a `prompt`. The prompt contains a timed reasoning puzzle.

**1b. Solve it.** Read the prompt. Show step-by-step reasoning, format your answer exactly as instructed, and include the nonce phrase from the prompt. Submit:

```bash
botcha solve --id <challengeId> --response "your reasoning and answer..."
```

For longer responses (recommended — avoids shell escaping):

```bash
echo "$RESPONSE" | botcha solve --id <challengeId>
```

Returns `{ "verified": true, "receipt": "eyJ..." }`. If `verified` is `false`, retry from 1a.

**1c. Sign up with your receipt:**

```
POST https://api.botcha.xyz/v1/signup
Content-Type: application/json

{ "name": "My Platform", "receipt": "<from 1b>" }
```

Returns:
```json
{
  "account_id": "...",
  "project": {
    "id": "...",
    "name": "My Platform",
    "sk_live": "sk_live_...",
    "pk_live": "pk_live_..."
  }
}
```

Store both keys. `sk_live` is shown only once.

**What the keys are:**
- `sk_live` — Secret key. Used server-side to validate agent receipts. Never expose to clients.
- `pk_live` — Public key. Embedded in your 403 responses so agents can request challenges scoped to your project.

Set these environment variables in your server:

```
BOTCHA_SECRET_KEY=sk_live_...   # from signup response
BOTCHA_PUBLIC_KEY=pk_live_...   # from signup response
BOTCHA_PROJECT_ID=...           # optional — the project.id from signup, enables fast local JWT verification
```

> You just solved a BOTCHA challenge — the same experience every agent will have on your platform. The CLI wraps the same API your SDK will call. From here, you're protecting routes.

---

## Step 2: Install the SDK

```
npm install @botcha/sdk
```

Choose the import for your framework:

| Framework | Import path |
|-----------|-------------|
| Express | `@botcha/sdk` |
| Hono | `@botcha/sdk/hono` |
| Next.js App Router | `@botcha/sdk/next` |
| Any Web API framework (SvelteKit, Remix, Deno, Astro) | `@botcha/sdk/web` |

---

## Step 3: Protect Your Routes

Pick the section matching your framework.

### Express

```javascript
import express from "express";
import { createBotcha } from "@botcha/sdk";

const app = express();
const botcha = createBotcha({
  secretKey: process.env.BOTCHA_SECRET_KEY,  // sk_live_...
  projectId: process.env.BOTCHA_PROJECT_ID,  // optional — enables fast local JWT verification
});

// Apply to routes you want to gate
app.use("/api/protected", botcha.middleware({
  publicKey: process.env.BOTCHA_PUBLIC_KEY,  // pk_live_... — included in 403 responses
}));

app.post("/api/protected/action", (req, res) => {
  // Middleware sets req.botcha on success
  const { challengeId, fresh } = req.botcha;
  res.json({ ok: true });
});
```

### Hono (Cloudflare Workers)

On Cloudflare Workers, env bindings are per-request via `c.env`. Create the BOTCHA instance inside a middleware:

```javascript
import { Hono } from "hono";
import { createBotcha } from "@botcha/sdk/hono";

const app = new Hono();

// Initialize per-request (required on CF Workers where env is per-request)
app.use("/api/protected/*", async (c, next) => {
  const botcha = createBotcha({
    secretKey: c.env.BOTCHA_SECRET_KEY,
    projectId: c.env.BOTCHA_PROJECT_ID,
  });
  const mw = botcha.middleware({ publicKey: c.env.BOTCHA_PUBLIC_KEY });
  return mw(c, next);
});

app.post("/api/protected/action", (c) => {
  // Middleware sets c.get("botcha") on success
  const { challengeId, fresh } = c.get("botcha");
  return c.json({ ok: true });
});
```

### Next.js (App Router)

Two files. The middleware verifies the receipt and forwards the result to route handlers via a header.

```javascript
// middleware.ts
import { NextResponse } from "next/server";
import { createBotcha, CONTEXT_HEADER } from "@botcha/sdk/next";

const botcha = createBotcha({
  secretKey: process.env.BOTCHA_SECRET_KEY,
});

export async function middleware(request) {
  if (!request.nextUrl.pathname.startsWith("/api/protected")) {
    return NextResponse.next();
  }

  const result = await botcha.protect(request, {
    publicKey: process.env.BOTCHA_PUBLIC_KEY,
  });
  if (result instanceof Response) return result;  // 403 — agent gets self-onboarding instructions

  const headers = new Headers(request.headers);
  headers.set(CONTEXT_HEADER, JSON.stringify(result));
  return NextResponse.next({ request: { headers } });
}
```

```javascript
// app/api/protected/route.ts
import { getBotchaContext } from "@botcha/sdk/next";

export async function POST(request) {
  const botcha = getBotchaContext(request);
  if (!botcha) return Response.json({ error: "unauthorized" }, { status: 403 });

  const { challengeId, fresh } = botcha;
  return Response.json({ ok: true });
}
```

### Web API (SvelteKit, Remix, Deno, Astro, etc.)

```javascript
import { createBotcha } from "@botcha/sdk/web";

const botcha = createBotcha({
  secretKey: process.env.BOTCHA_SECRET_KEY,
});

async function handleRequest(request) {
  const result = await botcha.protect(request, {
    publicKey: process.env.BOTCHA_PUBLIC_KEY,
  });
  if (result instanceof Response) return result;  // 403 or 500

  // result is a VerifyResult — request is verified
  const { challengeId, fresh } = result;
  return Response.json({ ok: true });
}
```

### Direct Verification (any framework)

If you don't want middleware, call `verify()` directly:

```javascript
const receipt = request.headers.get("x-botcha-receipt");
const result = await botcha.verify(receipt);

if (!result.valid) {
  // result.reason is one of: INVALID_RECEIPT, RECEIPT_EXPIRED, PROJECT_MISMATCH, etc.
  return Response.json({ error: result.reason }, { status: 403 });
}

// result: { valid, challengeId, projectId, issuedAt, expiresAt, fresh }
```

---

## Verification Result

On success, the verified result contains:

| Field | Type | Description |
|-------|------|-------------|
| `valid` | `true` | Verification passed |
| `challengeId` | `string` | Which challenge was solved |
| `projectId` | `string` | Which project the receipt is for |
| `issuedAt` | `number` | Unix timestamp when receipt was issued |
| `expiresAt` | `number` | Unix timestamp when receipt expires |
| `fresh` | `boolean` | `true` if receipt was issued within the last 10 minutes — use this to reject stale/replayed receipts |

---

## What Agents See When Blocked

When an agent hits a protected endpoint without a valid receipt, the SDK returns this 403:

```json
{
  "error": "BOTCHA_REQUIRED",
  "message": "Access denied — this endpoint requires BOTCHA agency verification. Read https://api.botcha.xyz/skill.md for instructions.",
  "reason": "MISSING_RECEIPT",
  "public_key": "pk_live_...",
  "receipt_header": "x-botcha-receipt",
  "skill_url": "https://api.botcha.xyz/skill.md"
}
```

The `message` points agents to `skill.md` — the single source of truth for how to authenticate. The structured fields (`public_key`, `receipt_header`) give agents the data they need. `skill.md` tells them to use these fields. You don't need to write any agent documentation.

---

## Step 4: Verify It Works

Test your protected endpoint end-to-end.

**Test the 403 (no receipt):**

```bash
curl -s -X POST https://your-api.com/api/protected/action | jq .error
# Expected: "BOTCHA_REQUIRED"
```

**Get a receipt using your key:**

```bash
botcha challenge --key YOUR_PK_LIVE
# Read the prompt, solve it
botcha solve --id <challengeId> --response "your reasoning and answer..."
# Copy the receipt from the response
```

**Test with a valid receipt:**

```bash
curl -s -X POST https://your-api.com/api/protected/action \
  -H "x-botcha-receipt: <receipt from above>" | jq .
# Expected: your endpoint's normal response
```

If you get `BOTCHA_REQUIRED` on the first call and your normal response with the receipt, integration is complete.

---

## Managing Keys

Create additional `pk_live` keys for different endpoints or environments:

```
POST https://api.botcha.xyz/v1/projects/:id/keys
Authorization: Bearer sk_live_...
Content-Type: application/json

{ "label": "search endpoint" }
```

Create additional projects:

```
POST https://api.botcha.xyz/v1/projects
Authorization: Bearer sk_live_...
Content-Type: application/json

{ "name": "Another App" }
```

---

## API Reference

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/v1/challenge` | `X-Botcha-Key: pk_live_*` | Get a challenge |
| POST | `/v1/solve` | None | Submit response, get receipt |
| POST | `/v1/validate` | `Bearer sk_live_*` | Validate receipt |
| POST | `/v1/signup` | Receipt in body | Create account + first project |
| POST | `/v1/projects` | `Bearer sk_live_*` | Create another project |
| GET | `/v1/projects` | `Bearer sk_live_*` | List projects |
| POST | `/v1/projects/:id/keys` | `Bearer sk_live_*` | Add public key to project |
