{"openapi":"3.0.3","info":{"title":"Mah Todo API","version":"0.2.0","description":"A todo list kept in memory by a server written in Mah. Every route is a plain function; this document is written from their declarations with std:reflect."},"servers":[{"url":"/"}],"tags":[{"name":"todos","description":"The todo list itself"},{"name":"service","description":"About the service"}],"paths":{"/api":{"get":{"tags":["service"],"summary":"Describe the service.","description":"Its name, version, size, and every route it answers.","operationId":"index","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Index"}}}}}}},"/health":{"get":{"tags":["service"],"summary":"Check that the service is up.","operationId":"health","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.json":{"get":{"tags":["service"],"summary":"This document.","description":"Written at run time from the handlers' declarations with std:reflect.","operationId":"openapi_json","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","additionalProperties":{}}}}}}}},"/todos":{"get":{"tags":["todos"],"summary":"List todos.","description":"Oldest first. `?done=true` leaves only the finished ones, `?done=false`\nonly the open ones.","operationId":"list_todos","parameters":[{"name":"done","in":"query","required":false,"description":"Only finished (true) or only open (false) todos.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoList"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"post":{"tags":["todos"],"summary":"Add a todo.","operationId":"create_todo","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewTodo"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Todo"}}},"headers":{"Location":{"description":"where the new resource can be fetched","schema":{"type":"string"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Unprocessable Entity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/todos/done":{"delete":{"tags":["todos"],"summary":"Remove every finished todo.","description":"Declared before `/todos/{id}`, which would otherwise take `done` for an id.","operationId":"clear_done","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cleared"}}}}}}},"/todos/{id}":{"get":{"tags":["todos"],"summary":"Get one todo.","operationId":"get_todo","parameters":[{"name":"id","in":"path","required":true,"description":"The todo's id.","schema":{"type":"number"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Todo"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"put":{"tags":["todos"],"summary":"Replace a todo.","description":"The title and the done flag both come from the body; a done flag left out\nis false.","operationId":"replace_todo","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewTodo"}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The todo's id.","schema":{"type":"number"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Todo"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Unprocessable Entity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"patch":{"tags":["todos"],"summary":"Change part of a todo.","description":"Only the fields in the body change. A body with neither is a 422.","operationId":"update_todo","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TodoPatch"}}}},"parameters":[{"name":"id","in":"path","required":true,"description":"The todo's id.","schema":{"type":"number"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Todo"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Unprocessable Entity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"delete":{"tags":["todos"],"summary":"Delete a todo.","operationId":"delete_todo","parameters":[{"name":"id","in":"path","required":true,"description":"The todo's id.","schema":{"type":"number"}}],"responses":{"204":{"description":"No Content"},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/todos/{id}/toggle":{"post":{"tags":["todos"],"summary":"Flip a todo between open and finished.","operationId":"toggle_todo","parameters":[{"name":"id","in":"path","required":true,"description":"The todo's id.","schema":{"type":"number"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Todo"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"schemas":{"Index":{"type":"object","properties":{"service":{"type":"string","example":"todo api"},"version":{"type":"string","description":"The API version."},"todos":{"type":"number","description":"How many todos there are now."},"docs":{"type":"string","description":"Swagger UI, for a browser.","example":"/docs"},"openapi":{"type":"string","description":"The OpenAPI document.","example":"/openapi.json"},"routes":{"type":"array","items":{"type":"string"},"description":"Every route, as `METHOD /path`."}},"required":["service","version","todos","docs","openapi","routes"],"description":"What this service is and where to go next."},"Health":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"todos":{"type":"number","description":"How many todos there are now."}},"required":["status","todos"],"description":"Whether the service is up."},"TodoList":{"type":"object","properties":{"count":{"type":"number","description":"How many todos `todos` holds.","example":1},"todos":{"type":"array","items":{"$ref":"#/components/schemas/Todo"}}},"required":["count","todos"],"description":"Some todos, and how many there are."},"Todo":{"type":"object","properties":{"id":{"type":"number","description":"Given by the server, counting up from 1; never reused.","example":1},"title":{"type":"string","description":"What needs doing, trimmed.","example":"buy milk"},"done":{"type":"boolean","description":"Whether it is finished.","example":false},"created_at":{"type":"string","description":"When it was added, in UTC (ISO 8601).","example":"2026-10-06T09:30:00Z"}},"required":["id","title","done","created_at"],"description":"One todo, exactly as the API hands it out."},"ErrorBody":{"type":"object","properties":{"error":{"type":"string","description":"A short code a program can test, like `not_found` or `bad_request`."},"message":{"type":"string","description":"What went wrong, in words."}},"required":["error","message"],"description":"The body of every error answer."},"NewTodo":{"type":"object","properties":{"title":{"type":"string","description":"What needs doing. Surrounding whitespace is dropped; it can't be blank.","example":"buy milk"},"done":{"type":"boolean","nullable":true,"description":"Whether it is already finished (false when left out).","example":false}},"required":["title"],"description":"A todo to add, or the whole of one to put in place of another."},"Cleared":{"type":"object","properties":{"removed":{"type":"number","example":2}},"required":["removed"],"description":"How many todos `DELETE /todos/done` removed."},"TodoPatch":{"type":"object","properties":{"title":{"type":"string","nullable":true,"description":"A new title. Surrounding whitespace is dropped; it can't be blank.","example":"buy oat milk"},"done":{"type":"boolean","nullable":true,"description":"Finished or not.","example":true}},"description":"The fields of a todo to change; leave out the ones to keep."}}}}