Back to overview

Crutan Developer API

Programmatic access to prospects, page generation, conversion analytics, and real-time webhooks. The same /api/v1 surface documented here powers the official MCP server and every first-party integration.

Introduction

The Crutan API is organized around REST. It accepts JSON request bodies, returns JSON responses, and uses standard HTTP verbs and status codes. All requests are scoped to a single workspace, determined by the API key you authenticate with.

Base URL

text
https://app.crutan.com/api/v1

Replace the host with your own deployment if you are self-hosting. All endpoints in this reference are relative to /api/v1.

Authentication

Authenticate every request with a workspace API key in the Authorization header using the Bearer scheme. Keys are prefixed with crt_ and are created in Settings → API & MCP keys. A key is shown once at creation — store it securely.

curl
curl https://app.crutan.com/api/v1/prospects \
  -H "Authorization: Bearer $CRUTAN_API_KEY"
API access requires a paid plan. Requests authenticated on a trial or inactive subscription return 402 with { "error": "plan_limit", "reason": "api_access_requires_paid_plan" }. Keys may carry a monthly spend cap (UTC calendar month); calls that would exceed it return spend_cap_exceeded.

Conventions

Requests with a body must send Content-Type: application/json. Successful responses return a JSON object keyed by the resource (for example { "prospects": [...] }). List endpoints are capped — request the next window by filtering on created_at.

Errors & status codes

Errors return a JSON object with an error field, and where relevant a machine-readable reason.

402 Payment Required
{
  "error": "plan_limit",
  "reason": "api_access_requires_paid_plan",
  "upgradeUrl": "/settings#billing"
}
StatusMeaning
200Success.
400Malformed request or validation error (e.g. no_prospects).
401Missing or invalid key (api_key_required, workspace_required).
402Plan limit reached or API access not enabled.
404Resource not found (not_found).
429Rate limited — retry after the Retry-After header.

Rate limits

Throttled requests receive 429 with a Retry-After header (seconds). Back off and retry; do not loop tightly.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 3600

{ "error": "rate_limited" }

Idempotency

Write endpoints accept an Idempotency-Key header. Re-sending the same key (per workspace) replays the original response instead of creating duplicate records — safe for retries from CRMs and queues.

curl
curl https://app.crutan.com/api/v1/prospects \
  -X POST \
  -H "Authorization: Bearer $CRUTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: import-2026-06-04-batch-17" \
  -d '{ "email": "jordan@acme.com", "company_name": "Acme" }'

Prospects

get/api/v1/prospects

List prospects in the workspace, newest first (max 500).

200 OK
{
  "prospects": [
    {
      "id": "b1f0…",
      "email": "jordan@acme.com",
      "first_name": "Jordan",
      "last_name": "Lee",
      "company_name": "Acme",
      "company_domain": "acme.com",
      "role_title": "VP Sales",
      "linkedin_url": null,
      "tags": ["q3-outbound"],
      "custom_fields": {},
      "external_ids": { "hubspot": "501" },
      "status": "rendered",
      "created_at": "2026-06-04T18:20:11Z"
    }
  ]
}
post/api/v1/prospects

Create or update prospects. Records are upserted on (workspace, email), so this is safe to call repeatedly. Accepts a single object, a bare array, or an object with a prospects array.

FieldTypeNotes
emailstringRequired. Upsert key.
first_name / last_namestringOptional.
company_name / company_domainstringOptional.
role_title / linkedin_urlstringOptional.
tagsstring[]Optional.
custom_fields / external_idsobjectOptional. Free-form JSON.
curl — bulk upsert
curl https://app.crutan.com/api/v1/prospects \
  -X POST \
  -H "Authorization: Bearer $CRUTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prospects": [
      { "email": "jordan@acme.com", "first_name": "Jordan", "company_name": "Acme" },
      { "email": "sam@globex.com",  "first_name": "Sam",    "company_name": "Globex" }
    ]
  }'
200 OK
{
  "prospects": [
    { "id": "b1f0…", "email": "jordan@acme.com", "first_name": "Jordan",
      "company_name": "Acme", "status": "pending", "created_at": "2026-06-04T18:20:11Z" }
  ]
}
get/api/v1/prospects/{id}

Fetch one prospect, including the personalized pages generated for them.

200 OK
{
  "prospect": {
    "id": "b1f0…",
    "email": "jordan@acme.com",
    "status": "rendered",
    "pages": [
      { "id": "c4a2…", "page_token": "k3f…q9.ab12cd",
        "url": "https://go.yourco.com/k3f…q9.ab12cd", "status": "rendered" }
    ]
  }
}

Pages

get/api/v1/pages/{id}

Return the status and public URL of a generated recipient page.

200 OK
{
  "page": {
    "id": "c4a2…",
    "page_token": "k3f…q9.ab12cd",
    "url": "https://go.yourco.com/k3f…q9.ab12cd",
    "status": "rendered",
    "prospect_id": "b1f0…",
    "template_id": "9d77…",
    "created_at": "2026-06-04T18:25:02Z"
  }
}

Analytics

get/api/v1/analytics

Conversion-first metrics for the workspace plus the hottest leads, ranked by conversions and high-intent signals. Low-value signals (scroll depth, dwell) are intentionally excluded — view them per page in the dashboard.

200 OK
{
  "metrics": {
    "pagesLive": 248,
    "visited": 173,
    "conversions": 41,
    "ctaClicks": 96,
    "conversionRate": 0.165
  },
  "hotLeads": [
    { "page_token": "k3f…q9.ab12cd", "prospect_id": "b1f0…",
      "visits": 4, "cta_clicks": 3, "conversions": 1,
      "high_intent_count": 2, "converted": true,
      "last_conversion_at": "2026-06-04T19:02:55Z" }
  ]
}

Leads

get/api/v1/leads

List lead submissions (form fills and booked meetings), newest first (max 250). Poll this endpoint, or receive the same data in real time via webhooks.

200 OK
{
  "leads": [
    {
      "id": "f02c…",
      "prospect_id": "b1f0…",
      "page_token": "k3f…q9.ab12cd",
      "event_type": "form.submitted",
      "kind": "lead",
      "fields": { "email": "jordan@acme.com", "message": "Let's talk" },
      "created_at": "2026-06-04T19:02:55Z"
    }
  ]
}

Batches

Create a batch to generate personalized pages for many prospects at once, then queue rendering. API-key calls must pass an exact acknowledgedEstimateCents from your cost estimate (same guardrail as MCP).

post/api/v1/batches

Create a draft batch for a template. Omit prospectIds to include up to 5,000 recent prospects in the workspace.

curl
curl https://app.crutan.com/api/v1/batches \
  -X POST \
  -H "Authorization: Bearer $CRUTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "9d77…",
    "batchName": "June outbound",
    "prospectIds": ["b1f0…", "c2a1…"]
  }'
post/api/v1/batches/{id}/generate

Queue async generation (default) or pass { "forceSync": true } for a blocking run in smaller batches.

Request body (API key)
{
  "acknowledgedEstimateCents": 2400,
  "prospectIds": ["b1f0…"]
}
202-style async response
{
  "batchId": "a8c1…",
  "status": "generating",
  "queued": 42,
  "mode": "async"
}

Events

get/api/v1/events

Cursor-paginated export of conversion events (form submits and bookings). Use when you prefer polling over webhooks. Pass cursor fromnextCursor to page forward; limit defaults to 50 (max 250).

200 OK
{
  "events": [
    {
      "id": "f02c…",
      "event": "form.submitted",
      "prospect_id": "b1f0…",
      "page_token": "k3f…q9.ab12cd",
      "fields": { "email": "jordan@acme.com" },
      "delivered_at": "2026-06-04T19:03:01Z",
      "created_at": "2026-06-04T19:02:55Z"
    }
  ],
  "nextCursor": "eyJjcmVhdGVkX2F0Ijoi…"
}

Discovery pages

Discovery pages are personalized follow-up pages generated immediately after a discovery call. The API lets you create a session, submit answers to the AI-surfaced questions, and trigger the page build — all programmatically.

post/api/discovery

Create a discovery session and kick off AI analysis. Supply any combination of transcript, notes, and solutionMap. The session moves to interview_ready once analysis completes (poll GET /api/discovery/{id} or use the MCP tool).

FieldTypeNotes
prospectIduuidOptional. Pin to an existing prospect.
prospectobjectOptional. Inline prospect fields if no prospectId.
transcriptstringOptional. Pasted call transcript.
notesstringOptional. Freeform call notes.
solutionMaparrayOptional. [{ painPoint, solution, proof }]
exclusionsstringOptional. Topics to keep off the page.
consentAttestedbooleanRequired when uploading a recording.
201 Created
{ "sessionId": "d7f3…" }
get/api/discovery/{id}

Poll session status and retrieve the AI-generated questions. The status field progresses through draft → ingesting → interview_ready → answered → building → completed.

200 OK
{
  "session": {
    "id": "d7f3…",
    "status": "interview_ready",
    "template_id": null
  },
  "questions": [
    {
      "id": "q1a2…",
      "question": "How long does implementation take for a team our size?",
      "context": "Raised during the call.",
      "category": "process",
      "ai_suggested_answer": "Typically 2–3 weeks…",
      "answer_source": "ai_rag",
      "rag_confidence": 0.91,
      "status": "open"
    }
  ]
}
post/api/discovery/{id}/answers

Persist answers to the AI-generated questions. Accepts the full questions array with your edits.

curl
curl https://app.crutan.com/api/discovery/d7f3… /answers \
  -X POST \
  -H "Authorization: Bearer $CRUTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "answers": [
      {
        "id": "q1a2…",
        "question": "How long does implementation take for a team our size?",
        "answer": "Typically 2–3 weeks with a dedicated point of contact.",
        "status": "answered"
      }
    ]
  }'
200 OK
{ "questions": [ /* updated array */ ] }
post/api/discovery/{id}/build

Enqueue async page build (Inngest). Debits one discovery credit when the job completes. Returns 202 Accepted immediately — poll GET /api/discovery/{id} until status is completed and template_id is set.

202 Accepted
{ "buildId": "a1b2…", "status": "building" }
Completed session (polled)
{
  "session": { "status": "completed", "template_id": "9d77…" },
  "questions": [ /* … */ ]
}
Discovery pages use a separate credit balance from standard recipient pages. Each workspace plan includes a monthly allotment; overages are billed per page. Check remaining credits with GET /api/billing/entitlements.

Webhooks

Register an endpoint to receive events the moment they happen. Crutan signs every delivery, retries with backoff, and records each attempt.

post/api/v1/connectors

Register an outbound webhook. The signing secret is returned once — store it.

curl
curl https://app.crutan.com/api/v1/connectors \
  -X POST \
  -H "Authorization: Bearer $CRUTAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "outboundUrl": "https://hooks.yourcrm.com/crutan",
    "events": ["form.submitted", "meeting.booked", "lead.intent"]
  }'
200 OK — secret shown once
{
  "connector": {
    "id": "a91b…",
    "provider": "custom",
    "outbound_url": "https://hooks.yourcrm.com/crutan",
    "event_subscriptions": ["form.submitted", "meeting.booked", "lead.intent"],
    "status": "active",
    "created_at": "2026-06-04T18:00:00Z"
  },
  "secret": "whsec_…"
}

Events

EventFires when
page.renderedA personalized page finishes generating.
form.submittedA prospect submits a lead form.
meeting.bookedA meeting is booked from a page.
lead.intentA high-intent signal is captured.

Delivery & signature

Each delivery is an HTTP POST with a JSON body and an X-Crutan-Signature header of the form t=<unix_seconds>,v1=<hex>. The HMAC-SHA256 is computed over {t}.{raw_body} using your connector secret. Always verify against the raw request body before trusting the payload.

Incoming delivery
POST /crutan HTTP/1.1
Host: hooks.yourcrm.com
Content-Type: application/json
X-Crutan-Signature: t=1717524175,v1=9f8c2b…e41

{
  "event": "form.submitted",
  "workspace_id": "7c1d…",
  "prospect": { "email": "jordan@acme.com", "company_name": "Acme" },
  "page_token": "k3f…q9.ab12cd",
  "fields": { "message": "Let's talk" },
  "created_at": "2026-06-04T19:02:55Z"
}
Verify in Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody MUST be the exact bytes received (do not re-serialize parsed JSON).
export function verifyCrutanSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("="))
  );
  const expected = createHmac("sha256", secret)
    .update(parts.t + "." + rawBody)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}
Respond 2xx within a few seconds to acknowledge. Non-2xx responses are retried with exponential backoff; persistent failures are recorded and surfaced in the connectors dashboard.

MCP server

The Crutan MCP server exposes 40+ tools and 7 skill prompts so agents (Claude, Cursor, etc.) can run full workflows — campaigns, discovery pages, CRM hooks, and reporting. It authenticates with your workspace API key; plan limits, credit balances, and monthly key spend caps apply.

Transports

  • stdio — local agents via pnpm --filter @crutan/mcp start
  • HTTPhttps://app.crutan.com/api/mcp with Authorization: Bearer crt_… (per-user key, not a shared server secret)

Skill prompts

Invoke by name to receive a step-by-step playbook: launch-campaign, discovery-workflow, onboard-workspace, weekly-conversion-report, connect-crm, research-and-personalize, reactivate-cold-leads. Catalog resource: crutan://skills/catalog.

Cost guardrails

Call estimate_cost before spend tools; pass the exact acknowledgedEstimateCents or acknowledgedDiscoveryCredits returned. Async tools (create_template, build_discovery_page) return immediately — poll until complete.

Full MCP setup guide →

OpenAPI

The full machine-readable specification is published as OpenAPI 3.1. Import it into Postman, generate a client, or wire it into an iPaaS tool.

Download openapi.json →