Quickstart

List the posts, then read one by slug:

curl https://mashprime.dev/api/v1/posts
curl https://mashprime.dev/api/v1/posts/one-folder-to-autonomy
curl "https://mashprime.dev/api/v1/search?q=memory"

Responses are JSON. Every endpoint, parameter and response schema is described in the OpenAPI 3.1 spec, which you can load straight into an LLM tool-calling layer: every operation has a unique operationId, a description and typed schemas.

Authentication

None. The API is public and read-only, so there are no API keys, tokens or accounts. Send requests from anywhere; CORS is open (Access-Control-Allow-Origin: *).

Endpoints

Base URL: https://mashprime.dev/api/v1

MethodPathoperationIdWhat it does
GET /api/v1 getApiIndex API index: version, documentation links, and available endpoints
GET /api/v1/site getSiteInfo Who Mash Prime is, what the site covers, and where the machine-readable resources live
GET /api/v1/posts listPosts List published blog posts, newest first
GET /api/v1/posts/{slug} getPost Read one blog post, including its full markdown text
GET /api/v1/search searchPosts Search blog posts by keyword across titles, descriptions, and full text

posts pages with optional limit (1–100) and offset, and reports total. search takes a required q (1–200 characters) and an optional limit, and ranks posts by BM25 over the site's passage index (best match first). posts/{slug} returns the post's metadata plus its full text in markdown.

The passage index behind search is a static file too: /search/index.json holds every post split into heading-level passages (with the anchor of each section) plus one 384-dimension int8 embedding per post, which powers related posts. The site's own search box ranks it in your browser; no query is sent anywhere.

Errors

Every 4xx and 5xx response is an RFC 9457 application/problem+json object. Branch on the stable code; show detail to humans; follow hint to recover.

{
  "type": "https://mashprime.dev/developers/#error-post_not_found",
  "title": "Post not found",
  "status": 404,
  "detail": "No published post has the slug 'no-such-post'.",
  "instance": "/api/v1/posts/no-such-post",
  "code": "post_not_found",
  "hint": "Get valid slugs from GET /api/v1/posts.",
  "documentation": "https://mashprime.dev/developers/#errors"
}
StatuscodeMeaning and fix
400 invalid_slug Invalid post slug. Slugs are lowercase letters, digits, and hyphens. Get valid slugs from GET /api/v1/posts.
400 missing_query Missing search query. Pass a keyword in the q parameter, e.g. GET /api/v1/search?q=memory.
400 invalid_parameter Invalid query parameter. limit must be a whole number from 1 to 100; offset must be a whole number of 0 or more.
400 query_too_long Search query too long. Keep q to 200 characters or fewer.
404 not_found Resource not found. See GET /api/v1 for the available endpoints.
404 post_not_found Post not found. Get valid slugs from GET /api/v1/posts.
404 unsupported_version Unsupported API version. Supported versions: v1. Use paths under /api/v1/.
405 method_not_allowed Method not allowed. This API is read-only. Use GET (or HEAD / OPTIONS).
429 rate_limited Rate limit exceeded. Wait for the number of seconds in Retry-After, then retry. Watch the RateLimit header to stay under the limit.
500 internal_error Internal error. Retry with exponential backoff. If it persists, report it via https://mashprime.dev/contact/.
502 upstream_unavailable Content source unavailable. The site's content index could not be read. Retry with exponential backoff.

Rate limits

60 requests per 60 seconds per client IP. Every response, including errors, tells you where you stand:

RateLimit-Policy: "default";q=60;w=60
RateLimit: "default";r=57;t=42
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1790467242

RateLimit and RateLimit-Policy follow the IETF RateLimit header fields draft: r is requests remaining, t is seconds until the window resets. The X-RateLimit-* headers carry the same numbers, with X-RateLimit-Reset as Unix epoch seconds. Over the limit you get a 429 with code: "rate_limited" and a Retry-After header in seconds. Throttle when r gets low rather than waiting for the 429.

Versioning and deprecation

Currently deprecated: the unversioned /api/posts.json build index. It still works and has no sunset date yet; it sends Deprecation and a successor link to /api/v1/posts.

MCP server

A remote Model Context Protocol server gives Claude, ChatGPT and other agents the site as native tools. Transport: streamable HTTP (stateless). No auth.

Claude Code:

claude mcp add --transport http mashprime https://mashprime.dev/mcp

Clients that take a JSON config:

{
  "mcpServers": {
    "mashprime": {
      "type": "http",
      "url": "https://mashprime.dev/mcp"
    }
  }
}

WebMCP

The pages are tool servers too. WebMCP is a W3C Web Machine Learning Community Group draft, started by Microsoft and Google, that lets a web page hand JavaScript tools to an AI agent running in the browser. When the browser exposes document.modelContext, every page here registers the tools below. Without it, the tool code isn't even downloaded. The site-wide read tools have the same names and schemas as the MCP server. The rest act on the open tab.

ToolRegistered onRead-onlyWhat it does
site_info Every page Yes Orientation: who Mash Prime is, what the site covers, when to use it, and every page — returned as markdown (the site's llms.txt).
list_posts Every page Yes List all published blog posts with slug, title, description, and publication date, newest first.
read_post Every page Yes Read the full markdown text of one blog post by slug (get slugs from list_posts).
search_posts Every page Yes Search blog posts across titles, descriptions, and full text, ranked best match first. Returns matching posts with the most relevant passage as a snippet.
navigate Every page No Navigate this browser tab to another page on mashprime.dev. Takes a same-origin path such as '/blog/one-folder-to-autonomy/' or '/developers/#mcp'; absolute and cross-origin URLs are rejected. This page's tools are replaced by the new page's tools once it loads.
set_theme Every page No Switch the site between light and dark colour themes. Same as the header toggle: applies immediately and is remembered for future visits on this device.
get_current_post Blog posts Yes Describe the blog post open in this tab: slug, title, URL, markdown URL, and its section headings with anchors (use an anchor with scroll_to_section).
scroll_to_section Blog posts No Scroll the open blog post to a section heading so the reader can see it. Takes an anchor from get_current_post, e.g. '#memory' or 'memory'.

Read-only tools carry the readOnlyHint annotation. Results are MCP-style text content ({ content: [{ type: "text", text }] }). Bad input gets isError: true and a message that says what to fix. Every argument is checked against its schema. Tools only fetch this site's public files from the same origin, and only change local UI state: the page, the scroll position, the theme.

Try it

When tools are live, the footer shows how many the page registered.

Implemented against WebMCP, Draft Community Group Report, 8 October 2026 (spec commit 4999377). It uses the imperative registerTool() API only: that revision dropped the declarative form API, and the spec defines no discovery file.

CLI

mashprime is a zero-dependency Node CLI that reads the site from a terminal: mashprime posts, mashprime read <slug>, mashprime search <query>, mashprime mcp. It is built on this API and published on npm as mashprime (Node 18+). No install needed:

npx mashprime
npx mashprime read one-folder-to-autonomy

Or install it: npm install -g mashprime. The in-browser terminal runs the same commands.

Machine-readable files

FileWhat it is
/openapi.jsonOpenAPI 3.1 description of the Mash Prime API
/.well-known/api-catalogRFC 9727 API catalog (linkset)
/.well-known/mcp.jsonMCP server card
/llms.txtAgent-readable site index
/rss.xmlRSS feed of posts
/sitemap-index.xmlSitemap

Every HTML page also has a markdown variant: send Accept: text/markdown. This page's is /developers/index.md.

Sandbox

The API is read-only, so production is the sandbox: nothing you send can change anything. Try a request (it counts toward your rate limit).

GET /api/v1

Response status, rate-limit headers and body appear below.

// response will appear here