Every way to read this site as a machine: Markdown, JSON, and MCP. No key, no rate limit.
Everything on this site is readable by machines as well as people. Nothing here needs a key, an account, or a quota, and none of it is rate limited. If you are an agent, start at llms.txt: it says in one page what this site is a good source for and what it is not.
Both representations carry Vary: Accept, Accept-Encoding, so a shared cache will not hand you the wrong one. Links and images inside the Markdown are absolute, so you can fetch a document on its own and still resolve everything in it.
For the whole site in one request, llms-full.txt concatenates every page and post.
Versioned under /api/v1/. Read-only, no authentication.
# Every post, newest firstcurl https://www.magnusrodseth.com/api/v1/posts# One post, including its Markdown bodycurl https://www.magnusrodseth.com/api/v1/posts/model-context-protocol
GET /api/v1/posts answers with a count and an array; each entry carries the slug, title, description, date, language, reading time, and links to the HTML, Markdown and JSON views. Where a post first ran somewhere else, originalPublication names the publication and links to the original.
GET /api/v1/posts/{slug} adds content, the post's Markdown source.
openapi.json is the machine-readable version of this page: every operation has a unique operationId, typed parameters, and a response schema, so it drops straight into a function-calling setup.
Failures come back as JSON with the same shape every time. Branch on error.code, which is stable; error.message is written for people and may be reworded.
{ "error": { "code": "not_found", "message": "No post with the slug \"nope\".", "status": 404, "resolution": "List every slug at https://www.magnusrodseth.com/api/v1/posts.", "documentation": "https://www.magnusrodseth.com/openapi.json" }}
The codes are not_found, method_not_allowed, upstream_unavailable and internal_error. A URL under /api/ that matches nothing returns this shape rather than an HTML error page.
Inside a version, changes are additive only: new fields may appear, and existing fields keep their name, type and meaning. A breaking change gets a new prefix, /api/v2/. A retired version keeps answering for at least six months and sends Deprecation and Sunset headers on every response for that whole window, so you find out from the response rather than from a broken integration.
Every post, newest first, no bodies. Filter by language, cap with limit
search_posts
Substring search over titles, descriptions and bodies, with the surrounding sentence
get_post
One post by slug, with its full Markdown body
get_page
Home, blog index, projects, daily drivers, about, contact or privacy, as Markdown
The descriptor lives at /.well-known/mcp. A bad argument comes back as isError: true inside the result, with a message saying what to do instead, rather than as a protocol-level error.
Every response also carries a Link header pointing at the api-catalog, the agent-skills index, the MCP descriptor and the sitemap, so one HEAD request is enough to find the rest.
There is no rate limit, so there are no RateLimit headers to read. Please still be reasonable: everything here is static and cached, and a sensible User-Agent that says who you are is appreciated. If something is missing or wrong, tell me.