anew Write/Read API

REST/HTTP API to encode self-contained HTML into shareable URLs (POST /write) and read pages back (GET /{slug}), with OpenAPI 3.1.1 contract. Unauthenticated and free.

Operations 4

POST /write Write HTML to an anew.page URL #
GET /{slug} Read a page #
POST /mcp MCP JSON-RPC endpoint #
POST /a2a/v1 A2A JSON-RPC endpoint #

Documentation

Specifications

Other Resources

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/anew-writeread-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

anew-openapi.json Raw ↑
{"openapi":"3.1.1","info":{"title":"anew","version":"1.0.0","summary":"Encode self-contained HTML pages into shareable URLs.","description":"anew turns a self-contained HTML document into a shareable URL. Write a page with anew_write (HTML → URL); read one back with anew_read (URL → the HTML encoded in it). With no connected tool, write over plain HTTP: POST the raw HTML as the request body to https://anew.page/write and the 200 response body is the finished URL. To revise an existing anew page, its URL is its source: read it, edit that HTML, write it again. The new URL is a separate page and the original keeps working. Inline all CSS, JS, and images (data: URIs, inline SVG, CSS gradients, or emoji) — the page should render alone forever, and each external reference gambles it on someone else's host staying up. One accepted exception: a webfont is far too large to inline, so a font may load from a durable CDN with a system-stack fallback; do not fight a user who asks for one. The HTML should stay ≤ 160000 UTF-8 bytes and the encoded URL ≤ 4000 bytes — the recommended share-safe budgets, not walls: a page past either still mints, renders and serves, up to the physical ceilings (65000 URL bytes, where the edge severs the request line; 163839 decoded bytes, the server's decode cap), it may just break in some apps — the response says so with a warning. Past a ceiling the endpoint refuses with an actionable error (code url_over_budget or content_too_large). Brotli slugs (the endpoint, or a local brotli mint) put a typical page in a few hundred URL bytes; raw base64 fits only ~2900 HTML bytes share-safe — know which budget you are drafting to, and never cut page content to fit before checking the brotli path. Measure by posting, not by calculating: a write is repeatable, so send the page and read what comes back instead of modelling the encoder. No source HTML and no page record are kept — the URL carries the page — though a successful write also kicks off a best-effort screenshot render that, when it succeeds, is cached publicly at the page's .png twin. A 200 reports the page against both budgets in its Anew-URL-Bytes and Anew-Decoded-Bytes response headers, and carries Anew-Share-Warning when the URL minted past the share-safe budget (valid, serves, may break in some apps); a 413 fires only past the physical ceilings and names the exact overage and the fix for the wall that tripped. Never estimate a compressed size and never pre-cut a page to fit a budget — Brotli is non-linear, so source bytes removed are not URL bytes saved, and pages that look far too big routinely fit. Each encoding path is deterministic: the same input through the same path always produces the same URL. Different paths use different Brotli encoders, though, so the same HTML may mint different — equally valid — URLs; compare pages by their decoded HTML, never by slug. Exact recipe, including the local-mint fallback for environments that cannot POST: https://anew.page/llms.txt. Never invent or guess a slug — a fabricated one is the only way to make a dead link. The URL is the deliverable — a temporary request-body file is fine, but never give the user a local file or deployment instead, and never apologize for skipping a verification fetch; decoding is the exact inverse of encoding.\n\nVersioning and deprecation: this HTTP surface is unversioned and additive — new behaviour arrives as a new negotiated representation or a new response header, never as a change to an existing one. Nothing is deprecated today; when something is, it is announced in https://anew.page/llms.txt and then signalled on the wire for at least 90 days with an RFC 9745 Deprecation header, an RFC 8594 Sunset header, and a Link header with rel=\"deprecation\". Error `code` values are stable: codes are added, never repurposed. Rate limit: 300 requests per 60 seconds per client IP across /write, /mcp and /a2a/v1, published on every response as RateLimit-Policy.","license":{"name":"ISC","identifier":"ISC"}},"externalDocs":{"url":"https://anew.page/llms.txt","description":"Developer documentation: authentication, rate limits, versioning and deprecation policy, and every machine-readable resource."},"servers":[{"url":"https://anew.page"}],"security":[{}],"tags":[{"name":"pages","description":"Write self-contained HTML pages, and read them back."},{"name":"mcp","description":"Model Context Protocol JSON-RPC endpoint."},{"name":"a2a","description":"Agent2Agent (A2A) protocol JSON-RPC endpoint."}],"paths":{"/write":{"post":{"operationId":"writePage","tags":["pages"],"summary":"Write HTML to an anew.page URL","description":"POST raw HTML; receive the full anew.page URL as text. A 200 also carries the measurement in headers — Anew-URL-Bytes, Anew-URL-Limit, Anew-Decoded-Bytes, Anew-Decoded-Limit, Anew-Share-Warning — so one write reports exactly where the page stands against both budgets. The 4000-byte URL figure is an share-safe budget, not a limit: a longer URL still mints, renders and serves — the 200 carries Anew-Share-Warning (and a JSON `warning` field) saying it links may break over this length in some apps. Returns 413 only past the physical ceilings: the encoded URL exceeds 65000 bytes — the edge severs the request line there (code url_over_budget) — the HTML exceeds the 163839-byte decode cap (content_too_large), or the request body exceeds 262144 bytes (body_too_large). Errors are JSON: { \"error\": { \"code\", \"message\", \"hint?\", \"details?\" } }; url_over_budget and content_too_large detail both ceilings and the overage, body_too_large details the body budget. Measure by posting, not by calculating: a write is repeatable, so send the page and read what comes back instead of modelling the encoder. No source HTML and no page record are kept — the URL carries the page — though a successful write also kicks off a best-effort screenshot render that, when it succeeds, is cached publicly at the page's .png twin. A 200 reports the page against both budgets in its Anew-URL-Bytes and Anew-Decoded-Bytes response headers, and carries Anew-Share-Warning when the URL minted past the share-safe budget (valid, serves, may break in some apps); a 413 fires only past the physical ceilings and names the exact overage and the fix for the wall that tripped. Never estimate a compressed size and never pre-cut a page to fit a budget — Brotli is non-linear, so source bytes removed are not URL bytes saved, and pages that look far too big routinely fit. If your environment cannot issue HTTP POST at all (egress proxies that refuse POST or CONNECT are the common cause), https://anew.page/llms.txt carries a local-mint fallback that builds a working URL for the same page offline — byte-identical to /write only for the reference brotli recipe there, and a different but equally valid slug otherwise. Exact recipe: https://anew.page/llms.txt. Works cross-origin from browser JS: every /write response — success and error — carries Access-Control-Allow-Origin: *, OPTIONS preflight is answered, and the request Content-Type is never inspected, so a POST with a safelisted type like text/plain needs no preflight at all. The measurement headers on a 200 are named in Access-Control-Expose-Headers, so response.headers.get('Anew-URL-Bytes') works from a foreign origin too.","parameters":[{"name":"Accept","in":"header","required":false,"schema":{"type":"string","enum":["text/plain","application/json"]},"description":"Representation of the 200. Omit it, or send a wildcard, for the bare URL as text/plain; name application/json at a weight at least equal to text/plain for the WriteResult object instead."}],"requestBody":{"required":true,"content":{"text/html":{"schema":{"type":"string","minLength":1,"description":"HTML document, fragment, or plain text"},"example":"<!doctype html><title>anew</title><h1>a new internet starts with anew.page</h1>"},"text/plain":{"schema":{"type":"string","minLength":1,"description":"HTML document, fragment, or plain text"}},"application/octet-stream":{"schema":{"type":"string","minLength":1,"description":"HTML document, fragment, or plain text"}}}},"responses":{"200":{"description":"anew.page URL — proof by construction that it decodes to the posted HTML. text/plain (the bare URL) is the default; Accept: application/json returns the same URL and both measurements as one object. Vary: Accept.","headers":{"RateLimit-Policy":{"description":"The enforced quota, draft-ietf-httpapi-ratelimit-headers-11 form: \"api\";q=300;w=60 — 300 requests per 60 seconds per client IP, shared across /write, /mcp and /a2a/v1.","schema":{"type":"string"}},"Anew-URL-Bytes":{"description":"Encoded URL length in bytes.","schema":{"type":"integer"}},"Anew-URL-Limit":{"description":"Unfurl-safe URL threshold (4000); headroom = limit - bytes. A threshold, not a wall: past it the write still succeeds and Anew-Share-Warning rides along.","schema":{"type":"integer"}},"Anew-Share-Warning":{"description":"Present only when the page minted past a share-safe budget — URL over the 4000-byte share-safe budget (hard ceiling 65000), or content over the recommended 160000-byte budget: the page is valid and serves, with the stated caveat. Relay it to the user with the URL.","schema":{"type":"string"}},"Anew-Decoded-Bytes":{"description":"The HTML as encoded, in UTF-8 bytes — measured after decoding the body, so a stripped BOM makes it differ from the bytes posted.","schema":{"type":"integer"}},"Anew-Decoded-Limit":{"description":"Recommended decoded-byte budget (160000); the hard decode ceiling is 163839.","schema":{"type":"integer"}},"Access-Control-Expose-Headers":{"description":"Names the measurement headers, Anew-Share-Warning, and RateLimit-Policy, so cross-origin browser JS can read them.","schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string","format":"uri"}},"application/json":{"schema":{"$ref":"#/components/schemas/WriteResult"}}}},"400":{"description":"Not a postable page — code empty_body, invalid_utf8, or json_envelope (a JSON wrapper such as { \"html\": ... } was posted in place of the HTML itself).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Wrong method. Carries Allow: POST, OPTIONS and a hint stating the full POST contract.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Over a physical ceiling — code url_over_budget (encoded URL > 65000 bytes, where the edge severs the request line), content_too_large (decoded HTML > 163839 bytes, the server's decode cap), or body_too_large (request body > 262144 bytes). The 4000-byte share-safe budget never 413s — it warns on a 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over the published quota (code rate_limited). Carries Retry-After: 60, RateLimit-Policy, and RateLimit with r=0. Reading a page URL is not rate limited. Policy: https://anew.page/llms.txt","headers":{"RateLimit-Policy":{"description":"The enforced quota, draft-ietf-httpapi-ratelimit-headers-11 form: \"api\";q=300;w=60 — 300 requests per 60 seconds per client IP, shared across /write, /mcp and /a2a/v1.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait — always the full 60-second window, so one wait always clears the refusal.","schema":{"type":"integer"}},"RateLimit":{"description":"draft-ietf-httpapi-ratelimit-headers-11 quota state at the moment of refusal: r=0 (exact) and t = the full window (an upper bound on the wait).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/{slug}":{"get":{"operationId":"readPage","tags":["pages"],"summary":"Read a page","description":"GET an anew.page URL. Default `Accept` renders the page. `Accept: text/plain` returns the author's exact HTML — no anew-injected tags, no proxied image srcs — so it re-encodes byte-for-byte through `POST /write`. `Accept: text/markdown` (or a `.md` suffix) returns a lossy Markdown rendering of the built page, which cannot be re-encoded. Responses `Vary: Accept`. A `.png` suffix returns a 1200×630 screenshot of the rendered page — the page's own scripts do run, but the capture blocks network egress, so fetch, XHR and external scripts never resolve. `HEAD` answers like `GET` without the body — the cheap way to check that a slug decodes (200) or does not (404).","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"The encoded document — everything after the origin. Cosmetic path prefixes and multi-segment slugs also resolve, though the {slug} template cannot express them."},{"name":"Accept","in":"header","required":false,"schema":{"type":"string","enum":["text/html","text/plain","text/markdown"]},"description":"Representation to return."}],"responses":{"200":{"description":"The page, its source, or its Markdown rendering.","content":{"text/html":{"schema":{"type":"string"}},"text/plain":{"schema":{"type":"string"}},"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"The slug does not decode to a page. JSON for a client that asks for it, Markdown with where to look next for one that asks for text, an HTML page otherwise.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"text/markdown":{"schema":{"type":"string"}},"text/html":{"schema":{"type":"string"}}}},"451":{"description":"The page decodes, but it links to a host currently flagged as phishing or malware by a threat feed (code content_blocked). Neither representation names the host, the feed or an address. JSON for a client that asks for it, the bare status code as text otherwise.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}},"text/plain":{"schema":{"type":"string"}}}},"502":{"description":"The page decodes, but the Markdown conversion failed (code markdown_failed). Only the Markdown representation is affected — the same URL still serves HTML and, under Accept: text/plain, the author's source.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/mcp":{"post":{"operationId":"mcpRpc","tags":["mcp"],"summary":"MCP JSON-RPC endpoint","description":"Accepts MCP methods: initialize, server/discover, tools/list, tools/call, resources/list, resources/read, resources/templates/list, prompts/list, prompts/get, ping. Dual-era: 2025-11-25 initialize-handshake clients and stateless 2026-07-28 clients (version and capabilities in params._meta, mirrored Mcp-* headers) are both served. Tools: anew_write, anew_read.","parameters":[{"name":"MCP-Protocol-Version","in":"header","required":false,"schema":{"type":"string","enum":["2026-07-28","2025-11-25"]},"description":"Protocol revision. Omit it and the request is read as handshake-era; 2026-07-28 requires it, matching params._meta[\"io.modelcontextprotocol/protocolVersion\"]."},{"name":"Mcp-Method","in":"header","required":false,"schema":{"type":"string"},"description":"Mirrors the body's method. Required on 2026-07-28 requests."},{"name":"Mcp-Name","in":"header","required":false,"schema":{"type":"string"},"description":"Mirrors params.name (tools/call, prompts/get) or params.uri (resources/read). Required on 2026-07-28 requests for those methods."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"const":"2.0"},"id":{},"method":{"type":"string"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC response. Under 2025-11-25 a protocol error (-32601, -32602) is also a 200 carrying the error member; the 2026-07-28 era answers those with 400 and 404.","headers":{"RateLimit-Policy":{"description":"The enforced quota, draft-ietf-httpapi-ratelimit-headers-11 form: \"api\";q=300;w=60 — 300 requests per 60 seconds per client IP, shared across /write, /mcp and /a2a/v1.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"202":{"description":"Notification accepted (no body)."},"400":{"description":"Parse error (-32700), batch or malformed envelope (-32600), header/body mismatch (-32020), unsupported protocol version (-32022), or invalid params (-32602).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}},"403":{"description":"A browser Origin that is opaque, insecure, or non-canonical (code origin_forbidden): any canonically serialized https origin is accepted; loopback deployments accept only loopback origins. Clients that send no Origin are never refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A method the 2026-07-28 revision does not define (-32601).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}},"405":{"description":"Wrong method (code method_not_allowed). Carries Allow: POST, OPTIONS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body over 262144 bytes (code body_too_large).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over the published quota (code rate_limited). Carries Retry-After: 60, RateLimit-Policy, and RateLimit with r=0. Reading a page URL is not rate limited. Policy: https://anew.page/llms.txt","headers":{"RateLimit-Policy":{"description":"The enforced quota, draft-ietf-httpapi-ratelimit-headers-11 form: \"api\";q=300;w=60 — 300 requests per 60 seconds per client IP, shared across /write, /mcp and /a2a/v1.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait — always the full 60-second window, so one wait always clears the refusal.","schema":{"type":"integer"}},"RateLimit":{"description":"draft-ietf-httpapi-ratelimit-headers-11 quota state at the moment of refusal: r=0 (exact) and t = the full window (an upper bound on the wait).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/a2a/v1":{"post":{"operationId":"a2aRpc","tags":["a2a"],"summary":"A2A JSON-RPC endpoint","description":"Accepts A2A 1.0 JSON-RPC methods. SendMessage encodes the message into an anew.page URL — or, when the message carries a `url` DataPart and no `html` DataPart, or is one bare anew.page URL, decodes that page and returns its HTML source — and returns a terminal Task immediately; ListTasks always returns empty; GetTask/CancelTask return TaskNotFound. Streaming and push-notification methods are unsupported. See the Agent Card at /.well-known/agent-card.json for skill metadata.","parameters":[{"name":"A2A-Version","in":"header","required":true,"schema":{"const":"1.0"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","id","method"],"properties":{"jsonrpc":{"const":"2.0"},"id":{},"method":{"type":"string"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC response: a result, or an A2A error as the error member (-32009 version not supported, -32601, -32602, and the task errors).","headers":{"RateLimit-Policy":{"description":"The enforced quota, draft-ietf-httpapi-ratelimit-headers-11 form: \"api\";q=300;w=60 — 300 requests per 60 seconds per client IP, shared across /write, /mcp and /a2a/v1.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"400":{"description":"Malformed request: parse error, batch, or missing id (A2A defines no notifications).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcError"}}}},"405":{"description":"Wrong method (code method_not_allowed). Carries Allow: POST, OPTIONS.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body over 262144 bytes (code body_too_large).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over the published quota (code rate_limited). Carries Retry-After: 60, RateLimit-Policy, and RateLimit with r=0. Reading a page URL is not rate limited. Policy: https://anew.page/llms.txt","headers":{"RateLimit-Policy":{"description":"The enforced quota, draft-ietf-httpapi-ratelimit-headers-11 form: \"api\";q=300;w=60 — 300 requests per 60 seconds per client IP, shared across /write, /mcp and /a2a/v1.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait — always the full 60-second window, so one wait always clears the refusal.","schema":{"type":"integer"}},"RateLimit":{"description":"draft-ietf-httpapi-ratelimit-headers-11 quota state at the moment of refusal: r=0 (exact) and t = the full window (an upper bound on the wait).","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error"],"description":"The envelope every REST failure answers with: a machine-readable code, a human-readable message, a hint where one helps, details where there is a measurement.","properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["not_found","method_not_allowed","origin_forbidden","rate_limited","empty_body","invalid_utf8","json_envelope","url_over_budget","content_too_large","body_too_large","markdown_failed","content_blocked"],"description":"Which failure this is. Stable across releases; a caller may switch on it exhaustively."},"message":{"type":"string"},"hint":{"type":"string","description":"What to do instead."},"details":{"type":"object","description":"On url_over_budget and content_too_large: both physical ceilings (url_limit = the 65,000-byte request line, decoded_limit = the 163,839-byte decode cap) and the overage against the one that tripped — these fire only past the ceilings, never at the 4,000-byte share-safe budget, which warns on a 200 instead. On body_too_large: the body budget, since the encode never ran and there is no url/decoded measurement to report.","properties":{"url_bytes":{"type":"integer"},"url_limit":{"type":"integer"},"decoded_bytes":{"type":"integer"},"decoded_limit":{"type":"integer"},"body_bytes":{"type":"integer"},"body_limit":{"type":"integer"},"over":{"type":"integer"}}}}}}},"WriteResult":{"type":"object","required":["url","url_bytes","url_limit","decoded_bytes","decoded_limit"],"description":"A successful write, for callers that asked for application/json instead of the bare URL. The same numbers the Anew-* response headers carry.","properties":{"url":{"type":"string","format":"uri","description":"The complete shareable anew.page URL. Return it verbatim."},"url_bytes":{"type":"integer","description":"Encoded URL length in bytes."},"url_limit":{"type":"integer","description":"Unfurl-safe URL threshold (4000); headroom = limit - bytes. A threshold, not a wall — writes past it succeed with a warning, up to the 65000-byte serve ceiling."},"decoded_bytes":{"type":"integer","description":"The HTML as encoded, in UTF-8 bytes."},"decoded_limit":{"type":"integer","description":"Recommended decoded-byte budget (160000)."},"warning":{"type":"string","description":"Present when the page minted past a share-safe budget — URL over the share-safe budget (links may break over it in some apps) or content over the recommended byte budget. The URL works and serves either way. Relay it to the user with the URL."}}},"JsonRpcError":{"type":"object","required":["jsonrpc","error"],"description":"A JSON-RPC 2.0 error response: parse error -32700, invalid request -32600, method not found -32601, invalid params -32602, header mismatch -32020, unsupported MCP protocol version -32022, A2A version not supported -32009.","properties":{"jsonrpc":{"const":"2.0"},"id":{},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Per-error detail — -32022 carries { supported, requested }."}}}}},"JsonRpcResponse":{"type":"object","required":["jsonrpc"],"description":"A JSON-RPC 2.0 response: exactly one of result or error is present.","properties":{"jsonrpc":{"const":"2.0"},"id":{},"result":{"type":"object"},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"data":{"description":"Per-error detail — -32022 carries { supported, requested }."}}}}}}}}