api reference

One index. Reach it over MCP or plain HTTP.

Every route returns typed, cited structure a model consumes directly. The MCP server is the front door most agents use; the same behaviour is one POST away for any runtime.

base url

All routes are served from the API host. Responses are JSON. Requests with a body are application/json.

base url
https://api.ipnops.ai
the primary interface

MCP server

A Streamable-HTTP, stateless MCP server at POST /mcp. Add one connector and six tools appear inside your runtime: ipnops_search, ipnops_answer, ipnops_extract, ipnops_research, ipnops_research_status, and ipnops_monitor. Each ships an outputSchema, so tool results arrive as typed structure — not text to re-parse.

~/.mcp/config
# one connector — Ipnops becomes an agent tool
{ "ipnops": {
    "transport": "http",
    "url": "https://api.ipnops.ai/mcp"
  } }
tools/list ◂ result
{ "tools": [
  { "name": "ipnops_search", "outputSchema": {…} },
  { "name": "ipnops_answer", "outputSchema": {…} },
  …extract, research, research_status, monitor
] }

A tools/call to answer returns the same AnswerResult shape documented under /answer as structuredContent.

grounded synthesis

POST /answer

Consolidates highlights into one grounded answer with Ipnops' own tiered reasoner. The response is the agent-native shape: marker-per-claim citations, a claims array with per-claim verdicts and evidence quotes, one calibrated confidence scalar, and a sufficiency verdict that says whether the evidence actually covered the question.

querystring
required — the question (1–1000 chars)
topKnumber
optional — highlights to synthesize over (1–20)
forceEscalateboolean
optional — skip the floor rung, use the flagship reasoner
request · curl
curl -sS https://api.ipnops.ai/answer \
  -H "content-type: application/json" \
  -d '{ "query": "who maintains sqlite?" }'
200 ◂ AnswerResult
{ "answer": "… [1] … [2]",
  "citations": [{ "marker": 1, "url": "…", "text": "…" }],
  "claims": [{ "text": "…", "verdict": "supported",
    "quotes": ["…"], "grounded": true }],
  "confidence": 0.84, "sufficiency": "supported",
  "conflicts": [], "grounding": "own-index", "tier": "floor" }

grounding is one of own-index, web, places, or none — the reach ladder falls through in that order and never fabricates when it misses. sufficiency is supported, conflicting (sources disagree — see conflicts), or insufficient (treat the answer as an abstain).

async deep research

POST /research

Deep research as a durable run. Returns a run_id immediately (202); the engine then iterates — answer, find the claims it couldn't ground, crawl the gap, re-answer — checkpointing every step. The run survives restarts, and pages crawled along the way stay in the index.

querystring
required — the research question (1–1000 chars)
webhookUrlstring (url)
optional — POSTed the terminal run state when the run closes
maxIterationsnumber
optional — answer→crawl→re-answer cycles (1–3, default 3)
request · curl
curl -sS https://api.ipnops.ai/research \
  -H "content-type: application/json" \
  -d '{ "query": "…" }'

# 202 ◂
{ "run_id": "run_5c1f…", "status": "queued" }
observe the run
# poll
GET /research/:id

# stream — SSE, resumes from Last-Event-ID
GET /research/:id/events
# events: started → iteration → gap → crawl → completed

# cooperative cancel
POST /research/:id/cancel

The terminal result is the same AnswerResult shape as /answer, plus iterations and pagesIndexed.

standing queries

POST /monitor

Watch a question instead of asking it once. Each sweep re-answers the query and diffs the grounded claim set against the last snapshot — a deterministic normalized-text comparison, zero extra model calls. The webhook fires only when the facts change: rephrasing is silence, a new number is a signal.

querystring
required — the watched question (1–1000 chars)
webhookUrlstring (url)
optional — POSTed the claim diff + new answer on change
routes
POST /monitor      # register → 201 { monitor_id }
GET /monitor       # list
GET /monitor/:id   # one monitor + last snapshot
DELETE /monitor/:id
POST /monitor/run   # the cron target — sweep + diff
webhook ◂ on change
{ "monitor_id": "mon_9b2e…",
  "diff": { "addedClaims": ["…"], "removedClaims": ["…"],
           "confidenceDelta": -0.05 },
  "answer": "… [1]", "citations": […], "confidence": 0.81
}
growing the index

POST /index/seed

Grow the index. Pass exactly one of seed (crawl one domain) or query (fan a free-text query across the discovery ladder and crawl what it finds).

seedstring (url)
crawl one explicit domain — mutually exclusive with query
querystring
fan out a free-text query and crawl discovered URLs
maxPagesnumber
optional — page cap per crawl (1–200)
maxSeedsnumber
optional — discovered URLs to crawl for a query seed (1–50)
purgeboolean
optional — drop the index before crawling (clean reseed)
request · curl
curl -sS https://api.ipnops.ai/index/seed \
  -H "content-type: application/json" \
  -d '{ "query": "sqlite documentation", "maxSeeds": 5 }'
ai discovery

Discovery — GEO

Generative Engine Optimization (GEO) makes your real site retrievable by AI answer engines — the systems that now write a user's first impression of you and cite a handful of sources instead of listing a page of links. Ipnops observes which AI crawlers actually hit you, audits the on-page signals that decide retrieval, and scores you per engine across the Western and Chinese frontier. It enriches the page you already ship — no cloaking, no mirror page served only to bots.

The tag vs. the server SDK

The client tag is a one-line script that reports your live on-page signals (structured data present, server-render state, meta) from a real browser. The server SDK reads your request logs and tags real AI-crawler hits. Both are required, and the server SDK is not optional: AI crawlers do not run JavaScript, so the client tag never sees them. The only place a crawler's visit shows up is server-side, in the request log. That log truth — who came, to which paths, and what HTML they got — is the differentiator on-page audits alone can't match.

index.html · client tag
<!-- reports live on-page signals -->
<script async
  src="https://api.ipnops.ai/geo/tag.js"
  data-site="example.com"></script>
server · observe AI-crawler hits
// crawlers don't run JS — the server sees them
import { ipnops } from "@ipnops/geo";

app.use(ipnops({ site: "example.com" }));
// tags every AI-crawler request in your logs

Install on any framework or hosted builder

The tag drops into any HTML head. For React and Next, install it declaratively; on the server, add the SDK middleware where requests land. On a hosted builder, paste the tag into the head-code field it already gives you — no code required.

HTMLtag
add the <script async> tag to <head> — done
Reacttag
mount <IpnopsDiscovery site="…" /> once (idempotent, SSR-safe)
Next.jstag + sdk
component in the root layout; SDK in middleware.ts / route handlers
Vue / Sveltetag
add the tag in the app shell (app.html / index.html)
Expresssdk
app.use(ipnops({ site })) before your routes
Workerssdk
wrap fetch() — tag the request from the edge log
No-codetag
Shopify, Squarespace, WordPress, Wix, Webflow, Ghost: paste the tag into the builder's head-code / code-injection field

Audit signals

The on-page audit grades the four levers that decide whether an engine can retrieve and cite you — each scored per page, per engine.

structuredDataschema.org
valid JSON-LD (Organization / Product / FAQPage) — the single highest-leverage lever, ~2.5× the odds of appearing in an AI answer
serverRenderhtml
content present in server-sent HTML, so JS-crawling-averse engines see it without executing scripts
crawlerAccessrobots.txt
the right bots allowed for the search-vs-training split — blocked bots can't cite you
answerFirstcontent
lead with the answer, clear entities, freshness signals — how a model binds a claim to you

The nine engines

Ipnops scores five Western engines — ChatGPT, Perplexity, Google AI, Copilot, Claude — and four Chinese frontier engines most tools ignore: Qwen, GLM, Kimi, and DeepSeek. Each is matched by its published crawler user-agent where one exists.

The DeepSeek nuance — DeepSeek publishes no crawler user-agent and fetches browser-like, so it is effectively unobservable in logs; there is no hit to report. The lever that reaches it is retrieval readiness on your real page — serverRender plus structuredData. Ipnops covers DeepSeek through that readiness score, not through crawler observation, and labels it as such rather than faking a hit.

Structured data is the lever

In-page schema.org JSON-LD resolves your entities — org, product, FAQ, author — into a graph the model can bind to instead of guessing from prose. It is the highest-leverage GEO move and the one Ipnops grades first. It lives on the page you already serve.

index.html · schema.org JSON-LD
<script type="application/ld+json">
{ "@context": "https://schema.org",
  "@type": "Organization", "name": "Acme",
  "url": "https://example.com",
  "description": "Acme grades AI-answer retrieval."
}
</script>
operations

Health & admin

GET/healthz

Liveness probe. Returns { status: 'ok' }.

GET/readyz

Readiness — reports sidecar + reasoner liveness.

POST/admin/migrate

Apply schema.sql (idempotent).

POST/index/purge

Drop the whole index.