Developer docs
One raw-data contract for local agents
Route managed scraper jobs and receive normalized records. Your code or agent performs every analysis step.
claude mcp add agentgeo -- npx -y agentgeo-mcp --api-url https://api.agentgeo.org --key ag_live_...REST base URL
https://api.agentgeo.orgAPI reference
Connect, authenticate, learn what a credit is, then inspect every endpoint, field and error the API can return.
Quickstart
Add the AgentGEO MCP server - it installs from npm on first run - then ask your agent for a GEO analysis or a raw fetch. The eight GEO analysis skills come built in, and everything the MCP server does maps 1:1 onto the REST endpoints documented below.
claude mcp add agentgeo -- npx -y agentgeo-mcp --api-url https://api.agentgeo.org --key ag_live_...ag_test_… keys always run in explicit demo mode (labelled "mode": "demo", zero credits) while ag_live_… keys fetch live data. On a self-hosted server, runs also stay in demo mode until provider credentials are configured (a dataset ID per chatbot surface, plus a SERP zone for Google AI Overview).Authentication
Every /v1 endpoint requires a Bearer token, except the public meta endpoints (GET /health, GET /, GET /v1/surfaces, GET /v1/engines). Two credential types resolve to a workspace:
Managed API key
Clerk session JWT
Create a key
The plaintext key is returned exactly once, on creation. List and read responses only ever expose the masked prefix. Keys with "scope": "test" always run in demo mode and charge zero credits.
curl -X POST https://api.agentgeo.org/v1/keys \
-H "Authorization: Bearer <clerk-session-or-existing-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "CI pipeline",
"scope": "live",
"budget_monthly_credits": 1000
}'401 responses
HTTP/1.1 401 Unauthorized
{ "detail": "Missing or malformed Authorization header" }
// or, when a token is present but resolves to nothing:
{ "detail": "Invalid API key or session" }Self-hosted deployments may also authenticate with a key from the server's API_KEYS environment variable; those callers share the single local workspace and are exempt from hosted plan enforcement.
Credits & plans
One successfully delivered provider record equals one credit. Failed records cost zero. Demo-mode runs cost zero. There is no charge per prompt, only per delivered record.
A single prompt requested from three surfaces costs at most three credits. A partial run bills only the records that arrived.
Plans
| Plan | Price / mo | Included credits | Overage per credit | Schedules |
|---|---|---|---|---|
| free (unsubscribed) | $0 | 0 | - (paywall: subscribe to run) | 0 |
| basic | $17 | 1,000 | $0.024 | 10 |
| growth | $79 | 5,000 | $0.019 | 100 |
| scale | $259 | 25,000 | $0.013 | Unlimited |
| custom | Custom | Committed use | Custom | Unlimited |
An unsubscribed workspace (the free state) has zero included credits and zero schedules: any live run or new schedule is refused before a provider call with a 402 (codes plan_included_credits_exhausted and plan_schedule_limit_reached). This is the paywall - subscribe to run; the free, human-run AI-visibility audit is the only no-cost way to see results and does not use this API. Paid plans never hard-stop at the included amount - overage records are billed at the plan's per-credit rate, and your account spend cap and per-key budgets remain the brakes (the cap defaults to your plan's included credits, 1,000 minimum, until you set one). Your resolved plan, this month's consumption and the current overage estimate are all in GET /v1/usage.
PATCH /v1/settings rejects plan_id with a 422 by design. Pick a plan on the Billing page in your console (compare tiers on the pricing page); overage beyond included credits is metered and billed through Stripe. Custom plans: email keronelau@gmail.com.Errors
Every error is a JSON body with a single detail field: a string for 401 / 404 / 422, and a structured object for 402. All four 402 shapes carry a machine-readable code.
| Status | Meaning |
|---|---|
| 401 | Missing, malformed or invalid Bearer token. |
| 402 | A live run or new schedule was blocked before any provider call: account spend cap, per-key budget, the unsubscribed-workspace paywall (no included credits or schedules), or a paid plan's schedule limit. |
| 404 | The resource does not exist in your workspace (runs, schedules, keys). |
| 422 | Validation failed: bad JSON, out-of-bounds field, unknown enum value, or an unknown surface key. |
HTTP/1.1 402 Payment Required
{
"detail": {
"code": "account_spend_cap_exceeded",
"message": "Account spend cap of 1000 credits/month would be exceeded: 998 used, 3 requested. Raise the cap in settings.",
"capMonthlyCredits": 1000,
"creditsUsed": 998,
"creditsRequested": 3
}
}Fetches
The core endpoint. One prompt, one or more surfaces, synchronous delivery of normalized raw records. It does not accept brands, competitors or analysis settings.
/v1/fetchesBearer requiredcurl -X POST https://api.agentgeo.org/v1/fetches \
-H "Authorization: Bearer ag_live_..." \
-H "Content-Type: application/json" \
-d '{
"query": "best cold brew subscription",
"surfaces": ["chatgpt", "perplexity", "gemini"],
"country": "US",
"language": "en",
"web_search": true
}'Request fields
| Field | Type | Constraints |
|---|---|---|
| query | string, required | 1-4,096 characters. |
| surfaces | string[], required | 1-6 keys from the catalog (GET /v1/surfaces). Unknown keys are rejected with 422. |
| country | string, optional | 2-8 characters, default "US". Provider targeting hint. |
| language | string, optional | 2-12 characters, default "en". Output-language hint. |
| web_search | boolean, optional | Passed through to surfaces that support it. Default null. |
| snapshot_id | string, optional | Redeems a finished async job from a failed record's providerFields.snapshot_id. Requires exactly one surface (422 otherwise) and is not supported for google_ai_overview (422). |
Response
The run envelope is camelCase. mode is "demo" when the key scope is test (or, on a self-hosted server only, when the provider is unconfigured) - demo runs are clearly labelled and charge zero. status is completed, partial (some surfaces failed) or failed (nothing delivered, nothing billed).
{
"id": "run_84c1f2ab91d3",
"query": "best cold brew subscription",
"surfaces": ["chatgpt", "perplexity", "gemini"],
"country": "US",
"language": "en",
"provider": "managed_scraper",
"mode": "live",
"delivery": "sync",
"status": "completed",
"createdAt": "2026-07-17T09:30:00.000Z",
"completedAt": "2026-07-17T09:30:41.000Z",
"recordsDelivered": 3,
"creditsCharged": 3,
"answers": [{
"surfaceKey": "chatgpt",
"status": "delivered",
"answerText": "...",
"sources": [{
"title": "...",
"url": "https://...",
"position": 1
}],
"fetchedAt": "2026-07-17T09:30:39.000Z",
"latencyMs": 39400,
"providerRecordId": "s_abc123...",
"providerFields": {
"web_search_triggered": true
}
}]
}| Status | When |
|---|---|
| 401 | No valid credential. |
| 402 | Spend cap, key budget, or the unsubscribed-workspace paywall (no included credits) blocks the run (live mode only, checked before any provider call). |
| 422 | Bad JSON, field out of bounds, or Unknown AI surfaces: ['foo']. |
Built-in GEO skills
The eight GEO analysis workflows - prompt-set, visibility, share-of-voice, citations, sentiment, competitors, monitor and report - ship built into the MCP server and are served by this API. Your agent lists them, loads the one that matches the ask, and runs every analysis step locally on fetch output. Skill reads are free: they contact no AI provider and spend no credits.
/v1/skillsBearer required · catalog, free/v1/skills/{name}Bearer required · one full SKILL.md, free{
"object": "skill_list",
"count": 8,
"pipeline": "geo-prompt-set builds the prompt library every other skill consumes; geo-visibility, geo-share-of-voice, geo-citations and geo-sentiment each analyze one dimension; geo-competitors joins them into one comparison; geo-monitor tracks runs over time; geo-report synthesizes everything into an executive report.",
"skills": [{
"name": "geo-visibility",
"description": "Measure whether and how prominently a brand appears in AI answers across engines...",
"version": "0.1.0"
}]
}Over MCP the same delivery is the list_geo_skills and get_geo_skill tools, and the eight skills are also exposed as MCP prompts (Claude Code renders them as /mcp__agentgeo__geo-report-style slash commands). The server prefers the live copy from this API and falls back to the copy bundled in the npm package, so workflows stay fresh without reinstalling and keep working offline. The skills themselves are open source: github.com/gumlau/agentgeo-skills.
Runs
Immutable fetch history. Every run - API, playground or scheduled - lands here with the same envelope.
/v1/runsBearer required/v1/runs/{id}Bearer requiredQuery parameters
| Param | Type | Constraints |
|---|---|---|
| limit | integer, optional | Default 50. Silently clamped to 1-200 (out-of-range values never error). |
The list returns summaries - the run envelope with answers stripped, plus origin (api or schedule) and scheduleId. Fetch a single run by ID to get the full record set back. Unknown IDs return 404 "Run not found".
curl "https://api.agentgeo.org/v1/runs?limit=50" \
-H "Authorization: Bearer ag_live_..."Schedules
Repeated raw fetches on a fixed cadence. A schedule re-runs the same prompt and its results land in run history; it never watches for semantic changes.
/v1/schedulesBearer required/v1/schedulesBearer required · 201/v1/schedules/{id}Bearer required/v1/schedules/{id}Bearer requiredCreate fields
| Field | Type | Constraints |
|---|---|---|
| name | string, required | 1-120 characters. |
| query | string, required | 1-4,096 characters. |
| surfaces | string[], required | 1-6 catalog keys; unknown keys → 422. |
| country | string, optional | 2-8 characters, default "US". |
| language | string, optional | 2-12 characters, default "en". |
| web_search | boolean, optional | Default null. |
| cadence | enum, optional | "hourly" | "daily" | "weekly", default "daily". |
New schedules start active with nextRunAt one cadence interval from creation. PATCH accepts any subset of the create fields plus status ("active" | "paused"); only supplied fields change, and changing cadence recomputes nextRunAt from now. Each envelope carries a runsThisMonth count. DELETE responds with { "object": "schedule", "id": "...", "deleted": true }.
curl -X POST https://api.agentgeo.org/v1/schedules \
-H "Authorization: Bearer ag_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Daily cold brew check",
"query": "best cold brew subscription",
"surfaces": ["chatgpt", "perplexity"],
"cadence": "daily"
}'| Status | When |
|---|---|
| 402 | POST only: your plan's schedule limit is reached (code plan_schedule_limit_reached). |
| 404 | PATCH / DELETE with an ID outside your workspace: "Schedule not found". |
| 422 | Field out of bounds, unknown enum value, or unknown surface key. |
API keys
Managed keys for programmatic access. Creation is shown in the Authentication section - the plaintext secret appears once and is never retrievable again.
/v1/keysBearer required/v1/keysBearer required · 201 · see Authentication/v1/keys/{id}Bearer required · revokes, does not deleteCreate fields
| Field | Type | Constraints |
|---|---|---|
| name | string, optional | 1-120 characters, default "Untitled key". An empty body is valid - every field has a default. |
| scope | enum, optional | "live" | "test", default "live". Test keys always run demo mode, charge zero, and have their budget forced to null. |
| budget_monthly_credits | integer | null, optional | 1-1,000,000, default null (no per-key budget - the account spend cap still applies). Crossing a set budget blocks live runs with a 402 (code key_budget_exceeded). |
Listing returns active keys only - masked prefix, never the secret or its hash. Revoking is a soft delete: the key stops authenticating immediately and disappears from the list. Unknown IDs return 404 "Key not found".
{
"object": "api_key_list",
"keys": [{
"id": "key_1f2e3d4c5b",
"name": "CI pipeline",
"prefix": "ag_live_9f2…8c1d",
"scope": "live",
"budgetMonthlyCredits": 1000,
"createdAt": "2026-07-17T09:00:00.000Z",
"lastUsed": "2026-07-17T09:30:00.000Z"
}]
}Usage
The operational credit ledger: records, not conclusions. Your resolved plan, this month's live-credit consumption, a dense day-by-day series and the newest ledger entries.
/v1/usageBearer requiredQuery parameters
| Param | Type | Constraints |
|---|---|---|
| days | integer, optional | Default 30. Must be an integer between 7 and 90 - anything else is a hard 422 (unlike the runs limit, this is not clamped). |
The plan block reflects your resolved plan: overageCredits is max(0, creditsUsed − includedCredits), overageEstimatedUsd multiplies that by the plan's overage rate (0 when the plan has none), and includedRemaining counts down to zero. On committed-use plans includedCredits, includedRemaining and overagePerCredit are null. daily is zero-filled and ascending; ledger holds the 50 newest events. Only mode: "live" events count toward creditsUsed.
curl "https://api.agentgeo.org/v1/usage?days=30" \
-H "Authorization: Bearer ag_live_..."Providers
Upstream connection readiness per surface, computed from the deployment's provider configuration. When nothing is configured, runs fall back to clearly-labelled demo records.
/v1/providersBearer required · deployment-wide status{
"object": "provider_status",
"provider": "managed_scraper",
"configured": true,
"demoFallback": false,
"surfacesConfigured": 6,
"surfaces": [
{ "key": "chatgpt", "access": "managed_ai_scraper", "datasetConfigured": true },
{ "key": "perplexity", "access": "managed_ai_scraper", "datasetConfigured": true }
]
}Self-hosters configure the provider token, per-surface dataset IDs and the Google AI Overview SERP zone in the server environment (second tab). Secrets, dataset IDs and zone names are never returned by the API or included in logs - this endpoint only exposes booleans.
Settings
Workspace name, default country and spend cap. Settings are key/value rows - anything you have not set falls back to its default.
/v1/settingsBearer required/v1/settingsBearer requiredPATCH fields (snake_case in, camelCase out)
| Field | Type | Constraints |
|---|---|---|
| workspace_name | string, optional | 1-120 characters. |
| default_country | string, optional | 2-8 characters. |
| spend_cap_enabled | boolean, optional | Default true. |
| spend_cap_monthly_credits | integer, optional | 0-10,000,000. Until set, defaults to your plan's included credits (1,000 minimum). A cap of 0 blocks all live spending. |
| plan_id / planId | rejected | 422 - plan changes go through Stripe checkout (the Billing page), never this endpoint. |
Both GET and PATCH return the full settings envelope, including the resolved planId.
{
"object": "workspace_settings",
"workspaceId": "user_2h9k...",
"workspaceName": "My workspace",
"defaultCountry": "US",
"spendCapEnabled": true,
"spendCapMonthlyCredits": 5000,
"authEnabled": true,
"planId": "growth",
"plan": { "name": "Growth", "includedCredits": 5000 }
}Meta & discovery
Public endpoints - no Authorization header required. Everything else in this reference sits behind the Bearer gate.
/healthPublic · liveness, version, env/Public · service root: auth_enabled, provider_configured, endpoint list/v1/surfacesPublic · the surface catalog/v1/enginesPublic · legacy alias, identical body{
"object": "surface_list",
"provider": "managed_scraper",
"count": 6,
"engines": [{
"key": "chatgpt",
"provider": "managed_scraper",
"access": "managed_ai_scraper",
"configured": true,
"credits_per_delivered_record": 1
}]
}Six surfaces are supported: chatgpt, perplexity, gemini, google_ai_overview, google_ai_mode and copilot. Each catalog entry reports whether its upstream dataset is configured and confirms the flat rate: one credit per delivered record.
Product boundaries
The contract deliberately keeps data collection separate from interpretation.
AgentGEO returns
Your agent produces
Want to see it on your own brand first?
The free audit runs these same surfaces by hand and sends back the raw answers with their citations - the shape every endpoint above returns.