LogoGEOLY Docs
LogoGEOLY Docs
Homepage
What‘s the GEOly?PromptsShopping
Basic Management Function
GEOly Agent APIGEOly CLI: Use GEOly from Your Own AgentGeoly Integration with CloudflareGA4 Operation Manual —— AI Agent AnalyticsGEOly MCP User Guide
Explore: AI Market & Brand Research
How to create an llms.txt file for my websiteKnow What's the llms.txt
How to SetupWhat is Catalog Optimization
Development

GEOly Agent API

Send one question over HTTP and GEOly's hosted GEO agent researches your AI visibility data and returns a sourced answer, streamed or as JSON. Authenticated with your GEOly OAuth token; usage draws on your organization's own credits.

The GEOly Agent API is the HTTP endpoint behind geoly run. You send one question; GEOly's hosted GEO agent chooses the data tools, reads your monitoring data (and public industry data when your plan includes it), and returns an answer with a receipt: which tools it used, how many steps it took, and what it cost. You do not write prompts or learn GEOly's data model — you get the answer.

The GEOly CLI is the official client of this API: sign in with it, and geoly run sends the request, follows the stream, retries safely, and saves the receipt. This page documents the HTTP interface underneath — endpoints, request and response shapes, streaming, idempotency, and errors — so you know exactly what a run returns.

How it relates to the CLI and MCP

What runs the reasoningBest for
Agent API (POST /api/agent/runs)GEOly's hosted GEO agentGetting finished answers into your own systems over HTTP
GEOly CLI — geoly runThe same hosted agent, through this APITerminals, scripts, and coding agents
GEOly CLI — geoly call, and GEOly MCPYour own model or agent, calling GEOly data tools one by oneWhen you want the raw data and your own reasoning

All three use the same GEOly OAuth authorization, the same organizations and brands, and the same permissions.

Quick start

The GEOly CLI is the official client of this API: geoly run signs you in, sends the request below, follows the stream, retries safely, and saves the receipt.

# 1. Install the CLI and sign in once
curl -fsSL https://geoly.ai/install.sh | sh
geoly auth login

# 2. Ask a question — this is a POST /api/agent/runs under the hood
geoly run "How did our visibility on ChatGPT change over the last 7 days?"

You will see the agent's steps and tools on stderr while it works, and one JSON receipt on stdout when it finishes. The rest of this page documents the HTTP interface itself: what geoly run sends and receives.

Authentication

Sign in with the GEOly CLI:

geoly auth login                  # opens the browser
geoly auth login --remote         # no browser on this machine: prints a URL to open anywhere…
geoly auth login --code <code>    # …then paste back the code the page shows

The consent screen asks you to choose the organization and approve read (and, if you need it, write) permissions — the same screen as GEOly MCP. Sign-in produces an OAuth access token for your GEOly account, valid for about 14 days; geoly auth status shows when it expires, and signing in again renews it. See the GEOly CLI guide for the full sign-in flow, including SSH, containers, and CI runners.

On the wire, every request carries that token:

Authorization: Bearer <access_token>

Browser session cookies are not accepted. The token represents your GEOly user and the consent you gave: the API can reach exactly the organizations and permissions that consent covers — nothing more. A missing, invalid, or expired token returns 401.

Endpoints

Base URL: https://app.geoly.ai

Method and pathPurpose
POST /api/agent/runsStart a run: one question in, one answer out (streamed or JSON)
GET /api/agent/runsRecent runs for the organization (receipt fields, newest first)
GET /api/agent/runs/{run_id}One run, including the question and the full answer

Start a run

POST /api/agent/runs
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: text/event-stream
Idempotency-Key: weekly-report-2026-09-22
{
  "question": "Compare our AI visibility with our top three competitors over the last 30 days.",
  "org_id": "<org_id>",
  "brand_id": "<brand_id>",
  "context": "We sell standing desks in the US. Keep the answer under 300 words.",
  "max_credits": 300
}
FieldRequiredLimitsMeaning
questionYesUp to 4,000 charactersThe question or task
org_idWhen your access spans several organizations—Whose data to read and whose credits to use
brand_idWhen the organization has several brands—Which brand the run is about
contextNoUp to 16,000 charactersBackground, requirements, or output format for the agent
max_creditsNoInteger, 25–2000Hard ceiling for this run. Without it, the run can use up to what the organization has left, at most 2,000
specNoOne of the names belowAsk for a fixed-format deliverable checked by the server
allow_writesNotrue onlyLet the agent use write tools your consent granted. Anything other than the literal true means read-only

When org_id or brand_id is needed and missing — or wrong — the 400 error lists the valid options by name, for example This organization has multiple brands. Pass brand_id explicitly … or brand_id "…" was not found in this organization. Did you mean …? Available: ….

Streamed (Server-Sent Events) — recommended

Send Accept: text/event-stream. The response is a stream of frames in this format:

event: started
data: {"run_id":"run_…","brand":{"id":"…","name":"…"},"model":"…"}

event: tool
data: {"name":"get_brand_overview","ok":true,"ms":1830,"args":{"time_range":"30d"},"output_chars":5120}

event: done
data: {"run_id":"run_…","answer":"…","stopped":"done", …}
EventDataMeaning
startedrun_id, brand, modelThe run began. Keep the run_id.
stepindexThe agent started step N.
toolname, ok, ms, args, output_charsA tool finished: name, success, duration, argument preview, output size.
tools_loadedquery, namesThe agent searched for and loaded additional tools.
textdeltaThe next piece of the answer text.
step_errormessageA step hit an upstream problem; the agent continues with what it has.
heartbeat{}Sent every 10 seconds of silence. Not an error — set your client's idle timeout to 30 seconds or more.
doneThe receiptFinished.
errorcode, messageThe run failed. It is not charged.

In streamed mode, a failed run arrives as an error event; the HTTP status is still 200.

A run keeps going if your client disconnects, and its result is stored. Reconnecting is not possible, but you can always read the result with GET /api/agent/runs/{run_id}.

JSON (synchronous)

Omit Accept: text/event-stream and the response is a single JSON receipt once the run finishes. This suits short questions only: in this mode the agent stops starting new steps after about 30 seconds, and a response that takes longer than about 100 seconds can be cut off by the network edge with a gateway error. The run still completes and is stored; find it with GET /api/agent/runs and read it by id. A failed run returns 502 with {"error":"RUN_FAILED","message":"…"}.

The receipt

The done event (streamed) or the response body (JSON):

{
  "run_id": "run_…",
  "answer": "…Markdown…",
  "stopped": "done",
  "stopped_reason": "done",
  "steps": 3,
  "tools_used": ["get_brand_overview", "get_brand_board"],
  "usage": { "input_tokens": 46253, "output_tokens": 1489 },
  "credits_cost": 54,
  "credits_remaining": 9946
}
FieldHow to read it
answerThe answer, in Markdown. It follows the language of the question.
stoppeddone when the agent finished on its own terms, otherwise max_steps.
stopped_reasondone, or why it stopped early: budget (reached max_credits or the organization's remaining credits), deadline (the run's time limit), max_steps (step limit), salvaged (an upstream failure; the agent answered with the data it already had). Anything but done means the answer may be partial.
tools_usedThe tools the agent called — where the numbers came from.
credits_costWhat this run charged. Never more than max_credits.
credits_remainingCredits left for the organization afterward (a number, "unlimited", or null if it could not be read).
deliverableOnly with spec: { spec, version, valid, problems, … }. valid: false lists what is missing; the answer is returned either way.

Read runs back

GET /api/agent/runs?org_id=<org_id>&limit=20
GET /api/agent/runs/{run_id}

GET /api/agent/runs returns { "runs": [ … ] }, newest first. Each row has run_id, status, stopped, steps, credits_cost, created_at, finished_at, question (first 200 characters), and brand_id. limit is 1–50 (default 20); pass org_id when your access spans several organizations.

GET /api/agent/runs/{run_id} returns the stored run:

{
  "run_id": "run_…",
  "status": "succeeded",
  "question": "…",
  "answer": "…",
  "stopped": "done",
  "steps": 3,
  "tools_used": ["…"],
  "usage": { "input_tokens": 46253, "output_tokens": 1489 },
  "credits_cost": 54,
  "max_credits": 2000,
  "error": null,
  "created_at": "2026-09-22T08:00:00.000Z",
  "finished_at": "2026-09-22T08:01:12.000Z"
}
  • status is running, succeeded, or failed here (the CLI shows succeeded as done).
  • To wait for a run, poll this endpoint every few seconds until status is no longer running.
  • A run still marked running 15 minutes after it started is reported as failed with error: "RUN_ABANDONED".
  • A run your token cannot see returns 404 RUN_NOT_FOUND, whether or not it exists.

Idempotency: safe retries

Send an Idempotency-Key header to make retries safe:

  • Format: 8–128 characters of A-Z a-z 0-9 _ - : .. A header that is present but malformed (including empty) is rejected with 400 IDEMPOTENCY_KEY_INVALID.
  • Scope: the same organization, brand, and key.
  • While the original run is still running, a request with the same key always returns that run.
  • After it finishes, the same key replays it for 10 minutes from when it started; after that, the same key starts a new run.
  • A replay does not start or charge a new run. It is returned as JSON, whatever you sent in Accept, with "replayed": true in the body and the response header Idempotent-Replayed: true. The body has the same shape as GET /api/agent/runs/{run_id}; if its status is running, poll that endpoint.
  • If the key cannot be checked for a moment, the request is refused with 503 IDEMPOTENCY_UNAVAILABLE rather than risking a double run. Retry with the same key.

Choose a key that identifies the job, such as weekly-report-<brand>-<date>. The CLI derives its key from the organization, brand, spec, question, context, max_credits, and write permission.

Credits

Agent API usage draws on your organization's own credits.

  • Cap a run with max_credits (25–2000). credits_cost in the receipt never exceeds it.
  • See what is left in every receipt (credits_remaining), with geoly credits, or in GEOly under Settings → Billing.
  • A run that fails is not charged.
  • If the organization has fewer than 25 credits left, the run is refused with 402 INSUFFICIENT_CREDITS (the body includes remaining and, when known, period_end).

Fixed-format deliverables (spec)

With spec, the agent follows a specification kept on the server: fixed sections, every number from a tool, and a machine-readable JSON block at the end. The receipt's deliverable field reports whether the answer passed the checks. Streamed runs with a spec get a longer time limit.

specDeliverablePut in question
geo-weekly-brand-healthWeekly brand health: visibility, mention rate, citation rate, by platform, competitor gaps, next stepsBrand (via org_id / brand_id); optionally the period
geo-content-briefA content brief for one keyword: audience, SERP and AI citation research, outline, FAQ, AEO notesprimary_keyword, domain; optionally article_title, target_prompt, country
geo-keyword-research-reportKeyword research: volumes, intent clusters, live SERP, AI query fan-out, content opportunitiestopic; optionally domain, country
geo-serp-gapA page against Google's top 10 and AI Overview sources: content gapsurl, query; optionally country

Write the inputs into question, for example "url: https://example.com/blog/x, query: best standing desk for small apartments, country: us". An unknown name returns 400 SPEC_NOT_FOUND: … Available: ….

Errors

Errors are JSON: { "error": "<code or message>" }, sometimes with extra fields.

HTTPerrorMeaning and what to do
400INVALID_JSON, INVALID_INPUT, QUESTION_REQUIREDFix the request body.
400MAX_CREDITS_OUT_OF_RANGE (25..2000)max_credits must be an integer from 25 to 2000.
400IDEMPOTENCY_KEY_INVALIDFix the Idempotency-Key header.
400SPEC_NOT_FOUND: …Use one of the listed spec names.
400A sentence about org_id / brand_idAmbiguous or wrong target; the message lists the options.
401Missing Authorization header, Invalid token, …Missing, invalid, or expired token. Sign in again with geoly auth login.
402INSUFFICIENT_CREDITSCredits for this period are used up (body: remaining, period_end). Wait for the reset or raise the limit.
402Any other messageThe organization has no active subscription.
403ORG_SELECTION_REQUIRED, BRAND_NOT_ALLOWED_FOR_TOKEN, Authorization not granted for this client. …The consent does not cover this. Re-authorize and choose the organization.
404BRAND_NOT_FOUND, RUN_NOT_FOUNDNot found, or not visible to this token.
413QUESTION_TOO_LONG, CONTEXT_TOO_LONGOver 4,000 / 16,000 characters.
429Rate limit exceeded. …Too many requests; back off and retry.
502RUN_FAILEDJSON mode only: the run failed and was not charged. Retry.
503BILLING_UNAVAILABLE, IDEMPOTENCY_UNAVAILABLETemporary. Retry shortly (with the same Idempotency-Key).

Rate limits and concurrency

  • Requests share one per-account, per-minute rate limit with GEOly MCP; over the limit you get 429. Back off before retrying.
  • There is no separate cap on concurrent runs. Each run reserves up to its own budget when it starts, so several runs at once each draw on the organization's credits.

Example: Python

import json
import os

import requests

TOKEN = os.environ["GEOLY_ACCESS_TOKEN"]  # your GEOly OAuth access token

resp = requests.post(
    "https://app.geoly.ai/api/agent/runs",
    headers={
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
        "Accept": "text/event-stream",
        "Idempotency-Key": "weekly-review-2026-09-22",
    },
    json={"question": "Summarize our AI visibility this week.", "max_credits": 300},
    stream=True,
    timeout=(10, 60),  # connect timeout, idle read timeout (heartbeats arrive every 10 s)
)
resp.raise_for_status()

if resp.headers.get("Content-Type", "").startswith("application/json"):
    print(resp.json())  # an idempotent replay of an earlier run
else:
    event = None
    for line in resp.iter_lines(decode_unicode=True):
        if line.startswith("event:"):
            event = line[len("event:"):].strip()
        elif line.startswith("data:") and event in ("done", "error"):
            payload = json.loads(line[len("data:"):])
            print(event, payload.get("stopped_reason"), payload.get("answer") or payload.get("message"))

Example: JSON mode with curl

curl https://app.geoly.ai/api/agent/runs \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"question":"What is our mention rate on Perplexity this month?","max_credits":100}'

Integration tips

  1. Prefer streaming. Use JSON mode only for questions you know finish within seconds.
  2. Treat heartbeat as normal and set the idle timeout to 30 seconds or more.
  3. Keep the run_id. Disconnects and timeouts do not lose the answer; read it back by id.
  4. Check stopped_reason before using the answer. In automated jobs, rerun or raise max_credits when it is not done.
  5. Ask once, precisely. Put the period, platforms, and output format in question, and background in context.
  6. Always send an Idempotency-Key from jobs that may retry.

Limits and boundaries

  • Scope follows the consent. The API reaches only the organizations and permissions granted to the token.
  • Read-only by default. Write tools are used only with allow_writes: true and a Write grant for that resource on a single-organization consent. Running monitoring on demand (trigger_prompt) is never available to the hosted agent.
  • Plans. An active subscription is required. Public industry intelligence is used only when the organization's plan includes it (Grow and above).
  • Bounded runs. At most 2,000 credits and 20 steps per run, with a time limit (longer for spec runs). An early stop is reported in stopped_reason.
  • Fixed model. The API uses the model GEOly selects; you cannot bring your own.
  • Runs are stored and visible to your token's organization scope through GET /api/agent/runs.

Related

  • GEOly CLI — the easiest way to call this API from a terminal or a coding agent.
  • GEOly MCP User Guide — call GEOly data tools from your own AI client.

Table of Contents

How it relates to the CLI and MCP
Quick start
Authentication
Endpoints
Start a run
Streamed (Server-Sent Events) — recommended
JSON (synchronous)
The receipt
Read runs back
Idempotency: safe retries
Credits
Fixed-format deliverables (`spec`)
Errors
Rate limits and concurrency
Example: Python
Example: JSON mode with curl
Integration tips
Limits and boundaries
Related