anew Pages API

Write self-contained HTML pages, and read them back.

Operations 2

POST /write Write HTML to an anew.page URL #
GET /{slug} Read a page #

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-pages-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 form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

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

OpenAPI Specification

anew-pages-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: anew Pages API
  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.
  license:
    name: ISC
    identifier: ISC
servers:
- url: https://anew.page
security:
- {}
tags:
- name: Pages
  description: Write self-contained HTML pages, and read them back.
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'
components:
  schemas:
    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.
    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
externalDocs:
  url: https://anew.page/llms.txt
  description: 'Developer documentation: authentication, rate limits, versioning and deprecation policy, and every machine-readable resource.'