Menu

Authentication

Every request needs your API key in the Authorization header, as a bearer token:

Authorization: Bearer sk_live_your_api_key_here

Get a key by signing up: a free trial key (20 requests, no card required) is issued instantly from your dashboard. Missing or invalid keys return UNAUTHORIZED with a 401 status.

Quota & rate limits

Each plan has a monthly request quota shared across both endpoints. An audit costs 1 request; a fix generation costs 5 requests (or 1 if the site's content hasn't changed since your last call, see previousContentHash below). Exceeding your quota returns QUOTA_EXCEEDED with a 429 status and these headers:

HeaderMeaning
X-Quota-LimitYour plan's monthly quota
X-Quota-RemainingRequests left this period (never negative)
Retry-AfterPaid plans only: seconds until it's worth trying again
X-Quota-ResetPaid plans only: approximate ISO timestamp the quota resets

Trial keys never reset automatically. Upgrade from your dashboard for a monthly quota instead.

POST /v1/audit

Crawls a URL the same way an AI crawler would and scores it across four categories, with evidence for every check. Never calls an LLM: artifacts in the response is always empty; use /v1/generate to actually produce fix content.

Request

{
  "url": "https://example.com"
}

Response: 200

{
  "url": "https://example.com/",
  "auditedAt": "2026-08-06T12:00:00.000Z",
  "engineVersion": "1.0.0",
  "score": {
    "total": 58,
    "band": "partially-visible",
    "categories": { "crawlAccess": 18, "structuredData": 9, "answerReadiness": 17, "napConsistency": 14 }
  },
  "checks": [
    {
      "id": "schema-localbusiness-missing",
      "category": "structuredData",
      "status": "fail",
      "weight": 10,
      "evidence": "No JSON-LD of type LocalBusiness found on /",
      "fix": { "summary": "Add a LocalBusiness JSON-LD block to the homepage <head>", "priority": 1, "effort": "low", "generatedArtifact": "jsonld" }
    }
  ],
  "artifacts": { "jsonld": "", "llmsTxt": "", "faqSuggestions": [], "needsConfirmation": [] },
  "meta": { "pagesCrawled": 5, "usedBrowserRendering": false, "needsBrowserRendering": false, "contentHash": "abc123" }
}

score.band is one of ai-ready, partially-visible, or likely-invisible. checks[].status is pass, warn, or fail. fix is present on any check that isn't a clean pass.

Errors

CodeHTTP statusMeaningHeaders
INVALID_URL400Request body is missing "url", or it isn't a valid http/https URL.None
UNAUTHORIZED401Missing, malformed, revoked, or unknown API key.None
QUOTA_EXCEEDED429This key has used its available quota for the current billing period.X-Quota-Limit, X-Quota-Remaining, Retry-After*, X-Quota-Reset*
SITE_UNREACHABLE502The site couldn't be fetched or crawled (DNS failure, timeout, connection refused, etc.).None

* Retry-After and X-Quota-Reset are only sent for paid plans; trial keys have no reset date.

Examples

curl https://api.geogize.ai/v1/audit \
  -X POST \
  -H "Authorization: Bearer sk_live_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
const res = await fetch("https://api.geogize.ai/v1/audit", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com" }),
});
const audit = await res.json();

POST /v1/generate

Runs the same crawl and checks as /v1/audit, then calls the LLM to generate real fix content (JSON-LD, an llms.txt file, FAQ suggestions) using only facts extracted from the site. Anything it can't confirm is listed in needsConfirmation rather than invented.

Request

{
  "url": "https://example.com",
  "previousContentHash": "abc123"
}

previousContentHash is optional: pass the contentHash from a previous /v1/audit or /v1/generate call for this URL. If the site's content hasn't changed, generation is skipped (costing 1 request instead of 5) and you get back the same artifacts you'd expect from your last call, unchanged.

Response: 200 (generated)

{
  "skipped": false,
  "contentHash": "abc123",
  "artifacts": {
    "jsonld": "{ \"@context\": \"https://schema.org\", \"@type\": \"LocalBusiness\", ... }",
    "llmsTxt": "# Example Ltd\n\n> A plain-English summary of what this business does...",
    "faqSuggestions": ["What areas do you serve? ..."],
    "needsConfirmation": ["Confirm your business phone number, not found on the site."]
  }
}

Response: 200 (skipped, content unchanged)

{
  "skipped": true,
  "contentHash": "abc123"
}

Errors

CodeHTTP statusMeaningHeaders
INVALID_URL400Request body is missing "url", or it isn't a valid http/https URL.None
UNAUTHORIZED401Missing, malformed, revoked, or unknown API key.None
RATE_LIMITED429Too many generation requests from this address in a short window.Retry-After
QUOTA_EXCEEDED429This key has used its available quota for the current billing period.X-Quota-Limit, X-Quota-Remaining, Retry-After*, X-Quota-Reset*
SITE_UNREACHABLE502The site couldn't be fetched or crawled.None
GENERATION_FAILED502The LLM call failed or returned unusable output. Safe to retry.None

* Retry-After and X-Quota-Reset are only sent for paid plans on QUOTA_EXCEEDED; trial keys have no reset date.

Examples

curl https://api.geogize.ai/v1/generate \
  -X POST \
  -H "Authorization: Bearer sk_live_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
const res = await fetch("https://api.geogize.ai/v1/generate", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_live_your_api_key_here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ url: "https://example.com" }),
});
const result = await res.json();
if (!result.skipped) {
  console.log(result.artifacts.jsonld);
}

MCP server

Prefer to work from Claude Desktop, Claude Code, or any other MCP client instead of raw HTTP? The engine exposes the same audit and fix-generation capability as an MCP server over Streamable HTTP, authenticated with the exact same API key as the REST endpoints above.

POST https://api.geogize.ai/mcp

Send your key the same way as REST: as a Bearer token in the Authorization header. There's no separate MCP-specific auth or key type. A missing or invalid key returns MCP_AUTH_FAILED with a 401 status before any tool call is even attempted.

Client config

Most MCP clients take a JSON config listing their servers. This is the standard shape for a remote server on the MCP specification's Streamable HTTP transport:

{
  "mcpServers": {
    "geogize": {
      "type": "http",
      "url": "https://api.geogize.ai/mcp",
      "headers": {
        "Authorization": "Bearer sk_live_your_api_key_here"
      }
    }
  }
}

Exact key names vary between clients: some spell the transport "type": "streamableHttp" or "transport": "http", and some add remote servers through their own UI rather than a config file. Check your client's own documentation, and note we haven't yet live-verified this snippet against a specific client. The endpoint itself is plain Streamable HTTP with a bearer token, so any spec-compliant client can reach it.

Tools

ToolInputOutputCost
audit_website{ url }: a full URL including scheme.The same score, category breakdown, and pass/warn/fail checks (each with a fix) as POST /v1/audit.1 request
generate_fixes{ url, previousContentHash? }: pass previousContentHash from a prior call to skip regeneration when content hasn't changed.The same generated artifacts (JSON-LD, llms.txt, FAQ suggestions) as POST /v1/generate.5 requests (1 if skipped via previousContentHash match)

Both tools draw from the same monthly quota as the REST endpoints: a call from an MCP client and a call to /v1/audit for the same key count against the same pool. Tool-level errors (invalid URL, quota exceeded, site unreachable, generation failed) come back as a normal tool result with isError: true rather than an HTTP error, so your MCP client can see and act on them.

One difference from REST: a tool result carries no HTTP headers of its own, so a QUOTA_EXCEEDED tool error doesn't come with the X-Quota-Limit, X-Quota-Remaining, Retry-After or X-Quota-Reset headers documented above. The same numbers are included in the error message text instead.