RUN IT YOURSELF

BENCH

The same content the pages render is served to machines seven ways: a REST API, an MCP server, an A2A agent, a SOAP service, a CLI, markdown mirrors, and an llms.txt index, and /developers describes all of them. A description is a claim. Here you can check it: pick a probe, press RUN, and read what this deployment actually answered — the status, the headers, the problem document when it fails. REST and its error catalogue, A2A, SOAP with a real WSDL, MCP over Streamable HTTP, and markdown negotiation, plus the handshakes real editors and agents send. Nothing on this page is a recording, and the path is editable, so the catalogue is a starting point rather than a fence.

Read-only, unauthenticated, 120 requests per 60s. Every response says how much of that window is left; you are unlikely to find the edge by hand.

Console

REST

ERRORS

A2A

SOAP

MCP

CLIENTS

MARKDOWN

REST

API INDEX

One call that hands an agent every other endpoint, the rate limit to throttle to, and where the machine description lives. Nothing else has to be guessed from a URL pattern.

The read-only JSON API. Every success is a { data, meta } envelope.

GET

Requests go to this origin. ↵ runs.

RESPONSE

Nothing has been run yet. Press RUN — the response below will be this deployment's, not a recording of one.

THE SAME CALL, IN A TERMINAL

curl -i \
  'https://zckr.dev/api/v1'

OR THROUGH THE CLI

zckr api

THE CATALOGUE

Every probe on the bench, with the request that reproduces it outside this page. Copy any line into a terminal and you get the same answer the console does.

REST

The read-only JSON API. Every success is a { data, meta } envelope.

  • GET/api/v1→ 200

    One call that hands an agent every other endpoint, the rate limit to throttle to, and where the machine description lives. Nothing else has to be guessed from a URL pattern.

    curl -i \
      'https://zckr.dev/api/v1'

    or: zckr api

  • GET/api/v1/projects?status=dead→ 200

    A filtered collection. meta.filters echoes what was applied, so a caller can tell an empty result from an ignored parameter.

    curl -i \
      'https://zckr.dev/api/v1/projects?status=dead'

    or: zckr projects --status=dead

  • GET/api/v1/projects/nexus→ 200

    The full lab notebook for one experiment — problem, hypothesis, architecture, measured metrics, learnings, and the cause of death.

    curl -i \
      'https://zckr.dev/api/v1/projects/nexus'

    or: zckr project nexus

  • GET/api/v1/notes/til-graph-highlight-without-javascript→ 200

    A note with its markdown body in the payload, so an agent needs one call rather than a fetch and a scrape.

    curl -i \
      'https://zckr.dev/api/v1/notes/til-graph-highlight-without-javascript'

    or: zckr note til-graph-highlight-without-javascript

  • GET/api/v1/agents→ 200

    The agent registry. Model tiers and tool grants here are read from the agent definition files themselves, so this endpoint cannot describe an agent the harness does not run.

    curl -i \
      'https://zckr.dev/api/v1/agents'

    or: zckr agents

  • GET/api/v1/mcp?origin=first-party→ 200

    The servers this origin serves, as opposed to the ones it consumes. A first-party row carries an endpoint and a tool list; a third-party row deliberately carries neither.

    curl -i \
      'https://zckr.dev/api/v1/mcp?origin=first-party'

    or: zckr mcp --origin=first-party

  • GET/api/v1/systems→ 200

    The infrastructure inventory. Every string served here passed a content-schema refusal of addresses, hostnames, ports and credentials — the guarantee is enforced at build, not promised at read.

    curl -i \
      'https://zckr.dev/api/v1/systems'

    or: zckr systems

  • GET/api/v1/graph→ 200

    Every relationship in the lab as one node/edge document, keyed kind:slug. Returned whole rather than paginated, because a graph split across pages is not a graph.

    curl -i \
      'https://zckr.dev/api/v1/graph'

    or: zckr graph

  • GET/api/v1/currents/2026-08→ 200

    An archived monthly snapshot. Immutable by design — an old one is honestly old rather than quietly stale.

    curl -i \
      'https://zckr.dev/api/v1/currents/2026-08'

    or: zckr currents --month=2026-08

  • GET/api/v1/version→ 200

    The lifecycle policy as data: release date, deprecation and sunset dates when they exist, and the minimum notice before a version is switched off.

    curl -i \
      'https://zckr.dev/api/v1/version'

    or: zckr version

ERRORS

The failure catalogue, fired on purpose. Every one is an RFC 9457 problem document with a code to branch on and a resolution that names the fix.

  • GET/api/v1/projects/perpetual-motion→ 404 not_found

    404 as a problem document. The resolution lists every valid slug, so a second guess is unnecessary — the error is the documentation.

    curl -i \
      'https://zckr.dev/api/v1/projects/perpetual-motion'

    or: zckr project perpetual-motion

  • GET/api/v1/projects?status=alive→ 400 invalid_parameter

    A parameter outside its allowed values. The resolution names what would have been accepted rather than pointing at the spec.

    curl -i \
      'https://zckr.dev/api/v1/projects?status=alive'

    or: zckr projects --status=alive

  • GET/api/v1/search?q=→ 400 invalid_parameter

    A required parameter left empty. Same shape, same code, a different resolution — and an example call to copy.

    curl -i \
      'https://zckr.dev/api/v1/search?q='
  • POST/api/v1/projects→ 405 method_not_allowed

    Nothing on zckr.dev accepts writes. Any write verb answers 405 in JSON with an Allow header — never an empty body, never an HTML error page.

    curl -i \
      -X POST \
      'https://zckr.dev/api/v1/projects'
  • GET/api/v1→ 406 not_acceptable

    An Accept header that excludes JSON. The problem document is returned anyway, in JSON, which is what RFC 9457 intends: a client that cannot take the answer still gets told why.

    curl -i \
      -H 'Accept: text/csv' \
      'https://zckr.dev/api/v1'
  • GET/api/v1/telemetry→ 404 not_found

    A path that was never a route. The whole /api namespace answers in JSON, including for endpoints that do not exist — a catch-all, not a gap.

    curl -i \
      'https://zckr.dev/api/v1/telemetry'

A2A

The Agent2Agent surface. The caller is another agent: it reads the card, asks in a sentence, and gets a Task back with the answer attached as artifacts.

  • GET/.well-known/agent-card.json→ 200

    A2A's entire discovery story is this one document: a client fetches it, reads the transport and the skills, and knows how to talk to the agent without any further negotiation.

    curl -i \
      'https://zckr.dev/.well-known/agent-card.json'
  • POST/api/a2a→ 200

    A sentence in, a completed Task out, with the answer as artifacts — prose to relay and the same facts as data. The cause of death comes back cited.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      'https://zckr.dev/api/a2a' \
      -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"role":"user","kind":"message","messageId":"bench-1","parts":[{"kind":"text","text":"What killed Nexus?"}]}}}'
  • POST/api/a2a→ 200

    The same endpoint, a different intent. A weighted keyword router picks the skill — no inference budget on a static site, and a router that guessed would be worse than one that cannot.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      'https://zckr.dev/api/a2a' \
      -d '{"jsonrpc":"2.0","id":2,"method":"message/send","params":{"message":{"role":"user","kind":"message","messageId":"bench-2","parts":[{"kind":"text","text":"list the dead projects"}]}}}'
  • POST/api/a2a→ 200

    -32001 by design. This agent is stateless: a task is created, completed and returned inside the one message/send that made it, and the error says so rather than implying the task was lost.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      'https://zckr.dev/api/a2a' \
      -d '{"jsonrpc":"2.0","id":3,"method":"tasks/get","params":{"id":"task-00000000"}}'
  • POST/api/a2a→ 200

    -32009. A2A has two live wire shapes; this server implements 0.3 and refuses to answer a 1.0 request with 0.3 shapes, which would be a lie told in valid JSON.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'A2A-Version: 1.0' \
      'https://zckr.dev/api/a2a' \
      -d '{"jsonrpc":"2.0","id":4,"method":"message/send","params":{"message":{"role":"user","kind":"message","messageId":"bench-4","parts":[{"kind":"text","text":"hello"}]}}}'

SOAP

The enterprise surface. A WSDL 1.1 contract, document/literal, both a 1.1 and a 1.2 binding, and faults that carry the same code and resolution the REST problem documents do.

  • GET/api/soap?wsdl→ 200

    The contract. Generated from the same operation table the dispatcher reads, so it cannot describe an operation the endpoint does not answer — and it declares both a 1.1 and a 1.2 port because both really work.

    curl -i \
      'https://zckr.dev/api/soap?wsdl'
  • POST/api/soap→ 200

    SOAP 1.1: text/xml, a quoted SOAPAction header, and an envelope in the 2001 namespace. The same records the REST collection returns, wearing a different coat.

    curl -i \
      -X POST \
      -H 'Content-Type: text/xml; charset=utf-8' \
      -H 'SOAPAction: "https://zckr.dev/soap/lab/v1/ListProjects"' \
      'https://zckr.dev/api/soap' \
      -d '<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="https://zckr.dev/soap/lab/v1"><soap:Body><tns:ListProjects><tns:status>dead</tns:status></tns:ListProjects></soap:Body></soap:Envelope>'
  • POST/api/soap→ 400

    1.2 replaces faultcode/faultstring with Code/Value and Reason/Text, and answers 400 rather than 1.1's 500. Serving the wrong shape reads as a transport failure inside a generated stub instead of the refusal it is — so both are built properly.

    curl -i \
      -X POST \
      -H 'Content-Type: application/soap+xml; charset=utf-8' \
      'https://zckr.dev/api/soap' \
      -d '<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tns="https://zckr.dev/soap/lab/v1"><soap:Body><tns:GetProject><tns:slug>perpetual-motion</tns:slug></tns:GetProject></soap:Body></soap:Envelope>'
  • POST/api/soap→ 500

    A SOAP Fault carrying the same code and resolution an RFC 9457 problem document would. Four protocols, one contract about what a failure has to tell you.

    curl -i \
      -X POST \
      -H 'Content-Type: text/xml; charset=utf-8' \
      'https://zckr.dev/api/soap' \
      -d '<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="https://zckr.dev/soap/lab/v1"><soap:Body><tns:ListProjects><tns:status>alive</tns:status></tns:ListProjects></soap:Body></soap:Envelope>'

MCP

The Model Context Protocol server, over Streamable HTTP. Raw JSON-RPC, no client library in between.

  • POST/api/mcp→ 200

    The handshake. Stateless: there is no session to establish, so the same server answers the next call just as well.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"zckr-bench","version":"1"}}}'
  • POST/api/mcp→ 200

    Every lab tool with its description. The descriptions are the prompt an agent actually reads, which is why they are written as instructions rather than labels.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 200

    Calling lab_search the way a model would. The same search the REST endpoint runs, returned as tool content.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"lab_search","arguments":{"query":"mcp"}}}'

CLIENTS

The handshakes real tools send, fired one at a time. This is the half of "will my client connect" that can be answered with a request rather than a promise — the config file is the client's business, the wire is this server's.

  • POST/api/mcp→ 200

    What the Streamable HTTP spec asks a client to send, and what a well-behaved client does send. Answered with JSON, because JSON is the representation no client has ever failed to parse.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":40,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 200

    What a great many HTTP clients send by default. The SDK alone answers 406 to this; the shim in lib/mcp-transport.ts is why it works here.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: */*' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":41,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 200

    A client that never thought about content negotiation. Also answered, for the same reason.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":42,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 200

    A client that cannot read an event stream. Gets JSON rather than a refusal.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":43,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 200

    A client that names the stream alone gets the stream. Both representations are permitted for a single request/response.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: text/event-stream' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":44,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 200

    Matches text/event-stream under RFC 9110 without naming it, and was answered 406 until the admission test stopped requiring the subtype to be spelled out. It gets the stream, because that is the only representation it can actually take.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: text/*' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":45,"method":"tools/list","params":{}}'
  • POST/api/mcp→ 406

    A client that accepts neither JSON nor the stream is told 406, because there is nothing honest left to send it.

    curl -i \
      -X POST \
      -H 'Content-Type: application/json' \
      -H 'Accept: text/plain' \
      'https://zckr.dev/api/mcp' \
      -d '{"jsonrpc":"2.0","id":46,"method":"tools/list","params":{}}'

MARKDOWN

Content negotiation. The same pages, as markdown, chosen by an Accept header rather than a different URL.

  • GET/stack→ 200

    The canonical URL, negotiated. Same address a browser uses, different representation — and the response says Vary: Accept so a cache keeps them apart.

    curl -i \
      -H 'Accept: text/markdown' \
      'https://zckr.dev/stack'
  • GET/stack.md→ 200

    The same mirror for clients that cannot set headers. Append .md to any page path.

    curl -i \
      'https://zckr.dev/stack.md'
  • GET/perpetual-motion.md→ 404

    An unknown path still answers in markdown, with recovery links, rather than dropping an agent onto an HTML error page it has to parse.

    curl -i \
      'https://zckr.dev/perpetual-motion.md'

CONNECT A TOOL

No account, no key, no allow-list. Paste one of these where your tool keeps its config and it can read the lab. Whether it will connect is a separate question from whether this snippet is spelled the way your vendor spells it this month — the CLIENTS probes above answer the first by firing it; the link on each entry is the authority for the second.

upstream
Read from the client's own current documentation.
convention
The shape this client's ecosystem uses. Its own documentation is the authority — the link goes there.

MCP

  • Claude Code

    upstreamits docs ↗

    Anthropic's terminal agent. One command, no file to edit.

    or .mcp.json at the project root

    claude mcp add --transport http zckr https://zckr.dev/api/mcp

    CONNECTS TO https://zckr.dev/api/mcp

  • Claude Code (.mcp.json)

    upstreamits docs ↗

    The project-scoped file the command above writes. Commit it and everyone on the repo gets the server.

    .mcp.json

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

    CONNECTS TO https://zckr.dev/api/mcp

  • GitHub Copilot (VS Code)

    upstreamits docs ↗

    Agent mode in VS Code. Note the key is `servers`, not `mcpServers` — the one difference that catches people moving a config across.

    .vscode/mcp.json

    {
      "servers": {
        "zckr": {
          "type": "http",
          "url": "https://zckr.dev/api/mcp"
        }
      }
    }

    CONNECTS TO https://zckr.dev/api/mcp

  • Cursor

    upstreamits docs ↗

    Remote servers are a url entry; no command, no transport field.

    ~/.cursor/mcp.json, or .cursor/mcp.json per project

    {
      "mcpServers": {
        "zckr": {
          "url": "https://zckr.dev/api/mcp"
        }
      }
    }

    CONNECTS TO https://zckr.dev/api/mcp

  • Gemini CLI

    upstreamits docs ↗

    Uses `httpUrl` for Streamable HTTP and `url` for the legacy SSE transport — the wrong one of the two fails quietly.

    ~/.gemini/settings.json, or .gemini/settings.json per project

    {
      "mcpServers": {
        "zckr": {
          "httpUrl": "https://zckr.dev/api/mcp"
        }
      }
    }

    CONNECTS TO https://zckr.dev/api/mcp

  • Windsurf

    conventionits docs ↗

    Cascade's MCP config. Historically `serverUrl` rather than `url`.

    ~/.codeium/windsurf/mcp_config.json

    {
      "mcpServers": {
        "zckr": {
          "serverUrl": "https://zckr.dev/api/mcp"
        }
      }
    }

    CONNECTS TO https://zckr.dev/api/mcp

  • Cline

    conventionits docs ↗

    The VS Code extension agent; names the transport explicitly.

    cline_mcp_settings.json

    {
      "mcpServers": {
        "zckr": {
          "type": "streamableHttp",
          "url": "https://zckr.dev/api/mcp"
        }
      }
    }

    CONNECTS TO https://zckr.dev/api/mcp

  • ChatGPT / OpenAI Responses API

    conventionits docs ↗

    A remote MCP server as a tool on a model call, rather than a file on your machine.

    {
      "type": "mcp",
      "server_label": "zckr",
      "server_url": "https://zckr.dev/api/mcp",
      "require_approval": "never"
    }

    CONNECTS TO https://zckr.dev/api/mcp

  • Anything else that speaks MCP

    upstreamits docs ↗

    There is no allow-list and no key. If your client can POST JSON-RPC to a URL, it can read this lab.

    curl -s -X POST https://zckr.dev/api/mcp \
      -H 'content-type: application/json' \
      -H 'accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

    CONNECTS TO https://zckr.dev/api/mcp

A2A

  • Any A2A agent

    upstreamits docs ↗

    Fetch the card, read the skills, then ask in a sentence. No client library required — it is JSON-RPC over POST.

    curl -s https://zckr.dev/.well-known/agent-card.json
    
    curl -s -X POST https://zckr.dev/api/a2a \
      -H 'content-type: application/json' \
      -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"role":"user","kind":"message","messageId":"1","parts":[{"kind":"text","text":"What killed Nexus?"}]}}}'

    CONNECTS TO https://zckr.dev/api/a2a

REST

  • The zckr CLI

    upstreamits docs ↗

    A dependency-free Node client for the REST API. Anything it can do, an integration can do — the commands are the endpoints.

    npx @zckr/cli projects --status=dead
    npm install -g @zckr/cli
    zckr search "tool description"

    CONNECTS TO https://zckr.dev/api/v1

  • Any HTTP client

    upstreamits docs ↗

    The REST API is the lowest common denominator: no auth, typed JSON, an OpenAPI 3.1 description for code generation.

    curl -s 'https://zckr.dev/api/v1/projects?status=dead'
    
    # generate a client from the description
    curl -s https://zckr.dev/openapi.json -o zckr.openapi.json

    CONNECTS TO https://zckr.dev/api/v1

SOAP

  • A generated SOAP stub

    upstreamits docs ↗

    Point any WSDL toolchain at the contract and let it write the client. Both a 1.1 and a 1.2 port are published.

    # Java
    wsimport -keep https://zckr.dev/api/soap?wsdl
    
    # .NET
    dotnet-svcutil https://zckr.dev/api/soap?wsdl
    
    # Python (zeep)
    python -c "from zeep import Client; c = Client('https://zckr.dev/api/soap?wsdl'); print(c.service.ListProjects(status='dead'))"

    CONNECTS TO https://zckr.dev/api/soap

WHAT IS NOT HERE

The REST API publishes six error codes and the bench fires four of them one button at a time. These two it does not, and a button that faked them would be the only dishonest thing on a page whose entire argument is that nothing here is simulated.

RATE LIMITED
It would take 120 requests in 60s to earn one, and spending the window to watch it close is a poor trade. Every response below carries the same live counter a 429 would — watch RateLimit fall as you run probes.
INTERNAL ERROR
Nothing behind this API talks to a network or a database, so an unexpected throw is a bug in the repo rather than a state to demonstrate. If you ever see one, the instance value in the document is what to report.

The full reference — every endpoint, the error catalogue, the rate limit policy, the version lifecycle, the MCP tool list and the CLI — is at /developers. The machine description is at /openapi.json, and the index every probe here starts from is /api/v1.