# AfterImage AfterImage is a shared copy of the public web for AI agents. If a page was fetched recently, reuse that copy instead of scraping again. Each copy has a timestamp and a sha256 hash of the raw bytes. Base URL: https://afterimage.page Do not scrape this file. Call the JSON endpoints below. ## 1. Get a key (human, once) POST https://afterimage.page/v1/billing/checkout Content-Type: application/json {"pack":"starter"} Packs: starter = $5 USD, builder = $20 USD. The JSON returns api_key and checkout_url. Open checkout_url, pay, then keep api_key secret. Send it on every later request: Authorization: Bearer ak_live_… HTTP 402 means unpaid. The JSON field code is one of: - missing_key: no Authorization header (x402 clients pay here) - unknown_key: bearer token is not a key from this instance - unfunded_key: key was created but never paid for - insufficient_credits: key is empty; buy another pack GET https://afterimage.page/v1/billing/balance with the same header to see remaining dollars. Successful billed responses also send X-Credits-Remaining. Prices: - GET /v1/search = $0.005 per call - GET /v1/page cache hit = $0.002 (copy still fresh) - GET /v1/page live fetch = $0.01 (origin was contacted) Optional: x402 USDC on Base instead of a key, if this instance advertises it at GET https://afterimage.page/.well-known/x402 ## 2. Search copies (does not visit the live web) GET https://afterimage.page/v1/search?q=fastapi+background+tasks Query: - q (required): words to find in titles and text - limit (optional, default 10, max 50) - max_age_s (optional): only copies this many seconds old Response: - indexed: how many copies are stored - hits[]: url, title, snippet, hash, fetched_at, age_s, status, score, truncated, aliases (other URLs with the same hash) GET https://afterimage.page/v1/stats is free and returns live indexed plus caps. If indexed is 0, the library is empty. Fetch URLs with /v1/page, then search again. If hits is empty but indexed > 0, nothing matched. Try different words or fetch the URL. ## 3. Fetch or reuse a URL GET https://afterimage.page/v1/page?url=https://example.com/pricing&max_age_s=900 Live fetches always send this fixed header set (never Cookie, never Authorization): User-Agent: AfterImage/0.1 (+https://github.com/Lordkro/afterimage; agent-snapshot) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.9 The stored copy is that one variant. If the origin sent Vary, it is returned as vary. Locale- or UA-specific pages may not match a different client. Query: - url (required): http or https. No logins, no private/internal addresses. - max_age_s (optional, default 900): reuse a stored copy if it is no older than this Response: - cache: "hit" (reused) or "miss" (live fetch) - text: readable extract, not HTML - hash: sha256 of the raw response body - fetched_at, age_s, status, title, final_url - truncated: true if text hit the character cap - stored: false if AfterImage did not keep a copy - stored_reason: noarchive | vary | http_error | thin_extract | volatile | challenge | training_data | example | alias - origin_max_age_s: remaining origin freshness in seconds (s-maxage/max-age minus Age, or Expires; 0 for no-cache / no-store / private). Reuse is min(your max_age_s, origin_max_age_s, 10-day TTL). - vary, etag, last_modified: origin validators when present (etag/last-modified are stored for later revalidation; they do not change price today) Reuse of a stored copy is min(your max_age_s, origin_max_age_s, 10-day TTL). If you already have the URL from a search hit, call /v1/page to get the full text. If cache=hit, do not fetch the origin yourself unless you need a smaller max_age_s. ## MCP POST https://afterimage.page/mcp JSON-RPC. Tools: search_pages, get_page. initialize and tools/list are free. tools/call for those two tools needs the same Authorization header as HTTP, or x402 USDC. HTTP 402 if unpaid. Server card: GET https://afterimage.page/.well-known/mcp.json Also: GET https://afterimage.page/mcp/server-card ## Other - Health: GET https://afterimage.page/health (includes live indexed) - Stats: GET https://afterimage.page/v1/stats (free; indexed and caps) - OpenAPI: GET https://afterimage.page/openapi.json - Agent card: GET https://afterimage.page/.well-known/agent-card.json - x402 discovery: GET https://afterimage.page/.well-known/x402 - robots.txt: GET https://afterimage.page/robots.txt ## Policy AfterImage caches public http(s) pages for agents. It rejects private/internal addresses and does not follow logins. It does not currently honor origin robots.txt crawl rules. Live fetch honors noarchive (meta robots and X-Robots-Tag) and Vary: *: the caller still gets the page; AfterImage does not store it. Cache-Control no-store / private / no-cache is stored for search (AfterImage is a snapshot index, not a shared HTTP cache) but origin freshness is 0, so /v1/page always refetches those URLs and bills at the live-fetch rate. Age is subtracted from max-age. stored_reason is why a live fetch was not kept: - noarchive / vary: the origin asked not to archive, or Vary: * - volatile: AfterImage will not index status pages (stale "up" is worse than a miss) - training_data: stdlib, PEPs, MDN, RFCs, git-scm, Node, TypeScript, OWASP, sqlite, redis, JSON Schema — fetchable, not sold - example: IANA example.com at the site root (ping/docs fixtures) - alias: same bytes as a URL still on the seed list; the seed URL is kept - challenge: bot interstitial (Cloudflare and similar) - thin_extract / http_error: extract was a JS shell, title-only, fat HTML with almost no body, an order of magnitude shorter than the copy already stored, or an HTTP error noarchive, Vary: *, volatile, and training_data delete any stored copy. challenge and thin_extract leave the previous copy in place so a wall does not replace a working snapshot. Copies that are stored are evicted after 10 days or when the 5,000-page cap drops the oldest. Caps and live size: GET https://afterimage.page/v1/stats. To request removal of a stored URL, email removal@afterimage.page.