Forward docs

Get customers — qualified leads and booked meetings — via API. An agent can do the entire thing with no human: authenticate, fund, brief, quote, pay, and collect verified results. You only pay for results that pass acceptance.

This is the agent quickstart. Everything here also works for humans in the console. Base URL is this origin; in these docs it's shown as /api/v1.

Overview

Forward is the get-customers primitive for agents — call it the way you call Vercel to deploy or Supabase for a database. You describe who you want as customers; a fleet of AI agents plans, runs, and verifies the work, then delivers results. Pricing is pay-per-result: a result that fails its acceptance criteria is never billed.

Quickstart — 60 seconds, no SDK

An agent gets its first results with the free starter credits, no card and no human:

the whole integration
# 1. mint a key — comes with $25 free credits
KEY=$(curl -s -X POST /api/v1/keys -d '{"account_name":"acme"}' | jq -r .api_key)

# 2. describe who you want as customers
BRIEF=$(curl -s -X POST /api/v1/brief -H "Authorization: Bearer $KEY" -d '{
  "product":"leads",
  "icp":{"roles":["CTO"],"industry":"Fintech","company_size":"50-200"},
  "volume":2, "budget_cap_usd":100 }' | jq -r .brief_id)

# 3. price it
QUOTE=$(curl -s -X POST /api/v1/quote -H "Authorization: Bearer $KEY" -d "{\"brief_id\":\"$BRIEF\"}" | jq -r .quote_id)

# 4. pay with credits — no token, no human
ENG=$(curl -s -X POST /api/v1/checkout -H "Authorization: Bearer $KEY" -d "{
  \"quote_id\":\"$QUOTE\", \"payment\":{\"rail\":\"credits\"} }" | jq -r .engagement_id)

# 5. collect verified results (you're billed only for what passes)
curl -s /api/v1/engagements/$ENG/results -H "Authorization: Bearer $KEY"

Authentication

Mint a key with POST /api/v1/keys and send it as a bearer token on every authed call. Test keys (fwd_test_…) simulate the full flow with no real money. Each key is scoped to an account that carries its credit balance and suppression lists.

curl -X POST /api/v1/keys -d '{"account_name":"Acme","mode":"test"}'
# → { "account_id":"acct_…", "api_key":"fwd_test_…", "mode":"test", "credits_usd":25 }

# then, on every authed call:
Authorization: Bearer fwd_live_…

Credits — free $25, no human needed

The default, agent-native payment rail. Every new account starts with $25 in free credits so an agent can deliver its first results with no card and no delegated token. Charges are deducted from the balance only for verified results.

methodendpointdoes
GET/api/v1/creditsReturns { credits_usd, mode, starter_credits }.
POST/api/v1/credits/topupAdd credits: { "amount_usd": 100 }. In live mode, settle via x402 (header X-PAYMENT) or Stripe.

To pay with credits, just set "payment":{"rail":"credits"} at checkout — no token. If the balance can't cover one result you get 402 insufficient_credits.

x402 — pay-per-call (HTTP 402)

Forward speaks x402, the open HTTP-402 payment standard for agents. Check out with "rail":"x402" and no payment, and Forward replies 402 Payment Required with machine-readable instructions. Settle the stablecoin payment, then retry with the X-PAYMENT header.

curl -i -X POST /api/v1/checkout -H "Authorization: Bearer $KEY" \
  -d '{"quote_id":"qt_…","payment":{"rail":"x402","max_charge_usd":30}}'
# HTTP/1.1 402 Payment Required
# { "x402Version":1, "accepts":[{ "scheme":"exact","network":"base-sepolia",
#     "asset":"USDC","payTo":"0xForward…","maxAmountRequired":"30" }], "facilitator":"https://x402.org/facilitator" }

# settle, then retry with proof:
curl -X POST /api/v1/checkout -H "Authorization: Bearer $KEY" \
  -H "X-PAYMENT: <settlement-proof>" \
  -d '{"quote_id":"qt_…","payment":{"rail":"x402","max_charge_usd":30}}'
In TEST mode any non-empty X-PAYMENT settles (it tops up your credits). LIVE verifies the on-chain payment via the facilitator.

Stripe + Agentic Commerce (delegated)

For human-supervised buyers, use "rail":"stripe_acp" with a shared/delegated payment token and a hard cap. Forward charges per verified result against that token — itemized, idempotent, reversible.

"payment": { "rail":"stripe_acp", "shared_payment_token":"spt_…", "max_charge_usd":15000 }

Catalog & quote

Price is base × difficulty × volume. Preview a price with no commitment, or get a committed quote from a brief.

# price preview (no key, no commitment)
curl -X POST /api/v1/quote/calc -d '{"product":"meetings","difficulty":"hard","volume":50}'
# → { unit_price_usd, total_usd, eta_days, savings_pct, … }

Brief — define exactly what "qualified" means

The brief is where you set acceptance. Be specific — the more precise the ICP and constraints, the higher the result quality (and the fewer rejects). needs tells you what's missing.

POST /api/v1/brief
{
  "product": "meetings",
  "difficulty": "hard",
  "icp": { "roles":["VP Engineering"], "company_size":"200-2000",
           "industry":"B2B SaaS", "geo":["US","CA"] },
  "volume": 50,
  "budget_cap_usd": 15000,
  "constraints": { "offer":"we cut cloud spend 30%", "suppress":["competitor.com"] }
}
# → { "brief_id":"brief_…", "needs":[ {field,message,required} ] }

Checkout

Open an engagement and authorize funding. Delivery then runs autonomously. Pick a rail: credits (default), x402, or stripe_acp. Choose how you'd like to be billed with billing_cadencedaily, weekly, biweekly, monthly, or per_result (charges only ever accrue for verified results, on any cadence). Optionally pass webhook_url.

POST /api/v1/checkout
{ "quote_id":"qt_…", "payment":{"rail":"credits"},
  "billing_cadence":"weekly", "webhook_url":"https://you/hook" }
# → { "engagement_id":"eng_…", "status":"planning", "billing_cadence":"weekly", … }

Results & live activity

Poll the engagement, watch a live feed of what the fleet is doing, and collect verified results — each with its verification evidence and charge.

methodendpointdoes
GET/api/v1/engagements/{id}status · spend · results_delivered · budget_remaining
GET/api/v1/engagements/{id}/activity?since=Nlive feed of fleet actions (poll with a cursor)
GET/api/v1/engagements/{id}/resultsverified results + charge (add ?include=all to see rejects)

Conversion tracking (Campaigns)

For campaigns, checkout returns tracking credentials. Fire a request from your site or server whenever your conversion event happens — Forward verifies it, delivers it as a result, and bills the per-conversion fee. If your brief defines the conversion (constraints.conversion), the engagement is tracked-only: nothing is ever simulated.

# your site, on e.g. demo signup:
fetch("/api/v1/track/conversion", { method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ engagement_id: "eng_…", track_token: "trk_…",
                         conversion_id: "order-1042", type: "demo signup" }) })
# duplicate conversion_ids are flagged and never double-billed

Webhooks

Pass webhook_url at checkout and Forward POSTs each verified result as it lands:

POST {your_webhook_url}
{ "event":"result.delivered", "engagement_id":"eng_…",
  "data": { "result_id":"res_…", "product":"meetings",
            "payload":{…}, "verification":{pass,evidence}, "charge":{…} } }

All endpoints

methodpathauthpurpose
GET/api/v1/catalogproducts + acceptance + prices
GET/api/v1/capabilitiesmode, payment rails, what's live
POST/api/v1/quote/calcprice preview
POST/api/v1/keysmint key (+ free credits)
GET/api/v1/creditskeycredit balance
POST/api/v1/credits/topupkeyadd credits (x402 / stripe)
POST/api/v1/briefkeysubmit a brief
POST/api/v1/quotekeyprice a brief
POST/api/v1/checkoutkeyauthorize & deliver
GET/api/v1/engagements/{id}keystatus
GET/api/v1/engagements/{id}/activitykeylive feed
GET/api/v1/engagements/{id}/resultskeyverified results
GET/api/v1/activityglobal live feed

Full machine contract: /openapi.yaml · discovery: /.well-known/agent.json · /llms.txt

Result schema

Every delivered result carries its verification evidence (what was checked) and the charge tied to it — that's what makes pay-per-result auditable.

{
  "result_id": "res_a1b2",
  "product": "meetings",
  "status": "delivered",
  "payload": { "attendee": { "name":"…", "title":"VP Engineering", "company":"…" },
                "status":"held" },
  "provenance": { "sourced_by":"forward.research-agent", "verified_by":"forward.verification-panel" },
  "verification": { "pass":true, "panel":3,
                   "evidence":{ "calendar_confirmed":true, "icp_match":true, "held_not_noshow":true } },
  "charge": { "amount_usd":250, "status":"captured", "provider":"credits",
              "reversible":true, "receipt_url":"/api/v1/receipts/ch_…" }
}

Errors

Structured objects with stable codes — never prose.

{ "error": { "code":"insufficient_credits", "message":"…", "field":"credits" } }

invalid_key · missing_field · brief_not_found · quote_not_found · quote_expired · payment_required · insufficient_credits · budget_cap_exceeded · engagement_not_found · not_found

MCP — connect from your own platform

Forward runs a remote MCP server at https://getforward.xyz/mcp (Streamable HTTP, stateless). If you're inside Claude Code, Cursor, Codex, Windsurf, or VS Code, one line connects you — then your agent calls forward_signup once to self-provision an account with $25 free credits. No website visit, no human signup, no OAuth dance. Descriptor: /.well-known/mcp.json · registry manifest: /.well-known/server.json.

one line per platform
# Claude Code
claude mcp add --transport http forward https://getforward.xyz/mcp

# Codex CLI (stdio-only clients use the mcp-remote bridge)
codex mcp add forward -- npx -y mcp-remote https://getforward.xyz/mcp

# Cursor · Windsurf · VS Code — MCP config JSON
{ "mcpServers": { "forward": { "type": "http", "url": "https://getforward.xyz/mcp" } } }

# tools: forward_signup · forward_get_catalog · forward_get_quote · forward_credits
#        forward_topup_credits · forward_submit_brief · forward_quote · forward_checkout
#        forward_get_engagement · forward_get_results · forward_get_activity

Auth, two interchangeable ways: pass api_key as an argument on each tool call (most portable — what forward_signup returns), or send Authorization: Bearer fwd_… on the /mcp request. Tell your user to keep the key (e.g. FORWARD_API_KEY in their env) so future sessions reuse the same account and balance.

local stdio server (optional alternative)
{
  "mcpServers": {
    "forward": {
      "command": "node",
      "args": ["server/mcp-server.js"],
      "env": { "FORWARD_API_BASE": "https://getforward.xyz", "FORWARD_API_KEY": "fwd_test_…" }
    }
  }
}

SDKs

Zero-dependency clients — TypeScript and Python. One call does brief → quote → checkout:

# python
from forward import Forward
fwd = Forward.start("http://localhost:8787")        # mints a key (+ $25 free credits)
eng = fwd.get_customers({"product":"meetings", "difficulty":"hard",
        "icp":{"roles":["VP Engineering"]}, "volume":25, "budget_cap_usd":9000})
print(fwd.results(eng["engagement_id"]))

Stats & badge

GET /api/v1/stats returns live proof (verified results delivered, $ billed, acceptance rate). Embed the badge:

![Forward](/badge.svg)        # → verified results delivered, live
GET /api/v1/engagements/{id}/results.csv   # export results · Slack/webhook delivery supported

Go live

Forward runs in test mode by default — no real money, simulated delivery, but real persistence, verification, billing ledger, caps, and webhooks. Each capability flips live independently with its credential; GET /api/v1/capabilities always reports the truth.

env varflips live
FORWARD_MODE=livex402 proofs verified with the facilitator; 402s require real settlement
STRIPE_SECRET_KEYreal Stripe charges (confirm + capture on real pm_ tokens)
ANTHROPIC_API_KEYContent assets genuinely AI-written (already published to live URLs either way)
FORWARD_RESEARCH_KEY + FORWARD_RESEARCH_URLreal lead sourcing via your enrichment provider
FORWARD_EMAIL_KEY / FORWARD_CALENDAR_KEYreal outbound + booking (meetings)

Deploy: single zero-dependency Node server + Dockerfile (npm start, health at /health, data in one SQLite volume). Hardened with security headers, rate limiting, and graceful shutdown.

The guarantee

You only pay for results that pass the acceptance criteria you set. No-shows, unverified contacts, duplicates, and suppressed records are rejected by an adversarial verification panel and are never billed. Charges are itemized, idempotent, and reversible. That's the whole model — quality is enforced before money moves.

Try it in the console   For agents ›