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.