One raw-data contract for local agents
AgentGEO routes managed AI scraper jobs and returns normalized records. Your code or local agent performs every analysis step. Every endpoint below lives at https://api.agentgeo.org.
Quickstart
Register the repository's local MCP server, then ask your agent to fetch a prompt from one or more AI surfaces. Everything the MCP server does maps 1:1 onto the REST endpoints documented below.
claude mcp add agentgeo -- node /absolute/path/to/agentgeo/mcp/index.mjs --api-url https://api.agentgeo.org --key ag_live_...Replace the example path with an absolute path to mcp/index.mjs. Runs execute in explicit demo mode (labelled "mode": "demo", zero credits) until provider credentials and dataset IDs are configured on the server.
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 | $0 | 200 | — (hard stop at included) | 2 |
| basic | $17 | 2,000 | $0.012 | 10 |
| growth | $79 | 10,000 | $0.009 | 100 |
| scale | $259 | 50,000 | $0.006 | Unlimited |
| custom | Custom | Committed use | Custom | Unlimited |
The free plan hard-stops when a live run would cross the included credits (402 with code plan_included_credits_exhausted). The wall counts your runs still in flight as well as your delivered records, so a refusal can arrive before the ledger alone explains it; the 402 body reports that headroom as creditsReserved and it is released as each run finishes. 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, 2,000 minimum, until you set one). Your resolved plan, this month's consumption and the current overage estimate are all in GET /v1/usage. The free tier's schedule limit is provisional and subject to change.
Plan changes are not self-serve yet: PATCH /v1/settings rejects plan_id with a 422. Compare tiers on the pricing page and email keronelau@gmail.com to switch.
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, free-plan included credits, or the plan's schedule limit. |
| 404 | The resource does not exist in your workspace (runs, schedules, saved queries, 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 2000 credits/month would be exceeded: 1998 used, 3 requested. Raise the cap in settings.",
"capMonthlyCredits": 2000,
"creditsUsed": 1998,
"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. |
| saved_query_id | string, optional | Stamps lastRunAt on that saved query. Unknown IDs are ignored silently. |
Response
The run envelope is camelCase. mode is "demo" when the provider is unconfigured or the key scope is test — 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 free-plan included credits would be crossed (live mode only, checked before any provider call). |
| 422 | Bad JSON, field out of bounds, or Unknown AI surfaces: ['foo']. |
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. Collection only — a schedule re-runs the same prompt; 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". |
| delivery | enum, optional | "webhook" | "store", default "store". |
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",
"delivery": "webhook"
}'| 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. |
Saved queries
Reusable prompt inputs, stored verbatim. Reference one from a fetch via saved_query_id to keep lastRunAt current.
/v1/queriesBearer required/v1/queriesBearer required · 201/v1/queries/{id}Bearer requiredFields
| Field | Type | Constraints |
|---|---|---|
| name | string, optional | 1–120 characters, default "Untitled query". |
| query | string, required | 1–4,096 characters. |
| tags | string[], optional | At most 10 strings, default []. |
| limit | query param (GET) | Default 100, silently clamped to 1–500. |
curl -X POST https://api.agentgeo.org/v1/queries \
-H "Authorization: Bearer ag_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Cold brew",
"query": "best cold brew subscription",
"tags": ["beverages"]
}'The list envelope is { "object": "saved_query_list", "queries": [...] }, newest first. DELETE returns { "object": "saved_query", "id": "...", "deleted": true }; unknown IDs return 404 "Saved query not found".
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 1000. null disables the per-key budget. Crossing it 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 and per-surface dataset IDs in the server environment (second tab). Secrets and dataset IDs are never returned by the API or included in logs — this endpoint only exposes booleans.
Settings
Workspace name, default country, spend cap, webhook URL and the signing secret. Settings are key/value rows — anything you have not set falls back to its default.
/v1/settingsBearer required/v1/settingsBearer required/v1/settings/rotate-webhook-secretBearer required/v1/webhook-deliveriesBearer required · 20 newestPATCH fields (snake_case in, camelCase out)
| Field | Type | Constraints |
|---|---|---|
| workspace_name | string, optional | 1–120 characters. |
| default_country | string, optional | 2–8 characters. |
| webhook_url | string, optional | At most 2,048 characters. Set to "" to disable deliveries. |
| 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 (2,000 minimum). A cap of 0 blocks all live spending. |
| plan_id / planId | rejected | 422 — plan changes are not self-serve yet; contact keronelau@gmail.com. |
Both GET and PATCH return the full settings envelope, including the resolved planId and the webhook signing secret (generated and persisted on first read). Rotating responds with { "object": "webhook_secret", "webhookSigningSecret": "whsec_..." } — deliveries signed after rotation use the new secret immediately.
{
"object": "workspace_settings",
"workspaceId": "user_2h9k...",
"workspaceName": "My workspace",
"defaultCountry": "US",
"webhookUrl": "https://example.com/hooks/agentgeo",
"webhookSigningSecret": "whsec_9f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c",
"spendCapEnabled": true,
"spendCapMonthlyCredits": 10000,
"authEnabled": true,
"planId": "growth",
"plan": { "name": "Growth", "includedCredits": 10000 }
}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.
Webhooks
Schedules with delivery set to webhook POST a signed operational event after each run — job.completed, job.partial or job.failed. Events describe delivery outcomes, never semantic claims about the answer.
job.completedAll requested records delivered
job.partialSome surfaces failed
job.failedNo records delivered or billed
Signature & delivery
Set your endpoint via webhook_url in PATCH /v1/settings. Every request carries X-AgentGEO-Event and X-AgentGEO-Signature: sha256=<hex> — an HMAC-SHA256 of the exact request body using your workspace signing secret. Delivery retries up to 3 attempts (0s, 1s, 4s backoff, 10s timeout each); any 2xx counts as delivered. Every attempt batch is logged to GET /v1/webhook-deliveries, and POST /v1/settings/rotate-webhook-secret invalidates the old secret.
POST <your webhook_url>
Content-Type: application/json
X-AgentGEO-Event: job.completed
X-AgentGEO-Signature: sha256=6d1f0a...
{
"event": "job.completed",
"occurredAt": "2026-07-17T09:30:41.000Z",
"schedule": {
"cadence": "daily",
"id": "sch_1f2e3d4c5b",
"name": "Daily cold brew check"
},
"run": { "...": "the full run envelope, answers included" }
}Product boundaries
The contract deliberately keeps data collection separate from interpretation.
AgentGEO returns
Your agent produces
Ready to inspect the contract?
The playground works in explicit demo mode before provider setup.
Open the playground