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 on code, not on message text. Full catalog: /docs/errors.
  • 429 rate_limited: wait the number of seconds in the Retry-After header, then retry. Budgets: 600 requests/minute per API key (authenticated routes), 5 signups/hour per IP. Every response carries X-RateLimit-Limit / -Remaining / -Reset.
  • Idempotency: send an Idempotency-Key header (any unique string, ≤200 chars) on every creating POST. Retries replay the stored response verbatim — including one-time secrets — with Idempotent-Replayed: true. Never reuse a key across different endpoints (409 idempotency_conflict).
  • 402 payment_required / insufficient_credits: the project needs an active subscription, or the credit balance is short (the body carries required_credits / balance_credits and 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/subscriptions with {"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 with POST /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.