Quickstart for agents

This page is executable. Every step is a copy-paste curl with the expected response shown, so you can verify before proceeding. Set two variables your human gives you, then run top to bottom. Google-source connection requires a human in a browser — that flow is here; this page uses SEMrush, the one fully scriptable source.

export FUNNELKEEPER_API="https://funnelkeeper.fly.dev"
export FK_EMAIL="you@example.com"        # the human's email
export FK_PASSWORD="a-long-passphrase"   # 10+ characters

1. Create the account

curl -s -X POST "$FUNNELKEEPER_API/auth/signup" \
  -H 'content-type: application/json' \
  -d "{\"email\":\"$FK_EMAIL\",\"password\":\"$FK_PASSWORD\"}"
# expect: {"ok":true,"status":"verification_sent"}

A verification email goes to the human. Stop and ask them to click it. The response is identical whether or not the email already existed — no enumeration.

2. Log in and mint an API key

Sessions are for browsers; agents should hold an API key.

SESSION=$(curl -s -X POST "$FUNNELKEEPER_API/auth/login" \
  -H 'content-type: application/json' \
  -d "{\"email\":\"$FK_EMAIL\",\"password\":\"$FK_PASSWORD\"}" | jq -r .token)

curl -s -X POST "$FUNNELKEEPER_API/auth/keys" \
  -H "authorization: Bearer $SESSION" \
  -H 'content-type: application/json' \
  -d '{"name":"agent"}'
# expect: {"id":"…","name":"agent","key":"fk_live_…"}

The key is shown exactly once. Store it:

export FUNNELKEEPER_API_KEY="fk_live_…"

3. Create a product

curl -s -X POST "$FUNNELKEEPER_API/products" \
  -H "authorization: Bearer $FUNNELKEEPER_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"name":"Demo Product","slug":"demo-product","currency":"USD"}'
# expect: {"id":"…","slug":"demo-product","name":"Demo Product","currency":"USD","stage":"validation"}

A 409 means the slug is taken — pick another.

4. Connect SEMrush

The human’s SEMrush API key (SEMrush → Profile → API). Stored encrypted; never test-called, because every SEMrush call burns their units.

curl -s -X POST "$FUNNELKEEPER_API/products/demo-product/connections/semrush" \
  -H "authorization: Bearer $FUNNELKEEPER_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"api_key":"'"$SEMRUSH_API_KEY"'","domain":"example.com"}'
# expect: {"ok":true,"connection_id":"…"}

5. Verify and read

curl -s -X POST "$FUNNELKEEPER_API/products/demo-product/connections/semrush/test" \
  -H "authorization: Bearer $FUNNELKEEPER_API_KEY"
# expect: {"ok":true,"detail":"Key stored. SEMrush is never test-called — every call burns your units; the weekly snapshot is the live test."}

curl -s "$FUNNELKEEPER_API/portfolio" \
  -H "authorization: Bearer $FUNNELKEEPER_API_KEY"
# expect: [{"slug":"demo-product","spend_30d_cents":0,"revenue_30d_cents":0,"cac_cents":null, …}]

Zeros are honest zeros — no data has synced yet. cac_cents is null, not 0, because no customers exist to divide by. The Health endpoint (GET /health) tells you which sources have delivered and when.

What an agent must never do

Queue cards (GET /queue) are resolved by humans. If you hold an approval tool (MCP: approve_card), it exists to relay a decision your human just made — the actor field is their name, not yours. The server’s policy engine enforces spend caps and network walls regardless of what any client asks for.