Quickstart for agents
Go from nothing to a completed technical SEO audit in 5 API calls — no CAPTCHA, no payment method, no human in the loop. Signup returns a working sandbox key instantly. All bodies are JSON with snake_case fields; base URL is https://serp-agent.com.
Free sandbox budget per key: 1 project · 1 audit · 2 draft-only articles, key expires after 14 days. Full endpoint details: REST reference · /openapi.json.
1Sign up — get an instant sandbox key
curl -X POST https://serp-agent.com/v1/accounts \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-$(hostname)-1" \
-d '{"email": "agent-owner@example.com", "company_name": "Acme Agents", "origin": "agent"}'{
"account_id": "usr_01h9x2",
"api_key": "sk_sandbox_...", // shown EXACTLY once — store it now
"mode": "sandbox",
"sandbox_limits": { "audits": 1, "articles": 2, "expires_days": 14 },
"upgrade_url": "https://serp-agent.com/pricing",
"docs": "https://serp-agent.com/docs/agents"
}409 account_exists means the email is already registered — authenticate with that account's existing key instead. Signup is limited to 5 attempts/hour per IP.
2Export the key — every later call authenticates with it
export SERP_AGENT_API_KEY="sk_sandbox_..."
# Sanity check: your account + remaining trial budget
curl https://serp-agent.com/v1/account \
-H "Authorization: Bearer $SERP_AGENT_API_KEY"GET /v1/account returns sandbox_usage vs sandbox_limits so you can self-manage the budget. Need more keys (max 5 active)? POST /v1/api-keys; rotate with DELETE /v1/api-keys/{id} (create the replacement first — revoking your last active key is refused).
3Create a project for the site you're working on
curl -X POST https://serp-agent.com/v1/projects \
-H "Authorization: Bearer $SERP_AGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: proj-example-com-1" \
-d '{"url": "https://example.com", "language": "en", "country": "US"}'{
"project_id": "proj_01h9x2",
"url": "https://example.com",
"name": "example.com",
"status": "setup",
"language": "en",
"country": "US",
"sandbox": true,
"access": "sandbox",
"created_at": "2026-07-23T09:01:00.000Z"
}Sandbox keys can create 1 project (403 sandbox_limit_reached after that). On live keys, access starts as "pending_payment" until the project's subscription is paid — work endpoints return 402 until then.
4Run a technical audit
curl -X POST https://serp-agent.com/v1/projects/proj_01h9x2/audits \
-H "Authorization: Bearer $SERP_AGENT_API_KEY" \
-H "Idempotency-Key: audit-example-com-1"{
"audit_id": "aud_01h9x4",
"status": "queued",
"poll": "/v1/audits/aud_01h9x4"
}Audits are asynchronous: a site crawl runs first when needed, then the audit. If one is already queued/running for the project you get that same run back (no duplicate, no budget burn).
5Poll until the report is ready
# Poll every 30-60s until status is "completed" (or "failed")
curl https://serp-agent.com/v1/audits/aud_01h9x4 \
-H "Authorization: Bearer $SERP_AGENT_API_KEY"{
"audit_id": "aud_01h9x4",
"project_id": "proj_01h9x2",
"status": "completed",
"started_at": "2026-07-23T09:20:00.000Z",
"completed_at": "2026-07-23T09:30:00.000Z",
"score": 74,
"summary": {
"issues_critical": 2,
"issues_warning": 7,
"issues_info": 12,
"top_issues": [
{ "check": "SSL", "severity": "critical", "title": "Certificate chain incomplete", "affected_count": 1 }
],
"categories": { "performance": 68, "security": 45, "indexability": 90, "mobile": 82, "structure": 88 }
}
}The report is summarized for agent consumption: severity counts, top issues grouped per check (max 10, worst first), and 0–100 category scores — never a raw page dump. The latest completed summary is also embedded in GET /v1/projects/{project_id} as latest_audit.
Bonus: generate an SEO article
curl -X POST https://serp-agent.com/v1/projects/proj_01h9x2/articles \
-H "Authorization: Bearer $SERP_AGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: article-wp-migration-1" \
-d '{"topic": "How to migrate a WordPress site without losing rankings", "primary_keyword": "wordpress migration seo"}'
# → 202 { "article_id": "art_01h9x5", "status": "queued", "poll": "/v1/articles/art_01h9x5" }
# Poll every ~60s; generation typically takes 10-30 minutes.
curl https://serp-agent.com/v1/articles/art_01h9x5 \
-H "Authorization: Bearer $SERP_AGENT_API_KEY"Status walks generating → qa → ready. Once ready, the response carries title, meta_description, content_markdown, word_count, qa_score, and language. Articles are drafts — publishing is yours. One article generates per project at a time (409 article_in_progress otherwise); sandbox articles carry a watermark object.
Webhooks instead of polling
curl -X POST https://serp-agent.com/v1/webhooks \
-H "Authorization: Bearer $SERP_AGENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://agent.example.com/hooks/serp-agent", "events": ["audit.completed", "article.published"]}'
# → 201 includes "secret" (shown once). Verify every delivery:
# expected = hex(hmac_sha256(secret, raw_request_body))
# compare with the X-SerpAgent-Signature header
# Dedupe on X-SerpAgent-Delivery (stable across retries); respond 2xx within 10s.Omit events (or pass []) to subscribe to everything. Failed deliveries retry with exponential backoff (max 8 attempts); 50 consecutive failures deactivate the endpoint.
Error handling conventions
- Every non-2xx response has the same body:
{"error": {"code": "...", "message": "...", "doc_url": "..."}}. Branch oncode, not on message text. Full catalog: /docs/errors. - 429 rate_limited: wait the number of seconds in the
Retry-Afterheader, then retry. Budgets: 600 requests/minute per API key (authenticated routes), 5 signups/hour per IP. Every response carriesX-RateLimit-Limit / -Remaining / -Reset. - Idempotency: send an
Idempotency-Keyheader (any unique string, ≤200 chars) on every creating POST. Retries replay the stored response verbatim — including one-time secrets — withIdempotent-Replayed: true. Never reuse a key across different endpoints (409idempotency_conflict). - 402 payment_required / insufficient_credits: the project needs an active subscription, or the credit balance is short (the body carries
required_credits/balance_creditsand both purchase paths). See the billing section below. 403 sandbox_limit_reached: trial budget exhausted — upgrade to continue. - 404 not_found: the id does not exist on your account (foreign ids look identical to missing ones).
Billing: two ways to pay
Machine-readable pricing lives at GET /v1/plans (public, no auth). Creem is the merchant of record; there is no base platform fee. Live keys (sk_live_) can pay two ways — sandbox keys are never billable (403 sandbox_not_billable):
- Per-project subscription (all-inclusive):
POST /v1/subscriptionswith{"project_id": "...", "plan_id": "basic"|"pro"}— Basic $199/mo, Pro $299/mo, one subscription per project. Subscribed projects run audits and articles with no per-unit charges. Status:GET /v1/subscription?project_id=.... - Prepaid credits (à la carte): 1 credit = $1 list price. Without a subscription, work endpoints charge your balance per unit (technical audit 25, article 15 — full price list at
GET /v1/credits). Buy packs withPOST /v1/credits/purchases— larger packs carry bonus credits ($500 → 500, $2,000 → 2,200, $10,000 → 12,500).
The checkout handoff pattern
Payment itself always happens in a browser (Creem checkout) — the API never takes card details. Both purchase endpoints return status: "pending_payment" plus a checkout_url: surface that URL to your human, then poll until the webhook lands.
# 1. Create the checkout (subscription shown; credits work the same)
curl -X POST https://serp-agent.com/v1/subscriptions \
-H "Authorization: Bearer $SERP_AGENT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sub-proj_01h9x2-1" \
-d '{"project_id": "proj_01h9x2", "plan_id": "pro"}'
# → 201 { "status": "pending_payment", "checkout_url": "https://checkout.creem.io/...", ... }
# 2. Hand checkout_url to your human — they pay in the browser.
# 3. Poll until active (activation is automatic via webhook):
curl "https://serp-agent.com/v1/subscription?project_id=proj_01h9x2" \
-H "Authorization: Bearer $SERP_AGENT_API_KEY"
# → { "project_id": "proj_01h9x2", "plan": "pro", "status": "active", "paid_until": "..." }
# Credits: POST /v1/credits/purchases {"pack_id": "pack_2000"} → same handoff,
# then poll GET /v1/credits for balance_credits. Or subscribe to the
# "subscription.updated" / "credits.updated" webhook events instead of polling.Honest v1 limits: article_published (+5 credits) is not auto-charged — credit-funded articles are draft-only (fetch content_markdown and publish yourself); automated publishing to a connected CMS requires a project subscription. Monthly-priced units (AI visibility monitoring, local SEO autopilot) are not yet purchasable with credits. If a pack returns 503 billing_not_configured, that product is not set up yet on the server — use a subscription or retry later.