# ARC full docs for agents > Check if an AI agent can reach checkout on a store before trying. Start here. No API key. - GET https://www.arcreport.ai/api/precheck?url= - MCP https://www.arcreport.ai/api/ask - This file: https://www.arcreport.ai/llms-full.txt - OpenAPI 3.1: https://www.arcreport.ai/api/openapi.json https://www.arcreport.ai/api/mcp is a different server. It does not serve precheck. Some tools there need a key. Do not start there. ## Advisory Results are advisory. ARC stops before payment. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Say reach checkout. ARC does not place an order. ## Precheck Docs: https://www.arcreport.ai/docs/precheck Examples: https://www.arcreport.ai/docs/precheck/examples Integrations: https://www.arcreport.ai/docs/precheck/integrations Ask whether ARC already knows if an agent can reach checkout at a store. The answer is built from saved checks. ARC does not fetch that URL, and it does not start a scan. A detected rail does not prove an agent completed checkout. reachable_checkout is true only when a saved check returned a checkout link. The saved result is ARC's automated HTTP check. It is not a live agent session. ### Request GET /api/precheck?url= with a product or store URL. HTTP and HTTPS only. No API key. Example: https://www.arcreport.ai/api/precheck?url=https://absorbine.com The same read is the precheck tool on https://www.arcreport.ai/api/ask. https://www.arcreport.ai/api/mcp does not serve this tool. curl -sD - "https://www.arcreport.ai/api/precheck?url=https://absorbine.com" ### What the check proves - host: the hostname, lowercased, without a leading www or a trailing dot. A subdomain can resolve to the registrable host ARC already lists (for example shop.example.co.uk to example.co.uk). - known: true when the public catalog, a saved rails check, directory check history, or a plain-HTTP checker row has this host. False for a host ARC has not published, including EachSpy, OpenStreetMap, campaign shops, and a store that opted out. - reachable_checkout: true when a saved check returned a checkout link, false when that check finished without one, null when ARC has no completed check. True is a saved checkout link, not an agent that finished checkout. - rails: UCP, ACP, Shop Pay, WebMCP, and MCP, each with a status and a proves sentence. Checkout WebMCP is included when a saved Shopify reading exists. Null when known is false. - rails[].proves: what that rail's saved status records. Every proves sentence includes "Does not prove an agent completed checkout." It does not mean an order was placed. - blockers: standard sentences for a saved block. Empty when nothing blocked the check. Null when known is false. - last_checked: ISO time of the saved check. Null when there is none. - checked_ago: coarse age of last_checked, such as "3 hours ago" or "2 days ago". "never checked" when the check time is missing or unparseable. Under an hour stays "under 1 hour ago". - stale: true when last_checked is 24 hours old or older. Missing check time counts as stale. False only when the saved check is newer than 24 hours. Always present. - confidence: high, medium, low, or none. A completed check from the last 7 days is high. A completed check from the last 30 days is medium. Older or failed evidence is low. No evidence is none. - lastReachableCheck, when present: checkedAt, proves, and evidenceLevel http-only. proves ends with "ARC did not reach checkout in this check". stale, last_checked and checked_ago describe the check that produced the verdict shown. lastReachableCheck is a separate, newer plain-HTTP time. It does not re-verify the checkout link or rails. - reachability_stale: true when lastReachableCheck is 24 hours old or older, or that time is missing or unparseable. False when that plain-HTTP time is newer than 24 hours. Absent when lastReachableCheck is absent. It does not change stale. - report_outcome: https://www.arcreport.ai/docs/outcome-report - source: ARC (arcreport.ai). - licence: CC BY 4.0. OSM-derived rows are ODbL. - scan_url: the free scan page when the host is unknown. Null when known is true. Opening this API does not run that scan. - proves: the plain-HTTP checker's sentence, only when the catalog, rails, and history have no row. Returned as stored. It is not a checkout result. - checkedAt: ISO time of that checker row. The same moment as last_checked. Absent when a directory, rails, or history result is used. - sources: source tags stored with that checker row. Absent on a directory, rails, or history result. - evidenceLevel: http-only on a checker fallback. Checkout was not part of the check. - checkoutTested: true only when ARC's check reached a checkout link. False on a checker fallback. ARC stops before payment. Does not prove an agent completed checkout. - note: a short line for that checker row, such as "HTTP reachable; robots allows; platform detected; no checkout link in this check". ### What it does not prove It reads saved ARC checks and never fetches the store live. It does not start a scan. A detected rail does not prove an agent completed checkout. reachable_checkout true is a saved checkout link, not an agent that finished checkout. Results are advisory. ARC stops before payment. ### Caching, counts, rate limit, licence Successful responses send Cache-Control: public, s-maxage=300, stale-while-revalidate=86400. The CDN can reuse a response for five minutes, and serve a stale copy for a day while it refreshes. The server also keeps a 256-entry memory cache for 60 seconds. A successful answer increments a daily total for that host. Unknown, excluded and opted-out lookups share one (unknown) bucket. The raw address and the user agent are not stored. Calls from ARC's own internal clients are left out of that total. GET /api/precheck/stats returns checks_served. 60 requests a minute for each network. The key is an HMAC of the client address. The raw address is not stored. The 61st request in that minute is HTTP 429 with a Retry-After header. OPTIONS is not counted. The limit is approximate and counted per server instance. The JSON is free to use, share and adapt under CC BY 4.0 with attribution to ARC (arcreport.ai). Questions: arcreport@mail.grokbot.com. ### Other free checks on /api/ask precheck proves the saved answer above. It does not fetch the store. can_agent_checkout proves a live HTTP check of one public URL: catalog answered, product found, a checkout link came back (boolean only), UCP profile, block status, and one fix. Cached per host for 24 hours. It does not store a checkout link and does not change a score. ARC stops before payment. A returned link means reach checkout, not a completed order. ask_arc proves a read of the public Index ARC already published. It does not start a scan and does not spend a model call. A missing score is not a zero. find_agent_ready_stores proves which saved HTTP checks returned a checkout link in the last thirty days. Category matching is not product inventory search. One tested product is not a promise about another item. check_store and find_stores are on /api/mcp, not on /api/ask. They read saved checks. A missing field says not recorded. They are not the first call. Start with precheck or /api/ask. ## Integrations Page: https://www.arcreport.ai/docs/precheck/integrations Call precheck before an agent tries to reach checkout. The HTTP read is GET /api/precheck?url=. The MCP tool precheck is on https://www.arcreport.ai/api/ask. https://www.arcreport.ai/api/mcp does not serve this tool. Copy checked_ago, stale, and rails[].proves unchanged. "never checked" means the check time is missing or unparseable. stale is true when the saved check is 24 hours old or older, or the time is missing. False only when the saved check is newer than 24 hours. proves describes that saved rail status. It does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link, not that an agent finished checkout. Set PRECHECK_USER_AGENT to the name of your agent. The tests set PRECHECK_URL to a local mock and do not call production. The examples do not call a model API. OpenAI function calling: pass the JSON schema in the tools list of your own client. The Python and TypeScript files run the tool locally. They do not call the OpenAI API. LangChain and LangGraph: a Python tool, and a one-node graph that calls it. With those packages missing, the same functions still run. Neither file calls a model. Vercel AI SDK: tool() from the ai package. Paste it into an app that depends on ai and zod. MCP clients: point the client at https://www.arcreport.ai/api/ask. No login. The tool name is precheck and the argument is url. Call precheck before checkout: read proves, stale, and checked_ago. Do not rewrite a missing check into a fresh one, and do not treat proves as a completed checkout. ARC is not affiliated with or endorsed by OpenAI, LangChain, LangGraph, Vercel, Claude, Cursor, VS Code, Smithery, Glama, PulseMCP, or mcp.so. Registry drafts are in integrations/registry-drafts/. Status: NOT SUBMITTED. Contact: arcreport@mail.grokbot.com. - MCP official registry server.json (mcp-registry.server.json): https://registry.modelcontextprotocol.io/v0.1/publish - Smithery (smithery.yaml): https://smithery.ai/new - Glama (glama.json): https://glama.ai/mcp/servers - PulseMCP (pulsemcp.md): https://www.pulsemcp.com/submit - mcp.so (mcp-so.md): https://github.com/chatmcp/mcpso/issues/new - LangChain community tools pull request (langchain-community-pr.md): https://github.com/langchain-ai/langchain/pulls - awesome-mcp-servers list entry (awesome-mcp-servers.md): https://github.com/punkpeye/awesome-mcp-servers ### integrations/README.md # Precheck integrations Copy-paste clients for ARC's saved pre-checkout read and the public MCP server. Docs: https://www.arcreport.ai/docs/precheck/integrations Call `precheck` before an agent tries to reach checkout. The HTTP read is `GET /api/precheck?url=`. The MCP tool of the same name is on `https://www.arcreport.ai/api/ask`. `https://www.arcreport.ai/api/mcp` does not serve this tool. Set `PRECHECK_USER_AGENT` to the name of your agent. The fallback in the snippets is a placeholder. CI sets `PRECHECK_URL` to a local mock and does not call production. These examples do not call a model API. ARC is not affiliated with or endorsed by OpenAI, LangChain, Vercel, Anthropic, Cursor, Microsoft, Browser Use, Stagehand, Smithery, Glama, PulseMCP, mcp.so, or the MCP registry. | Integration | Files | | --- | --- | | OpenAI function calling | `openai/precheck.tool.json`, `openai/precheck.py`, `openai/precheck.ts` | | LangChain tool and LangGraph | `langchain/precheck_tool.py`, `langchain/langgraph_precheck.py` | | Vercel AI SDK `tool()` | `vercel/precheck.ts` | | MCP clients | `mcp/claude_desktop.json`, `mcp/cursor.mcp.json`, `mcp/vscode.mcp.json`, `mcp/mcp-remote.json` | | Call precheck before checkout | `before-checkout/precheck.py`, `before-checkout/precheck.ts` | | Framework examples | `examples/README.md`, `examples/openai_agents_precheck.py`, `examples/langchain_precheck.py`, `examples/browser_use_precheck.py`, `examples/stagehand_precheck.ts` | | Registry drafts (not submitted) | `registry-drafts/` | The framework examples send the store origin only. `ref` and `X-ARC-Client` are one fixed integration label, never a user or session ID. Results are advisory; ARC stops before payment. `checked_ago`, `stale`, and `rails[].proves` are copied unchanged. `"never checked"` means the check time is missing or unparseable. `stale` is true when the saved check is 24 hours old or older, or the time is missing. `proves` does not prove an agent completed checkout. Contact: arcreport@mail.grokbot.com ### integrations/openai/precheck.tool.json { "type": "function", "function": { "name": "precheck", "description": "Check if an AI agent can reach checkout on a store before trying. Read ARC's saved pre-checkout answer for a product or store URL. Does not fetch the store. proves does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link, not that an agent finished checkout. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL.", "parameters": { "type": "object", "properties": { "url": { "type": "string", "description": "Product or store URL, such as https://allbirds.com/products/shoe." } }, "required": ["url"], "additionalProperties": false }, "strict": true } } ### integrations/openai/precheck.py """Minimal OpenAI function-calling handler for ARC precheck. PRECHECK_TOOL is the JSON schema in precheck.tool.json. Pass that object in the tools list of your own client. Running this file executes the tool locally. It does not call the OpenAI API and it does not fetch the store. The saved-field read lives in integrations/before-checkout/precheck.py. """ from __future__ import annotations import json import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "before-checkout")) from precheck import fetch_precheck, honest TOOL = json.loads(Path(__file__).with_name("precheck.tool.json").read_text()) EXAMPLE_URL = "https://allbirds.com/products/shoe" def handle_tool_call(name: str, arguments: dict) -> dict: if name != TOOL["function"]["name"]: raise RuntimeError("unknown tool") url = arguments.get("url") if isinstance(arguments, dict) else None if not isinstance(url, str) or not url: raise RuntimeError("precheck requires a url string") return honest(fetch_precheck(url, ref="int-openai-py", user_agent="openai-agents/1.0 (+https://example.com/contact)")) def main() -> None: url = os.environ.get("PRECHECK_EXAMPLE_URL", EXAMPLE_URL) reading = handle_tool_call(TOOL["function"]["name"], {"url": url}) print(json.dumps({"reading": reading})) if __name__ == "__main__": main() ### integrations/openai/precheck.ts /** * Minimal OpenAI function-calling handler for ARC precheck. * * PRECHECK_TOOL is the JSON schema in precheck.tool.json. Pass that object in * the tools list of your own client. Running this file executes the tool * locally. It does not call the OpenAI API and it does not fetch the store. * * The saved-field read lives in integrations/before-checkout/precheck.ts. */ import { readFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { fetchPrecheck, honest } from "../before-checkout/precheck.ts"; const dir = path.dirname(fileURLToPath(import.meta.url)); export const PRECHECK_TOOL = JSON.parse(readFileSync(path.join(dir, "precheck.tool.json"), "utf8")) as { type: "function"; function: { name: string; description: string; parameters: { type: "object"; required: string[] } }; }; const EXAMPLE_URL = "https://allbirds.com/products/shoe"; export async function handleToolCall(name: string, arguments_: { url?: unknown }) { if (name !== PRECHECK_TOOL.function.name) throw new Error("unknown tool"); const url = arguments_?.url; if (typeof url !== "string" || !url) throw new Error("precheck requires a url string"); return honest(await fetchPrecheck(url, { ref: "int-openai-ts", userAgent: "openai-agents/1.0 (+https://example.com/contact)", })); } async function main() { const url = process.env.PRECHECK_EXAMPLE_URL ?? EXAMPLE_URL; const reading = await handleToolCall(PRECHECK_TOOL.function.name, { url }); console.log(JSON.stringify({ reading })); } const isDirect = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; if (isDirect) { main().catch((error: unknown) => { console.error(error instanceof Error ? error.message : "precheck failed"); process.exit(1); }); } ### integrations/langchain/precheck_tool.py """LangChain tool: call precheck before an agent tries to reach checkout. Running this file executes the tool against PRECHECK_URL (or the public precheck API) and does not call a model. If langchain-core is not installed, the function still runs so CI can call a mock. With langchain-core, @tool wraps that same function. """ from __future__ import annotations import json import os import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "before-checkout")) from precheck import fetch_precheck, honest try: from langchain_core.tools import tool except ImportError: # CI has no LangChain install and makes no model call. def tool(fn): return fn @tool def precheck(url: str) -> str: """Call before trying to reach checkout. Read ARC's saved pre-checkout answer. Does not fetch the store. Copy checked_ago, stale, and rails[].proves unchanged. "never checked" means the check time is missing or unparseable. stale true means the saved check is 24 hours old or older, or the time is missing. proves describes that saved rail status and does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link, not that an agent finished checkout. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL. """ ref = os.environ.get("ARC_CLIENT_REF", "int-langchain-py") return json.dumps(honest(fetch_precheck( url, ref=ref, user_agent="langchain/1.0 (+https://example.com/contact)", ))) def main() -> None: url = os.environ.get("PRECHECK_EXAMPLE_URL", "https://allbirds.com/products/shoe") raw = precheck.invoke({"url": url}) if hasattr(precheck, "invoke") else precheck(url) print(json.dumps({"reading": json.loads(raw)})) if __name__ == "__main__": main() ### integrations/langchain/langgraph_precheck.py """LangGraph example: one node calls precheck before checkout. No model call. If langgraph is not installed, the same node still runs so CI can call a mock. Keep this file next to precheck_tool.py. """ from __future__ import annotations import json import os import sys from pathlib import Path from typing import TypedDict sys.path.insert(0, str(Path(__file__).resolve().parent)) from precheck_tool import precheck class PrecheckState(TypedDict, total=False): url: str reading: dict def precheck_node(state: PrecheckState) -> dict: raw = precheck.invoke({"url": state["url"]}) if hasattr(precheck, "invoke") else precheck(state["url"]) return {"reading": json.loads(raw)} def run(url: str) -> dict: os.environ.setdefault("ARC_CLIENT_REF", "int-langgraph-py") try: from langgraph.graph import END, START, StateGraph except ImportError: return precheck_node({"url": url}) graph = StateGraph(PrecheckState) graph.add_node("precheck", precheck_node) graph.add_edge(START, "precheck") graph.add_edge("precheck", END) return graph.compile().invoke({"url": url}) def main() -> None: url = os.environ.get("PRECHECK_EXAMPLE_URL", "https://allbirds.com/products/shoe") print(json.dumps(run(url))) if __name__ == "__main__": main() ### integrations/vercel/precheck.ts /** * Vercel AI SDK tool() example for ARC precheck. * * `vercelToolExample` is the copy-paste block. It calls tool() from the `ai` * package. Running this file does not import `ai` and does not call a model. * `execute` is the same read, and CI points PRECHECK_URL at a mock. */ import { pathToFileURL } from "node:url"; import { LIMITS, fetchPrecheck, honest } from "../before-checkout/precheck.ts"; const description = `Call before an agent tries to reach checkout. Read ARC's saved pre-checkout answer. Does not fetch the store. Copy checked_ago, stale, and rails[].proves unchanged. "never checked" means the check time is missing or unparseable. stale is true when the saved check is 24 hours old or older, or the time is missing. proves does not prove an agent completed checkout. reachable_checkout true means a saved check returned a checkout link. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL.`; export const vercelToolExample = `import { tool } from "ai"; import { z } from "zod"; // Set PRECHECK_USER_AGENT to the name of your agent. The fallback names this client. const USER_AGENT = process.env.PRECHECK_USER_AGENT ?? "vercel-ai-sdk/1.0 (+https://example.com/contact)"; const endpoint = process.env.PRECHECK_URL ?? "https://www.arcreport.ai/api/precheck"; const DAY_MS = 24 * 60 * 60 * 1000; const cache = new Map(); function storeHost(url) { try { return new URL(url).hostname.toLowerCase().replace(/\\.$/, "").replace(/^www\\./, "") || url; } catch { return url; } } function retryAfterSeconds(header) { if (!header) return 60; const asNumber = Number(header); if (Number.isFinite(asNumber) && asNumber >= 0) return Math.ceil(asNumber); const when = Date.parse(header); if (Number.isFinite(when)) return Math.max(0, Math.ceil((when - Date.now()) / 1000)); return 60; } export const precheck = tool({ description: ${JSON.stringify(description)}, inputSchema: z.object({ url: z.string().describe("Product or store URL"), }), execute: async ({ url }) => { const host = storeHost(url); const saved = cache.get(host); // One saved answer per host for 24 hours. The limit is 60 requests a minute. if (saved && Date.now() - saved.at < DAY_MS) return saved.value; const res = await fetch(endpoint + "?url=" + encodeURIComponent(url) + "&ref=int-vercel-ai", { headers: { "user-agent": USER_AGENT, "x-arc-client": "int-vercel-ai", accept: "application/json" }, }); if (res.status === 429) { // Respect Retry-After. Do not retry in a loop. return "rate limited, retry after " + retryAfterSeconds(res.headers.get("retry-after")) + " s"; } if (!res.ok) throw new Error("precheck HTTP " + res.status); const body = await res.json(); const rails = Array.isArray(body.rails) ? body.rails.map((rail) => ({ name: rail.name ?? null, status: rail.status ?? null, proves: rail.proves ?? null })) : null; const value = { host: body.host ?? null, known: body.known ?? null, reachable_checkout: body.reachable_checkout ?? null, checked_ago: body.checked_ago ?? null, stale: body.stale ?? null, rails, limits: { checked_ago: ${JSON.stringify(LIMITS.checked_ago)}, stale: ${JSON.stringify(LIMITS.stale)}, proves: ${JSON.stringify(LIMITS.proves)}, reachable_checkout: ${JSON.stringify(LIMITS.reachable_checkout)}, }, }; cache.set(host, { at: Date.now(), value }); return value; }, }); `; const EXAMPLE_URL = "https://allbirds.com/products/shoe"; export async function execute(url: string) { return honest(await fetchPrecheck(url, { ref: "int-vercel-ai", userAgent: "vercel-ai-sdk/1.0 (+https://example.com/contact)", })); } async function main() { const url = process.env.PRECHECK_EXAMPLE_URL ?? EXAMPLE_URL; console.log(JSON.stringify({ reading: await execute(url) })); } const isDirect = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; if (isDirect) { main().catch((error: unknown) => { console.error(error instanceof Error ? error.message : "precheck failed"); process.exit(1); }); } ### integrations/mcp/claude_desktop.json { "mcpServers": { "arc-ask": { "command": "npx", "args": ["-y", "mcp-remote@0.14.3", "https://www.arcreport.ai/api/ask?ref=int-mcp-claude"] } } } ### integrations/mcp/cursor.mcp.json { "mcpServers": { "arc-ask": { "url": "https://www.arcreport.ai/api/ask?ref=int-mcp-cursor" } } } ### integrations/mcp/vscode.mcp.json { "servers": { "arc-ask": { "type": "http", "url": "https://www.arcreport.ai/api/ask?ref=int-mcp-vscode" } } } ### integrations/mcp/mcp-remote.json { "mcpServers": { "arc-ask": { "command": "npx", "args": ["-y", "mcp-remote@0.14.3", "https://www.arcreport.ai/api/ask?ref=int-mcp-remote"] } } } ### integrations/before-checkout/precheck.py """Call precheck before checkout and read proves, stale, and checked_ago honestly. Set PRECHECK_USER_AGENT to the name of your agent. The fallback names this client. Set PRECHECK_URL to a mock in CI. This file does not fetch the store and it does not call a model. """ from __future__ import annotations import json import math import os import time import urllib.error import urllib.parse import urllib.request # Set PRECHECK_USER_AGENT to the name of your agent. The fallback names this client. USER_AGENT = os.environ.get("PRECHECK_USER_AGENT", "arc-precheck/1.0 (+https://example.com/contact)") CLIENT_REF = "int-before-py" DEFAULT_ENDPOINT = "https://www.arcreport.ai/api/precheck" DAY_S = 24 * 60 * 60 _CACHE: dict[str, tuple[float, dict]] = {} EXAMPLE_URL = "https://allbirds.com/products/shoe" LIMITS = { "checked_ago": 'Coarse age of last_checked, copied unchanged. "never checked" means the check time is missing or unparseable. null means the saved time is in the future, so no age is stated.', "stale": "Copied unchanged. True when last_checked is 24 hours old or older, or the check time is missing or unparseable. False only when the saved check is newer than 24 hours.", "proves": "Copied unchanged from each rail. The sentence describes that saved status. It does not prove an agent completed checkout. null rails means ARC returned no proves sentence.", "reachable_checkout": "Copied unchanged. True only when a saved check returned a checkout link. False when that check finished without one. null when there is no completed check. This is not an agent that finished checkout.", } def endpoint() -> str: return os.environ.get("PRECHECK_URL", DEFAULT_ENDPOINT) def _store_host(url: str) -> str: parsed = urllib.parse.urlparse(url if "://" in url else "https://" + url) host = (parsed.hostname or url).lower().rstrip(".") if host.startswith("www."): host = host[4:] return host def _retry_after_seconds(header: str | None) -> int: if not header or not header.strip(): return 60 try: seconds = float(header) except ValueError: seconds = None if seconds is not None and math.isfinite(seconds) and seconds >= 0: return math.ceil(seconds) try: from email.utils import parsedate_to_datetime when = parsedate_to_datetime(header) except (TypeError, ValueError, IndexError, OverflowError): return 60 if when is None: return 60 return max(0, math.ceil(when.timestamp() - time.time())) def fetch_precheck(url: str, *, ref: str = CLIENT_REF, user_agent: str | None = None) -> dict: host = _store_host(url) saved = _CACHE.get(host) # One saved answer per host for 24 hours. The limit is 60 requests a minute. if saved and time.time() - saved[0] < DAY_S: return saved[1] chosen = os.environ.get("PRECHECK_USER_AGENT") or user_agent or USER_AGENT target = endpoint() + "?" + urllib.parse.urlencode({"url": url, "ref": ref}) request = urllib.request.Request( target, headers={"User-Agent": chosen, "X-ARC-Client": ref, "Accept": "application/json"}, method="GET", ) try: with urllib.request.urlopen(request, timeout=10) as response: payload = json.load(response) except urllib.error.HTTPError as error: if error.code == 429: # Respect Retry-After. Do not retry in a loop. wait = _retry_after_seconds(error.headers.get("Retry-After")) raise RuntimeError("rate limited, retry after " + str(wait) + " s") from None raise if not isinstance(payload, dict): raise RuntimeError("precheck response was not a JSON object") _CACHE[host] = (time.time(), payload) return payload def honest(body: dict) -> dict: """Copy the saved fields. Do not rewrite a missing check into a fresh one.""" rails = body.get("rails") copied = None if isinstance(rails, list): copied = [ {"name": rail.get("name"), "status": rail.get("status"), "proves": rail.get("proves")} for rail in rails if isinstance(rail, dict) ] return { "host": body.get("host"), "known": body.get("known"), "reachable_checkout": body.get("reachable_checkout"), "checked_ago": body.get("checked_ago"), "stale": body.get("stale"), "rails": copied, "limits": LIMITS, } def reading_for(url: str) -> dict: return honest(fetch_precheck(url)) def main() -> None: url = os.environ.get("PRECHECK_EXAMPLE_URL", EXAMPLE_URL) print(json.dumps({"reading": reading_for(url)})) if __name__ == "__main__": main() ### integrations/before-checkout/precheck.ts /** * Call precheck before checkout and read proves, stale, and checked_ago honestly. * * Set PRECHECK_USER_AGENT to the name of your agent. The fallback names this client. * Set PRECHECK_URL to a mock in CI. This file does not fetch the store and it * does not call a model. */ import { pathToFileURL } from "node:url"; // Set PRECHECK_USER_AGENT to the name of your agent. The fallback names this client. export const USER_AGENT = process.env.PRECHECK_USER_AGENT ?? "arc-precheck/1.0 (+https://example.com/contact)"; export const CLIENT_REF = "int-before-ts"; const DEFAULT_ENDPOINT = "https://www.arcreport.ai/api/precheck"; const DAY_MS = 24 * 60 * 60 * 1000; const cache = new Map(); const EXAMPLE_URL = "https://allbirds.com/products/shoe"; export const LIMITS = { checked_ago: 'Coarse age of last_checked, copied unchanged. "never checked" means the check time is missing or unparseable. null means the saved time is in the future, so no age is stated.', stale: "Copied unchanged. True when last_checked is 24 hours old or older, or the check time is missing or unparseable. False only when the saved check is newer than 24 hours.", proves: "Copied unchanged from each rail. The sentence describes that saved status. It does not prove an agent completed checkout. null rails means ARC returned no proves sentence.", reachable_checkout: "Copied unchanged. True only when a saved check returned a checkout link. False when that check finished without one. null when there is no completed check. This is not an agent that finished checkout.", } as const; type Rail = { name?: unknown; status?: unknown; proves?: unknown }; type PrecheckBody = { host?: unknown; known?: unknown; reachable_checkout?: unknown; checked_ago?: unknown; stale?: unknown; rails?: unknown; }; export function endpoint(): string { return process.env.PRECHECK_URL ?? DEFAULT_ENDPOINT; } function storeHost(url: string): string { try { return new URL(url).hostname.toLowerCase().replace(/\.$/, "").replace(/^www\./, "") || url; } catch { return url; } } function retryAfterSeconds(header: string | null): number { if (!header) return 60; const asNumber = Number(header); if (Number.isFinite(asNumber) && asNumber >= 0) return Math.ceil(asNumber); const when = Date.parse(header); if (Number.isFinite(when)) return Math.max(0, Math.ceil((when - Date.now()) / 1000)); return 60; } export async function fetchPrecheck(url: string, options?: { ref?: string; userAgent?: string }): Promise { const host = storeHost(url); const saved = cache.get(host); // One saved answer per host for 24 hours. The limit is 60 requests a minute. if (saved && Date.now() - saved.at < DAY_MS) return saved.body; const ref = options?.ref ?? CLIENT_REF; const userAgent = process.env.PRECHECK_USER_AGENT || options?.userAgent || USER_AGENT; const target = endpoint() + "?url=" + encodeURIComponent(url) + "&ref=" + encodeURIComponent(ref); const res = await fetch(target, { headers: { "user-agent": userAgent, "x-arc-client": ref, accept: "application/json" }, }); if (res.status === 429) { // Respect Retry-After. Do not retry in a loop. throw new Error("rate limited, retry after " + retryAfterSeconds(res.headers.get("retry-after")) + " s"); } if (!res.ok) throw new Error("precheck HTTP " + res.status); const payload: unknown = await res.json(); if (!payload || typeof payload !== "object" || Array.isArray(payload)) { throw new Error("precheck response was not a JSON object"); } const body = payload as PrecheckBody; cache.set(host, { at: Date.now(), body }); return body; } /** Copy the saved fields. Do not rewrite a missing check into a fresh one. */ export function honest(body: PrecheckBody) { const rails = Array.isArray(body.rails) ? body.rails.filter((rail): rail is Rail => Boolean(rail) && typeof rail === "object").map((rail) => ({ name: rail.name ?? null, status: rail.status ?? null, proves: rail.proves ?? null, })) : null; return { host: body.host ?? null, known: body.known ?? null, reachable_checkout: body.reachable_checkout ?? null, checked_ago: body.checked_ago ?? null, stale: body.stale ?? null, rails, limits: LIMITS, }; } export async function readingFor(url: string) { return honest(await fetchPrecheck(url)); } async function main() { const url = process.env.PRECHECK_EXAMPLE_URL ?? EXAMPLE_URL; console.log(JSON.stringify({ reading: await readingFor(url) })); } const isDirect = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href; if (isDirect) { main().catch((error: unknown) => { console.error(error instanceof Error ? error.message : "precheck failed"); process.exit(1); }); } ## Free MCP tools on https://www.arcreport.ai/api/ask No API key. POST JSON-RPC initialize, tools/list, and tools/call to https://www.arcreport.ai/api/ask. These 16 tools are the free surface. Descriptions are the live tool text. A checkout link is not a completed order. Results are advisory. ARC stops before payment. ### ask_arc(url, brand, category, q) Read whether an AI agent can reach checkout for a brand the ARC Index already lists. No login and no new scan. Pass a brand, URL, or category. Returns the score, evidence, and both lanes: the agent-catalog HTTP check and the browser cart. The JSON field `the checkout flag` is true when the catalog returned a checkout link or a browser shopper added the product to the cart. ARC stops before payment. It is not a completed order. The catalog check does not change the score. A missing score is not a zero. Results are advisory. ### can_agent_checkout(url) Check if an AI agent can reach checkout on a store before trying. Live HTTP check of a public homepage or product URL: catalog, product, checkout link, UCP profile, blocks, and one fix. No login, model call, payment, or browser scan. Cached per host for 24 hours. A checkout link means reach checkout, not a completed order. Results are advisory. ARC stops before payment. ### precheck(url) Check if an AI agent can reach checkout on a store before trying. Saved automated HTTP check for a product or store URL. Uses ARC's saved directory, rails, store answers and check history. When those have no row, uses the plain-HTTP checker. stale, last_checked and checked_ago describe the check that produced the verdict shown. lastReachableCheck is a separate, newer plain-HTTP time. It does not re-verify the checkout link or rails. Does not fetch the store and does not start a scan. The saved result is ARC's automated HTTP check. It is not a live agent session and not a completed order. EachSpy, OpenStreetMap, campaign shops and opted-out hosts come back as unknown. A checker fallback is http-only and checkout was not part of the check. JSON: GET /api/precheck?url=. Results are advisory. ARC stops before payment. Advisory only, not a guarantee. Do not refuse or avoid a store solely because of ARC's data. known:false or missing data is not a no. Data CC BY 4.0, attribute ARC (arcreport.ai); OSM-derived rows are ODbL. ### find_agent_ready_stores(category, q, limit) Find stores where a saved check shows an AI agent can reach checkout, for running shoes, beauty, clothing, home goods, or another product need. Reads real HTTP agent-catalog checks from the last thirty days whose catalog found a product and returned a checkout link. This is not a completed order. One tested product is not a promise about another item. No login, browser scan, or model call. Results are advisory. ARC stops before payment. ### agent_commerce_adoption() Read ARC's agent commerce adoption counts, category and commerce platform rates from saved HTTP checks, including Checkout WebMCP among measured Shopify checkouts, with unknown and not checked separate; shopper approval required. Curated sample, not all e-commerce. No store requests or model calls. ### product_data_check(host, url) Free latest saved product data and return/shipping policy check from the store's pages and structured data. Reads history only; no store fetch or model call. A missing result is not a zero. ### google_checkout_readiness(host) Read saved public Google agentic checkout readiness checks for a store. Free; no new scan or paid calls. Merchant Center feed eligibility cannot be checked from outside. Not Google approval or certification. ### get_agent_facts(host, url, slug) Read free ARC agent facts for a store or product with no login. Returns TypeSafe Jev typed decisions (use-case mention, price clarity, agent-friendly description, cart-blocker suspicion, readiness score) from an HTTP scrape. Not a browser session and not any third-party agent product; ARC's browser cart tests (ARC browser shoppers A–E) are separate. JSON: GET /api/agent-facts?host= ### list_category_agent_facts(category, limit) List free ARC agent facts by category (beauty, footwear, apparel, …) with no login. Ranked by newest real Jev scores. JSON: GET /api/agent-facts?category=beauty ### dataset_info() Latest open ARC dataset snapshot (start with store_answers: one plain answer per store on whether AI agents can find products and get a checkout link): immutable versioned download URLs plus latest/ aliases (CSV and Parquet), row counts, method version and licence (CC-BY-4.0, attribution ARC, arcreport.ai). Reads only. ### protocol_matrix(protocol) Read ARC's neutral Protocol Matrix: for UCP, ACP, PAP, WebMCP, MCP and agent payment protocols, how many checked stores declare, validate, answer and transact, with evidence labels (Measured / Inferred / Not measured). Saved checks only; no store requests or model calls. CC-BY-4.0. ### agent_leaderboard(include_stores) Read ARC's open agent leaderboard: cart success over the fixed ARC-100 v1 test set for each agent path ARC measures (ARC browser shoppers A-E, the UCP path, the storefront MCP path), with n, dates, evidence labels and Wilson 95% intervals, plus unverified outside submissions. Saved results only; no store requests or model calls. CC-BY-4.0. ### submit_leaderboard_run(spec, test_set, agent, run, results, contact) Submit your agent's run against ARC-100 v1 to ARC's open leaderboard. Free. The run is listed as 'unverified' until ARC re-runs a sample. Format: arc-leaderboard-submission/v1 (see /leaderboard/spec). Runs must stop before payment. Limit 5 per network per day. Same as POST /api/leaderboard/submit. ### submit_store(url, hosts, category) Add stores to ARC's free, open agent-commerce directory (CC-BY dataset). Pass url for one store or hosts for up to 50. Each new host is validated (live HTTPS, not parked, not redirecting to another brand); accepted hosts are queued for ARC's plain-HTTP rails checks (UCP, WebMCP, MCP, policies, product data) and appear in the dataset with source=submitted. No model calls, no login. Rate limited per caller. ### store_card(host) Read a public store card and saved brand claim. No outbound fetch or model calls. Brand statements are from the brand and not measured by ARC; claims never change scores. ### validate_ucp_profile(url, profile) Validate a Universal Commerce Protocol (UCP) business profile against the published UCP JSON Schema (current release) and spec rules: authority binding, https endpoints, orphaned extensions and, for a URL, hosting headers (no redirects, Cache-Control public max-age>=60). Pass url (store host, homepage or profile URL; /.well-known/ucp is added for a homepage) or profile (the JSON). Returns errors, warnings and info, each with a path and the spec section. Free, no login, no model call. Validation is not certification. JSON: GET /api/ucp/validate?url= ## OpenAPI summary openapi: 3.1.0 ARC public API. Free, no-login API for the ARC open agentic commerce benchmark: check a store, validate UCP, read the Index, leaderboard, protocol matrix and dataset, and contribute stores and runs. Full document: https://www.arcreport.ai/api/openapi.json The same free document is also at https://www.arcreport.ai/openapi.json and https://www.arcreport.ai/.well-known/openapi.json. No API key. The document covers precheck and the free /api/ask surface, plus the other public reads. Signed-in scans, billing, admin, and cron routes are omitted. Precheck and /api/ask: GET /api/precheck — operationId precheck — Saved pre-checkout answer for a product or store URL GET /api/ask — operationId askArc — Ask which stores agents can reach checkout on, or read one brand POST /api/ask — operationId askArcPost — Read the Index or check checkout via MCP Other free operations in the same document: GET /.well-known/mcp.json — operationId mcpDiscovery — Both MCP servers and every public tool, with docs links GET /api/agent-checkout — operationId canAgentCheckout — Check whether an AI agent can reach checkout at a store GET /api/agent-checkout/adoption — operationId agentCommerceAdoption — Agent commerce adoption counts by category and platform GET /api/agent-checkout/product-data — operationId product_data_check — Latest saved product data from the page’s structured data GET /api/agent-checkout/stores — operationId findAgentReadyStores — Find stores where an AI agent can reach checkout for running shoes or another need GET /api/agent-facts — operationId getAgentFacts — Free agent facts for any store (Jev typed decisions) GET /api/ask/server-card — operationId askServerCard — MCP server card for the no-login /api/ask server GET /api/insights/{file} — operationId insightData — Data behind the /insights research pages, as JSON or CSV GET /api/leaderboard — operationId agentLeaderboard — Open agent leaderboard on ARC-100 GET /api/leaderboard/submit — operationId leaderboardSubmissionFormat — Submission format, schema URL and a full example body POST /api/leaderboard/submit — operationId submitLeaderboardRun — Submit your agent's ARC-100 run GET /api/mcp/server-card — operationId scanServerCard — MCP server card for /api/mcp. check_store, find_stores, and product_data_check need no login. A new scan needs OAuth. GET /api/methodology/changelog — operationId methodologyChangelog — Method version and changelog GET /api/outcomes — operationId getOutcomeReport — Public checkout outcomes for one store, labelled by source POST /api/outcomes — operationId reportCheckout — Report how far an agent got at checkout GET /api/precheck/stats — operationId precheckStats — How many pre-checkout answers ARC has served GET /api/protocols — operationId protocolMatrix — Protocol Matrix: UCP, ACP, PAP, WebMCP, MCP and payments, stage by stage GET /api/store — operationId storeCard — Look up one store's saved public card POST /api/stores/submit — operationId submitStores — Add stores to ARC's free open directory GET /api/ucp/validate — operationId validateUcpProfile — Validate a store's UCP profile against the published UCP schema POST /api/ucp/validate — operationId validateUcpProfileJson — Validate pasted UCP profile JSON, or a URL GET /api/weekly/agent-checkout.json — operationId weeklyAgentCheckoutLatest — Latest weekly US share of stores that returned a UCP checkout link GET /api/weekly/agent-checkout/{edition} — operationId weeklyAgentCheckoutEdition — Immutable weekly US agent-checkout figure for one edition date GET /badge/{host}.svg — operationId storeBadgeSvg — Free embeddable SVG badge. It says 'ARC checkout check passed' only for a measured checkout link, with the evidence label (Measured, Inferred or Not measured) and check date. Not a score and not a live agent session. Cached about an hour. Never starts a check. GET /data/latest/manifest.json — operationId datasetManifest — Latest open dataset manifest (CSV and Parquet) GET /data/versions.json — operationId datasetVersions — Every immutable dataset snapshot version GET /store/{host}/badge.svg — operationId storeBadge — Embeddable SVG badge with a store's ARC result (same as /badge/{host}.svg)