---
title: Moatlight API documentation
description: REST API, OAuth login, MCP server and discovery files for measuring AI search visibility.
canonical: https://moatlight.com/docs
last-updated: 2026-11-06
---

# Moatlight API documentation

> Measure whether ChatGPT and Google AI Overview name a business, and read a ranked plan of changes. REST, MCP and OAuth 2.0. Public endpoints and a sandbox need no login.

Canonical page: https://moatlight.com/docs

## Overview

The Moatlight API lets an AI agent or a script measure whether ChatGPT and Google AI Overview name a business, see which rivals they name instead, and read a ranked plan of changes. It is REST over JSON, with an MCP server for agents and an OpenAPI description.

Use it when a person asks about AI search visibility (GEO or AEO) for a website, wants the buyer questions AI answers with a rival, or wants to check how ready a site is for AI agents. Do not use it to write or publish content: Moatlight measures and advises only.

- Base URL: https://moatlight.com/api/v1
- OpenAPI: https://moatlight.com/openapi.json
- MCP server (Streamable HTTP): https://moatlight.com/mcp
- Docs MCP server: https://moatlight.com/mcp/docs
- Sandbox (no login, made-up data): https://moatlight.com/api/v1/sandbox
- Agent kit (skills, AGENTS.md, Agent Plugins manifest, MCP config): https://github.com/Moatlight/moatlight-agent-kit
- API version: 1.1.0 (policy: https://moatlight.com/docs#versioning)
- Contact: ceo@moatlight.com

## Quickstart

Try the public endpoints and the sandbox first. They need no login and cost nothing.

```bash
curl https://moatlight.com/api/v1/product
curl https://moatlight.com/api/v1/pricing
curl https://moatlight.com/api/v1/sandbox/reports/demo
```

Then start a sandbox report. It validates the body like the real endpoint and returns a fake report.

```bash
curl -X POST https://moatlight.com/api/v1/sandbox/reports \
  -H 'Content-Type: application/json' \
  -d '{"website":"yourcompany.com"}'
```

## Authentication

Account and report endpoints use OAuth 2.0 (authorization code with PKCE). The user signs in and approves the scopes you ask for. An app identifies itself with an HTTPS client_id URL that points to its metadata document (Client ID Metadata Documents, MCP 2026-07-28), or registers itself with Dynamic Client Registration (RFC 7591). No manual approval or API key is needed.

- Protected resource metadata (RFC 9728): https://moatlight.com/.well-known/oauth-protected-resource
- Authorization server metadata (RFC 8414): https://moatlight.com/.well-known/oauth-authorization-server
- Registration endpoint: https://moatlight.com/api/auth/oauth2/register
- Authorization endpoint: https://moatlight.com/api/auth/oauth2/authorize
- Token endpoint: https://moatlight.com/api/auth/oauth2/token
- Agent identity (no browser redirect: you name the person's email, they type a code): POST https://moatlight.com/agent/identity
- Step-by-step guide for agents: https://moatlight.com/auth.md

Send the token as Authorization: Bearer <access_token>. Access tokens last one hour; ask for the offline_access scope to get a refresh token. Without a valid token the API answers 401 with a WWW-Authenticate header that points to the metadata above.

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://moatlight.com/.well-known/oauth-protected-resource"
Content-Type: application/json

{"error":{"code":"unauthorized","message":"This endpoint needs a bearer access token.","hint":"..."}}
```

### Scopes

| Scope | What it allows |
| --- | --- |
| account:read | Read your plan, limits and whether you hold the Early Access pass. |
| reports:read | Read your reports: status, visibility numbers, competitors and the ranked plan. |
| reports:write | Start a report for a website and confirm its setup. Uses your free report allowance. |
| agent-readiness:read | Read the stored agent readiness scan of a report (Early Access pass). |
| agent-readiness:scan | Run an agent readiness scan for a report (Early Access pass). |

## Plans and access

The API follows the user's plan. GET /api/v1/me returns it. A payment needs a person in a browser; an agent cannot pay, so a gated call answers 402 with a link to the pricing page.

|  | Free | Early Access pass |
| --- | --- | --- |
| Price | US$0, no card | US$10 one-time, no subscription (Dodo Payments is the seller of record) |
| Websites | 1 | 1 |
| Reports | 1 per 30 days | 1 per 30 days |
| AI engines per report | 2: ChatGPT and Google AI Overview | 4: ChatGPT, Google AI Overview, Claude and Perplexity |
| Visibility reports and ranked plan | Yes | Yes |
| Agent readiness scan (API and tab) | No (402) | Yes, for 90 days |
| Refund | n/a | 14 days, full |

## Report lifecycle

A report is a short job. Each step is one call; the report's stage tells you what to do next.

| Stage | What to do |
| --- | --- |
| analyzing | Poll GET /api/v1/reports/{id} every few seconds until stage is needs_confirmation. |
| analysis_failed | Call POST /api/v1/reports again with the same website to retry. |
| needs_confirmation | Review profile and questions, then call POST /api/v1/reports/{id}/start. |
| running | Poll GET /api/v1/reports/{id} until stage is finished (about two minutes). |
| finished | Read the result with GET /api/v1/reports/{id} and GET /api/v1/reports/{id}/actions. |
| failed | See run_error. If run_failure is 'limit', the free allowance is used; otherwise start again. |

```bash
# 1. start (scope reports:write)
curl -X POST https://moatlight.com/api/v1/reports -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"website":"yourcompany.com"}'

# 2. poll until stage = needs_confirmation
curl https://moatlight.com/api/v1/reports/$ID -H "Authorization: Bearer $TOKEN"

# 3. confirm the proposed setup and run (about two minutes)
curl -X POST https://moatlight.com/api/v1/reports/$ID/start -H "Authorization: Bearer $TOKEN"

# 4. read the result and the plan
curl https://moatlight.com/api/v1/reports/$ID/actions -H "Authorization: Bearer $TOKEN"
```

Show the user the proposed profile and questions before step 3. Do not invent facts about the business.

## Sandbox

The sandbox is a test mode for the report endpoints. Base URL: https://moatlight.com/api/v1/sandbox. It needs no login and no key, costs nothing and reads no real website, so an integration can be built and tested before any user logs in. Every answer has "sandbox": true and uses the same shapes, errors, rate-limit headers and paging as production.

| Sandbox | Production twin |
| --- | --- |
| GET /api/v1/sandbox/me | /api/v1/me |
| GET /api/v1/sandbox/reports | /api/v1/reports |
| POST /api/v1/sandbox/reports | /api/v1/reports |
| GET /api/v1/sandbox/reports/{reportId} | /api/v1/reports/{reportId} |

- Any report id returns the same finished sample report (a fictional coffee roaster).
- POST /api/v1/sandbox/reports validates the body like production and answers 202 with a Location header, then GET the report to see it finished. Nothing is stored, so repeating a call is always safe.
- When it works in the sandbox, change the base URL to https://moatlight.com/api/v1 and add the bearer token.

```bash
curl -i -X POST https://moatlight.com/api/v1/sandbox/reports \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: demo-1' \
  -d '{"website":"yourcompany.com"}'
```

## Endpoints

| Method and path | Scope | What it does |
| --- | --- | --- |
| GET /api/v1/product | none | What Moatlight does and when to use it |
| GET /api/v1/pricing | none | Plans and prices |
| GET /api/v1/faq | none | Frequently asked questions |
| GET /api/v1/sample-report | none | A finished sample report on made-up data |
| GET /api/v1/me | account:read | The signed-in user, plan and limits |
| GET /api/v1/reports | reports:read | List the user's reports |
| POST /api/v1/reports | reports:write | Start a report for a website |
| POST /api/v1/reports/batch | reports:read | Read up to 25 reports in one request |
| GET /api/v1/reports/{reportId} | reports:read | Read one report: stage, setup to confirm, or results |
| POST /api/v1/reports/{reportId}/start | reports:write | Confirm the setup and run the report |
| GET /api/v1/reports/{reportId}/actions | reports:read | The ranked plan of changes and the questions you lost |
| GET /api/v1/reports/{reportId}/agent-readiness | agent-readiness:read | Stored agent readiness scan (Early Access pass) |
| POST /api/v1/reports/{reportId}/agent-readiness/scan | agent-readiness:scan | Run an agent readiness scan (Early Access pass) |

Request and response schemas, parameters and error codes for every endpoint are in the OpenAPI document at https://moatlight.com/openapi.json. The sandbox mirrors the report endpoints under https://moatlight.com/api/v1/sandbox.

### Example: read a finished report

```json
{
  "id": "…",
  "website": "yourcompany.com",
  "stage": "finished",
  "summary": {
    "answers": 50,
    "named": 18,
    "share_of_voice": {
      "k": 18,
      "n": 112,
      "brands": 11
    },
    "rank": {
      "place": 4,
      "of": 11
    },
    "per_engine": [
      {
        "engine": "ChatGPT",
        "answers": 25,
        "named": 7
      }
    ],
    "top_brands": [
      {
        "name": "Rival Co",
        "appeared": 34,
        "own": false
      }
    ],
    "lost_questions": 6
  }
}
```

For a finished report: up to three ranked moves (reason, exact edit, page, effort) and, for each buyer question AI answered with a rival, who won and why.

## Paging, batches and long jobs

### Cursor paging

List endpoints (GET /api/v1/reports, GET /api/v1/faq and their sandbox twins) take ?limit= (1 to 50, default 20) and ?cursor=. The answer has next_cursor and has_more. Send next_cursor back unchanged to get the next page; it is null on the last page. A cursor is opaque and may stop working after a day.

```json
{
  "reports": [
    {
      "id": "…",
      "website": "yourcompany.com",
      "stage": "finished"
    }
  ],
  "next_cursor": "eyJ0IjoiMjAyNi0xMS0wMVQxMDowMDowMC4wMDBaIiwiaWQiOiJhYmMifQ",
  "has_more": true
}
```

### Batch read

POST https://moatlight.com/api/v1/reports/batch takes {"report_ids":[...]} (1 to 25 ids) and returns one result per id, each with its own status, so one unknown id does not fail the others. It only reads. Use it instead of looping over GET /reports/{id}.

### Long jobs: 202 Accepted

Starting a report and running it take time. Those calls answer 202 Accepted with a Location header (and status_url in the body) that points at the job: GET it every poll_after_seconds (Retry-After on the 202) until the status is done. The report id is the job id (job_id) and the report stage is the job status; job.done_when says which stage ends the wait. If there is nothing left to wait for the call answers 200 instead.

```http
HTTP/1.1 202 Accepted
Location: https://moatlight.com/api/v1/reports/rpt_123
Retry-After: 5

{"id":"rpt_123","stage":"analyzing","job_id":"rpt_123","status_url":"https://moatlight.com/api/v1/reports/rpt_123","poll_after_seconds":5,"job":{"id":"rpt_123","status":"analyzing","status_url":"https://moatlight.com/api/v1/reports/rpt_123","poll_after_seconds":5,"done_when":"stage is needs_confirmation (then POST .../start) or finished"}}
```

### Fewer fields

Add ?fields=id,stage to a GET to receive only those top-level fields.

## Idempotency

Networks fail and agents retry. Send an Idempotency-Key header (1 to 255 printable characters, for example a UUID) on POST /reports, POST /reports/{id}/start and the scan endpoint, and a retry cannot start a second report or run a second scan.

- Same key and same body: you get the first answer back, with the header Idempotent-Replayed: true.
- Same key and a different body or path: 422 idempotency_key_reused.
- Same key while the first request is still running: 409 request_in_progress with Retry-After.
- Server errors (5xx) and 429 are not saved, so a retry runs again.
- Keys are kept for 24 hours and belong to your account.

```bash
curl -X POST https://moatlight.com/api/v1/reports -H "Authorization: Bearer $TOKEN" \
  -H 'Idempotency-Key: 7d1f6c52-0b6e-4c1e-9d0a-3f4f9a2b8c11' \
  -H 'Content-Type: application/json' -d '{"website":"yourcompany.com"}'
```

## MCP server

Agents can use Moatlight as tools over the Model Context Protocol (Streamable HTTP). https://moatlight.com/mcp exposes the same operations as the REST API as tools; the docs server at https://moatlight.com/mcp/docs lets an agent search these docs.

- Moatlight: https://moatlight.com/mcp (server card: https://moatlight.com/mcp/server-card)
- Moatlight docs: https://moatlight.com/mcp/docs (server card: https://moatlight.com/mcp/docs/server-card)

Listing tools needs no login. Calling a tool that reads or writes an account sends the client through the OAuth login above (the server answers 401 with a WWW-Authenticate header). Public tools work without it.

### Install

One URL is all a client needs. There is no API key: the client opens the OAuth login in a browser the first time a tool needs your account.

Claude Code. Run /mcp in Claude Code and choose moatlight to log in.

```bash
claude mcp add --transport http moatlight https://moatlight.com/mcp
```

Claude Code plugin (MCP, skills and the docs server together). Installs the MCP server and the skills.

```bash
claude plugin marketplace add Moatlight/moatlight-agent-kit
claude plugin install moatlight@moatlight
```

Codex. Then run codex mcp login moatlight if the client does not open the browser itself.

```bash
codex mcp add moatlight --url https://moatlight.com/mcp
```

Cursor. Open this link in Cursor and check that the server name and URL are right before you accept it.

[Add moatlight to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=moatlight&config=eyJ1cmwiOiJodHRwczovL21vYXRsaWdodC5jb20vbWNwIn0%3D)

Any other client (VS Code, Windsurf, Claude Desktop). Add this to the client's MCP settings file.

```json
{
  "mcpServers": {
    "moatlight": {
      "type": "http",
      "url": "https://moatlight.com/mcp"
    }
  }
}
```

### Read-only mode

Add ?readonly=true to the URL (https://moatlight.com/mcp?readonly=true; a value other than true or false is refused with 400) and the server lists only the tools that read data. Tools that start a report or run a scan are not offered on that connection. This only filters the tool list of the connection whose URL carries the flag: the login can still grant write scopes, and the same token works on the REST API or on a connection without the flag. To stop an agent writing, approve only the read scopes on the consent screen.

Tool names: get_product, get_pricing, list_faq, get_sample_report, get_account, list_reports, create_report, batch_get_reports, get_report, start_report, get_report_actions, get_agent_readiness, scan_agent_readiness.

## Ask endpoint (NLWeb) and agent card (A2A)

POST https://moatlight.com/ask answers questions about Moatlight from these docs, the FAQ and the pricing, using the NLWeb protocol (v0.55). Send {"query":{"text":"how much does it cost"}}. Add "prefer":{"streaming":true} for server-sent events (start, result, complete).

```bash
curl -X POST https://moatlight.com/ask -H 'Content-Type: application/json' \
  -d '{"query":{"text":"does the free plan need a card"}}'
```

The A2A agent card is at https://moatlight.com/.well-known/agent-card.json and its endpoint at https://moatlight.com/a2a. It answers questions about Moatlight; starting reports needs the OAuth-protected API.

## Errors

Every error is JSON with a code, a message and a hint. Branch on the code, not the message.

```json
{
  "error": {
    "code": "needs_early_access_pass",
    "message": "Agent readiness is part of the Early Access pass.",
    "hint": "A person must buy the pass in a browser: https://moatlight.com/pricing",
    "docs": "https://moatlight.com/docs#errors",
    "upgrade_url": "https://moatlight.com/pricing"
  }
}
```

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | invalid_request, invalid_website, invalid_locale | The body is malformed or the website cannot be used. |
| 401 | unauthorized, invalid_token | No valid bearer token. Follow the WWW-Authenticate header. |
| 402 | needs_early_access_pass | The call needs the pass. Send the user to upgrade_url. |
| 403 | insufficient_scope, domain_cap | The token lacks a scope, or the free plan covers one website. |
| 404 | not_found, no_scan | No such item for this account. |
| 409 | domain_locked, not_ready, not_finished, setup_closed | The call does not fit the report's stage or the 30-day limit (see unlock_at). |
| 404 | not_found (also for a path that does not exist) | Unknown endpoint or item. The body is JSON, never an HTML page. |
| 405 | method_not_allowed | The path exists but not for that method. The Allow header lists the methods. |
| 422 | site_unreachable, profile_invalid, idempotency_key_reused | The website could not be read, the setup needs the dashboard, or the Idempotency-Key was used for another request. |
| 429 | rate_limited | Too many requests. Wait for Retry-After seconds. |
| 503 | unavailable | The feature is off right now. |

## Limits and good manners

- Free plan: 1 website and 1 report every 30 days. A report uses real AI queries, so start one only when the user asks.
- Public endpoints: 120 requests per minute per IP. Signed-in endpoints: 120 per minute per user. Writes also have hourly caps (20 report starts, 10 scans). Poll jobs no faster than every 5 seconds.
- Every answer carries RateLimit and RateLimit-Policy (IETF draft-ietf-httpapi-ratelimit-headers, for example RateLimit: "default";r=119;t=60) and the older RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. A 429 adds Retry-After. Read them to slow down before you are refused.
- Agent readiness scans reuse results under 24 hours old. Use force only after a fix is deployed.
- Moatlight reads public pages only and respects robots.txt.
- Do not promise traffic, leads or dates from a report. It shows counts of answers.

## Versioning and deprecation

The current version is 1.1.0. The major version is in the path (/api/v1); every answer carries an API-Version header. Inside a major version changes only add things: new endpoints, optional fields, new enum values. Write clients that ignore fields they do not know.

### Deprecation policy

- A deprecated endpoint or field keeps working for at least 180 days after it is announced.
- While it is deprecated, every answer from it carries a Deprecation header (RFC 9745), a Sunset header with the removal date (RFC 8594) and a Link header with rel="deprecation" pointing at this page.
- Breaking changes ship only in a new major version (/api/v2) that runs beside v1 until v1 reaches its Sunset date.
- Announcements are listed in the changelog below and in the OpenAPI document (info.x-api-lifecycle).
- Today no endpoint is deprecated and no Sunset date is set.

### Changelog

| Version | Date | Changes |
| --- | --- | --- |
| 1.1.0 | 2026-11-06 | Added: cursor pagination (limit, cursor, next_cursor, has_more) on list endpoints; POST /reports/batch; Idempotency-Key on writes; RateLimit headers; 202 job pointers (Location, job_id, status_url); ?fields=; API-Version header. No removals. |
| 1.0.0 | 2026-11-05 | First release: public endpoints, sandbox, OAuth-protected report and agent readiness endpoints, MCP server. |

## CLI and SDKs

Official clients, generated from the OpenAPI document and kept in https://github.com/Moatlight/moatlight-agent-kit/tree/main/packages.

```bash
npm install @moatlight/sdk   # TypeScript / JavaScript
pip install moatlight        # Python
gem install moatlight        # Ruby
go get github.com/Moatlight/moatlight-agent-kit/packages/sdk-go   # Go
npx @moatlight/cli product   # command line
```

## Discovery files

| What | Where |
| --- | --- |
| llms.txt | https://moatlight.com/llms.txt |
| Agent view of the home page | https://moatlight.com/?mode=agent |
| OpenAPI | https://moatlight.com/openapi.json |
| API catalog (RFC 9727) | https://moatlight.com/.well-known/api-catalog |
| ARD catalog | https://moatlight.com/.well-known/ard.json |
| AI catalog | https://moatlight.com/.well-known/ai-catalog.json |
| Agent skills | https://moatlight.com/.well-known/agent-skills/index.json |
| A2A agent card | https://moatlight.com/.well-known/agent-card.json |
| MCP server cards | https://moatlight.com/mcp/server-card |
| OAuth metadata | https://moatlight.com/.well-known/oauth-protected-resource |
| Auth guide | https://moatlight.com/auth.md |
| Web Bot Auth keys | https://moatlight.com/.well-known/http-message-signatures-directory |
