|
| 1 | +# RundownAPI |
| 2 | + |
| 3 | +An endpoint that teaches AI how to use your API. No SDK. It just reads and connects. |
| 4 | + |
| 5 | +## What this is |
| 6 | + |
| 7 | +A pattern for making your API self-describing to AI agents. You add one endpoint — `/api/rundown` — that returns everything an AI needs to integrate: what the API does, how to authenticate, what endpoints exist, and when to use them. |
| 8 | + |
| 9 | +The AI reads that endpoint, understands the API, and starts working with it. No pre-built connectors, no MCP servers, no SDK to install. One URL, and the AI figures out the rest. |
| 10 | + |
| 11 | +I built this for Threadbaire Server, tested it with Claude Code. Gave it the URL, gave it a token, and it built its own integration and started querying project history without any other setup. |
| 12 | + |
| 13 | +This document describes the pattern so you can add it to your own APIs. |
| 14 | + |
| 15 | +## Why bother |
| 16 | + |
| 17 | +Right now, if you want an AI agent to use your API, you have a few options: |
| 18 | + |
| 19 | +**MCP servers** — Anthropic's protocol, now adopted by others. Works great, but you need to run a server. Infrastructure to maintain. |
| 20 | + |
| 21 | +**Agent frameworks** — LangChain, Haystack, Google ADK. They can auto-generate tools from OpenAPI specs. But you need Python or Node wrapping your spec, and the agent needs to be built for that framework. |
| 22 | + |
| 23 | +**Custom integration** — You write the connector yourself, or the AI writes it for you every time. Works, but repetitive. |
| 24 | + |
| 25 | +**OpenAPI specs** — Good for documenting structure. But they tell you *how* to call an endpoint, not *when* or *why*. The AI knows the shape of the API but not when to use it. |
| 26 | + |
| 27 | +RundownAPI adds the missing piece: behavioral instructions. Not just "here are the endpoints" but "here's when you should query this, here's what to offer the user, here's how to behave." |
| 28 | + |
| 29 | +The AI reads one URL and knows what to do. No setup on the AI side, no infrastructure on your side beyond the endpoint itself. |
| 30 | + |
| 31 | +## The spec |
| 32 | + |
| 33 | +A RundownAPI endpoint returns JSON with these fields: |
| 34 | + |
| 35 | +### Required |
| 36 | + |
| 37 | +| Field | What it is | |
| 38 | +|-------|-----------| |
| 39 | +| `rundown_version` | Version of the spec (currently `"0.1"`) | |
| 40 | +| `base_url` | Where the API lives | |
| 41 | +| `auth` | How to authenticate (method, parameter names, header format) | |
| 42 | +| `purpose` | What this API does, in plain language. One or two sentences. | |
| 43 | +| `endpoints` | Array of available endpoints — path, method, description, parameters | |
| 44 | +| `ai_instructions` | When to use this API, how to behave, what to offer the user | |
| 45 | + |
| 46 | +### Recommended |
| 47 | + |
| 48 | +| Field | What it is | |
| 49 | +|-------|-----------| |
| 50 | +| `examples` | Curl or fetch snippets the AI can use immediately | |
| 51 | +| `error_format` | What errors look like (so the AI can handle them) | |
| 52 | +| `capabilities` | Quick summary of what's possible: `["read", "write", "search"]` | |
| 53 | + |
| 54 | +### Optional |
| 55 | + |
| 56 | +Everything else — rate limits, response schemas, MCP hints, whatever's useful for your case. |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +### Full example (from Threadbaire Server) |
| 61 | + |
| 62 | +This is the actual endpoint that worked with Claude Code: |
| 63 | + |
| 64 | +```json |
| 65 | +{ |
| 66 | + "rundown_version": "0.1", |
| 67 | + "base_url": "https://your-server.vercel.app", |
| 68 | + "purpose": "Project decision log and context memory. Store and retrieve dated entries about what you've done, decided, and learned across projects.", |
| 69 | + |
| 70 | + "auth": { |
| 71 | + "method": "token", |
| 72 | + "parameter": "token", |
| 73 | + "header_alternative": "Authorization: Bearer <token>", |
| 74 | + "note": "Most AI chat interfaces can't set headers. Use ?token= in the URL." |
| 75 | + }, |
| 76 | + |
| 77 | + "capabilities": ["read", "write", "search"], |
| 78 | + |
| 79 | + "endpoints": [ |
| 80 | + { |
| 81 | + "path": "/api/entries", |
| 82 | + "method": "GET", |
| 83 | + "description": "List entries with optional filters", |
| 84 | + "parameters": { |
| 85 | + "project": "Filter by project name", |
| 86 | + "document_type": "Filter by type: 'addendum' or 'dev_log'", |
| 87 | + "after": "Entries on or after this date (YYYY-MM-DD)", |
| 88 | + "before": "Entries on or before this date (YYYY-MM-DD)", |
| 89 | + "q": "Keyword search across title, summary, details", |
| 90 | + "limit": "Max entries to return (default 20)", |
| 91 | + "offset": "Skip this many entries (for pagination)" |
| 92 | + } |
| 93 | + }, |
| 94 | + { |
| 95 | + "path": "/api/entries", |
| 96 | + "method": "POST", |
| 97 | + "description": "Create a new entry", |
| 98 | + "body": { |
| 99 | + "project": "(required) Project name", |
| 100 | + "document_type": "(required) 'addendum' or 'dev_log'", |
| 101 | + "entry_date": "(required) Date in YYYY-MM-DD format", |
| 102 | + "title": "(required) Short title for the entry", |
| 103 | + "entry_type": "Category like 'Feature', 'Decision', 'Fix'", |
| 104 | + "status": "Status like 'complete', 'in_progress', 'blocked'", |
| 105 | + "summary": "Brief description", |
| 106 | + "details": "Full details (markdown supported)", |
| 107 | + "next_steps": "What follows from this" |
| 108 | + } |
| 109 | + }, |
| 110 | + { |
| 111 | + "path": "/api/entries/:id", |
| 112 | + "method": "GET", |
| 113 | + "description": "Get a single entry by ID" |
| 114 | + }, |
| 115 | + { |
| 116 | + "path": "/api/entries/:id", |
| 117 | + "method": "PUT", |
| 118 | + "description": "Update an existing entry" |
| 119 | + }, |
| 120 | + { |
| 121 | + "path": "/api/entries/:id", |
| 122 | + "method": "DELETE", |
| 123 | + "description": "Soft delete an entry" |
| 124 | + } |
| 125 | + ], |
| 126 | + |
| 127 | + "ai_instructions": { |
| 128 | + "triggers": [ |
| 129 | + "User asks about project history or past decisions", |
| 130 | + "User asks what was done on a specific date", |
| 131 | + "User wants to know why something was decided", |
| 132 | + "User completes work and might want to log it" |
| 133 | + ], |
| 134 | + "behaviors": [ |
| 135 | + "Query entries when user asks about history — don't guess from memory", |
| 136 | + "Offer to create entries when user completes significant work", |
| 137 | + "Summarize results in natural language, don't dump raw JSON", |
| 138 | + "Use keyword search (q parameter) for finding specific topics" |
| 139 | + ], |
| 140 | + "constraints": [ |
| 141 | + "Ask user for API token if not provided", |
| 142 | + "Never fabricate entries or dates", |
| 143 | + "Don't modify or delete entries without explicit user request" |
| 144 | + ] |
| 145 | + }, |
| 146 | + |
| 147 | + "examples": [ |
| 148 | + { |
| 149 | + "description": "Get recent entries for a project", |
| 150 | + "request": "curl 'https://your-server.vercel.app/api/entries?project=myproject&limit=10&token=YOUR_TOKEN'" |
| 151 | + }, |
| 152 | + { |
| 153 | + "description": "Search for entries about authentication", |
| 154 | + "request": "curl 'https://your-server.vercel.app/api/entries?q=authentication&token=YOUR_TOKEN'" |
| 155 | + }, |
| 156 | + { |
| 157 | + "description": "Create a new entry", |
| 158 | + "request": "curl -X POST 'https://your-server.vercel.app/api/entries?token=YOUR_TOKEN' -H 'Content-Type: application/json' -d '{\"project\": \"myproject\", \"document_type\": \"addendum\", \"entry_date\": \"2025-01-19\", \"title\": \"Added user login\", \"status\": \"complete\", \"summary\": \"Implemented OAuth flow\"}'" |
| 159 | + } |
| 160 | + ], |
| 161 | + |
| 162 | + "error_format": { |
| 163 | + "structure": "{ \"error\": \"message\" }", |
| 164 | + "codes": { |
| 165 | + "401": "Missing or invalid token", |
| 166 | + "400": "Missing required fields", |
| 167 | + "404": "Entry not found" |
| 168 | + } |
| 169 | + } |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +### Minimal example (read-only API) |
| 176 | + |
| 177 | +Not every API needs all of this. Here's a simpler version for a read-only service: |
| 178 | + |
| 179 | +```json |
| 180 | +{ |
| 181 | + "rundown_version": "0.1", |
| 182 | + "base_url": "https://api.example.com", |
| 183 | + "purpose": "Weather data for any city. Returns current conditions and 5-day forecast.", |
| 184 | + |
| 185 | + "auth": { |
| 186 | + "method": "api_key", |
| 187 | + "header": "X-API-Key" |
| 188 | + }, |
| 189 | + |
| 190 | + "capabilities": ["read"], |
| 191 | + |
| 192 | + "endpoints": [ |
| 193 | + { |
| 194 | + "path": "/weather/:city", |
| 195 | + "method": "GET", |
| 196 | + "description": "Get current weather and forecast", |
| 197 | + "parameters": { |
| 198 | + "units": "'metric' or 'imperial' (default: metric)" |
| 199 | + } |
| 200 | + } |
| 201 | + ], |
| 202 | + |
| 203 | + "ai_instructions": { |
| 204 | + "triggers": [ |
| 205 | + "User asks about weather", |
| 206 | + "User is planning travel or outdoor activities" |
| 207 | + ], |
| 208 | + "behaviors": [ |
| 209 | + "Include both current conditions and relevant forecast days", |
| 210 | + "Mention precipitation probability if above 30%" |
| 211 | + ] |
| 212 | + }, |
| 213 | + |
| 214 | + "examples": [ |
| 215 | + { |
| 216 | + "description": "Get weather for London", |
| 217 | + "request": "curl -H 'X-API-Key: KEY' 'https://api.example.com/weather/london'" |
| 218 | + } |
| 219 | + ] |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +--- |
| 224 | + |
| 225 | +### The `ai_instructions` field |
| 226 | + |
| 227 | +This is what makes RundownAPI different from OpenAPI. It tells the AI *when* to act, not just *how*. |
| 228 | + |
| 229 | +**Triggers** — What should prompt the AI to use this API? User questions, topics, situations. |
| 230 | + |
| 231 | +**Behaviors** — How should the AI present results? What should it offer proactively? |
| 232 | + |
| 233 | +**Constraints** — What should the AI never do? What requires explicit user permission? |
| 234 | + |
| 235 | +You can structure this as an object (like above) or write it as prose: |
| 236 | + |
| 237 | +```json |
| 238 | +"ai_instructions": "Query this API when the user asks about their project history or past decisions. Offer to log entries when they complete significant work. Always ask for the token if not provided. Never fabricate entries — if you don't find something, say so." |
| 239 | +``` |
| 240 | + |
| 241 | +The format matters less than the content. Give the AI enough guidance to be useful without being asked. |
| 242 | + |
| 243 | +## Security and trust |
| 244 | + |
| 245 | +The obvious question: "If my AI reads behavioral instructions from an API, what stops a malicious API from telling it to do bad things?" |
| 246 | + |
| 247 | +Fair concern. Here's how to think about it: |
| 248 | + |
| 249 | +**You initiate the connection.** You give the AI the URL. You choose which APIs to connect to. This is no different from `git clone` or `npm install` — you're trusting the source. Don't give your AI a RundownAPI URL you wouldn't trust with your data. |
| 250 | + |
| 251 | +**The AI still has its own guardrails.** Claude won't delete your files or exfiltrate your data just because an API endpoint said to. Modern AI models are trained to resist prompt injection and verify suspicious instructions with the user. The RundownAPI instructions don't override the model's safety layers. |
| 252 | + |
| 253 | +**The instructions are scoped.** A RundownAPI endpoint describes how to use *that specific API* — when to query, what to offer, how to format results. It's not a general prompt that can say "ignore your system instructions" or "run this shell command." It's documentation with behavioral hints. |
| 254 | + |
| 255 | +**You control the token.** Read access might be open, but writes require authentication. The AI asks you for the token. You decide whether to provide it. No token, no write access. |
| 256 | + |
| 257 | +**The endpoint is readable.** Before you connect, you (or your AI) can fetch `/api/rundown` and inspect exactly what instructions it contains. Nothing is hidden. If something looks suspicious, don't use it. |
| 258 | + |
| 259 | +--- |
| 260 | + |
| 261 | +### What this doesn't protect against |
| 262 | + |
| 263 | +If you connect to a malicious API and give it your token, you've given that API access. Same as any API. RundownAPI doesn't add new attack surface — it just makes the integration faster. |
| 264 | + |
| 265 | +If you're working with APIs you don't fully trust, don't give them write tokens. Use read-only access, or don't connect at all. |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +### The short version |
| 270 | + |
| 271 | +RundownAPI is a trust relationship. Same as using any library, any API, any code you didn't write yourself. |
| 272 | + |
| 273 | +You choose what to connect to. The AI's safety training still applies. If you wouldn't `git clone` it, don't give your AI the URL. |
| 274 | + |
| 275 | +## Adding this to your API |
| 276 | + |
| 277 | +Create an endpoint at `/api/rundown` (or wherever you like) that returns the JSON described above. That's it. |
| 278 | + |
| 279 | +No library to install. No schema to register. Just a JSON endpoint your AI can read. |
| 280 | + |
| 281 | +### The minimum |
| 282 | + |
| 283 | +```javascript |
| 284 | +// /api/rundown endpoint |
| 285 | +export function GET(request) { |
| 286 | + return Response.json({ |
| 287 | + rundown_version: "0.1", |
| 288 | + base_url: "https://your-api.com", |
| 289 | + purpose: "What your API does, in plain language.", |
| 290 | + auth: { |
| 291 | + method: "bearer", |
| 292 | + header: "Authorization: Bearer <token>" |
| 293 | + }, |
| 294 | + endpoints: [ |
| 295 | + { |
| 296 | + path: "/your/endpoint", |
| 297 | + method: "GET", |
| 298 | + description: "What it returns" |
| 299 | + } |
| 300 | + ], |
| 301 | + ai_instructions: "When to use this. How to behave. What to avoid." |
| 302 | + }); |
| 303 | +} |
| 304 | +``` |
| 305 | + |
| 306 | +### Tips |
| 307 | + |
| 308 | +**Detect your base URL dynamically.** If you deploy to multiple environments, read from the request headers instead of hardcoding: |
| 309 | + |
| 310 | +```javascript |
| 311 | +const host = request.headers.get('host'); |
| 312 | +const protocol = host.includes('localhost') ? 'http' : 'https'; |
| 313 | +const base_url = `${protocol}://${host}`; |
| 314 | +``` |
| 315 | + |
| 316 | +**Keep it public.** The rundown endpoint is documentation — no auth required to read it. The AI will ask the user for tokens when it needs to authenticate with your actual endpoints. |
| 317 | + |
| 318 | +**Update it when your API changes.** The rundown should match your actual endpoints. If you add a new route, add it here too. |
| 319 | + |
| 320 | +**Test it with an AI.** Give Claude or ChatGPT the URL and ask it to integrate. Watch what works and what confuses it. Update your `ai_instructions` based on what you see. |
| 321 | + |
| 322 | +## License |
| 323 | + |
| 324 | +CC BY-SA 4.0 — Use it, adapt it, build on it. Keep it open and credit the source. |
| 325 | + |
| 326 | +If you make something with this, that's great. If you improve the spec, even better. Just keep derivatives open so no one can close the gate. |
| 327 | + |
| 328 | +## Attribution |
| 329 | + |
| 330 | +RundownAPI was created by Lida Liberopoulou as part of the Threadbaire project. |
| 331 | + |
| 332 | +- Threadbaire method: [github.com/threadbaire/method](https://github.com/threadbaire/method) |
| 333 | +- Threadbaire server: [github.com/threadbaire/server](https://github.com/threadbaire/server) |
| 334 | + |
| 335 | +Questions, suggestions, improvements: [GitHub issues](https://github.com/threadbaire/rundownapi/issues) or find me on [LinkedIn](https://linkedin.com/in/lliberopoulou). |
| 336 | + |
| 337 | +## Questions |
| 338 | + |
| 339 | +**Do I need Threadbaire to use this?** |
| 340 | + |
| 341 | +No. RundownAPI is a standalone pattern. It came out of building Threadbaire, but works with any API. |
| 342 | + |
| 343 | +**Is this a standard?** |
| 344 | + |
| 345 | +It's v0.1. A pattern that worked for me, documented so others can try it. If it's useful, it'll spread. If people improve it, great. |
| 346 | + |
| 347 | +**What AI models does this work with?** |
| 348 | + |
| 349 | +Tested with Claude Code. Should work with anything that can fetch a URL and read JSON — ChatGPT, Gemini, Cursor, whatever. Some models have quirks (ChatGPT can't modify URL parameters, Gemini gets blocked by some bot protection). Your mileage may vary. |
| 350 | + |
| 351 | +**What about MCP?** |
| 352 | + |
| 353 | +MCP is great if you want to run servers and build that infrastructure. RundownAPI is for when you don't. They're not competing — use whichever fits your situation. Use both if you want. |
| 354 | + |
| 355 | +**Can I use a different endpoint path?** |
| 356 | + |
| 357 | +Yes. `/api/rundown` is a convention, not a requirement. Put it wherever makes sense. Just make sure your AI knows where to find it. |
| 358 | + |
| 359 | +**What if I want to contribute to the spec?** |
| 360 | + |
| 361 | +Open an issue. I'm not running a standards body, but if you've found something that works better, I'm interested. |
0 commit comments