Codewright Public API (v1)

Read-only, public REST API over the recipe library.

https://codewright.tools/api/v1

Auth

None. Every v1 endpoint is public and unauthenticated — recipe content is not gated. (Favorites/saved recipes, if you're signed in on the website, are a separate authenticated surface, not part of this API.)

Rate limits

Requests are limited per client IP using a sliding window: 60 requests per 60 seconds by default. When you exceed it, the response is 429 with a Retry-After header (seconds until the window resets):

{ "error": { "code": "rate_limited", "message": "Too many requests. Please retry later." } }

Logging & usage metrics

Every /api/v1/* route logs one line of structured JSON to stdout with method, path, status, and latency_ms only — no query strings, headers, or other user-identifying data. Usage counters are tracked in-memory for the lifetime of the process and reset on every deploy/restart.

Internal routes

Not part of the public v1 surface — for the operator only, gated by a shared-secret header.

GET /api/internal/usage

Returns the current usage snapshot as JSON: { "usage": { "<routeLabel>": { "count": number, "statusCounts": { "<status>": number } } } }

Set INTERNAL_API_TOKEN in the deployment environment, then send it back on each request. If unset or wrong, the route always returns 401 unauthorized — it fails closed rather than falling back to open access.

curl https://codewright.tools/api/internal/usage \

  -H "x-internal-token: $INTERNAL_API_TOKEN"

Errors

All error responses share one shape: { "error": { "code": "string", "message": "human-readable", "details": "optional" } }

CodeStatusMeaning
invalid_request400Failed validation — details is a list of per-field problems.
invalid_params400Render request had an unknown or out-of-range parameter.
not_found404No resource with that id.
rate_limited429Too many requests from this client IP; see Retry-After header.
upstream_error502The recipe store failed.
service_unavailable503The recipe store isn't configured in this environment.

Endpoints

MethodPathSummary
GET/recipesList and filter recipes.
GET/recipes/searchFull-text search over recipes.
GET/recipes/{id}Fetch one recipe's full document.
POST/recipes/{id}/renderRender a recipe's commands for a platform with parameter values substituted in. Never executes anything server-side — it only returns text.
GET/workflowsList and filter workflows.
GET/workflows/searchFull-text search over workflows.
GET/workflows/{id}Fetch one workflow's full document (entry, nodes), for a client that wants to do its own node-walking.
GET/recipes

List and filter recipes.

ParamTypeNotes
categorystringone of git, docker, files, network, node, db
platformstringone of macos, linux, windows
riskstringone of read_only, low, medium, destructive
limitinteger1–100, default 20
offsetintegerdefault 0

curl "https://codewright.tools/api/v1/recipes?category=git&risk=read_only&limit=5"

{ recipes: [...], pagination: { limit, offset, total } } — each item is a summary (id, slug, category, title, summary, risk, requires_confirmation, platforms, tags, version), not the full document.

GET/recipes/search

Full-text search over recipes.

ParamTypeNotes
qstringrequired, non-empty
limitinteger1–100, default 20

curl "https://codewright.tools/api/v1/recipes/search?q=undo%20last%20commit"

{ query, recipes: [...] } with the same summary shape as /recipes.

GET/recipes/{id}

Fetch one recipe's full document.

curl "https://codewright.tools/api/v1/recipes/git.undo_last_commit"

{ recipe: {...} }, or 404 not_found if no recipe has that id.

POST/recipes/{id}/render

Render a recipe's commands for a platform with parameter values substituted in. Never executes anything server-side — it only returns text.

ParamTypeNotes
platformstringrequired, one of macos, linux, windows
paramsobjectoptional map of parameter name → string/number/boolean. Missing parameters fall back to the recipe's declared default.

curl -X POST "https://codewright.tools/api/v1/recipes/network.find_process_on_port/render" \

-H "Content-Type: application/json" \

-d '{"platform": "macos", "params": {"port": 3000}}'

{ id, version, risk, requires_confirmation, commands: [{ command, purpose, risk }], verification: [{ command, expect }], undo: { possible, note? } }. Errors: invalid_platform / platform_unavailable (400), invalid_params (400), invalid_recipe_data (500).

GET/workflows

List and filter workflows.

ParamTypeNotes
categorystringone of git, docker, files, network, node, db
limitinteger1–100, default 20
offsetintegerdefault 0

curl "https://codewright.tools/api/v1/workflows?category=git&limit=5"

{ workflows: [...], pagination: { limit, offset, total } } — each item is a summary (id, category, title, description, tags, version), not the full node graph.

GET/workflows/search

Full-text search over workflows.

ParamTypeNotes
qstringrequired, non-empty
limitinteger1–100, default 20

curl "https://codewright.tools/api/v1/workflows/search?q=cant%20push"

{ query, workflows: [...] } with the same summary shape as /workflows.

GET/workflows/{id}

Fetch one workflow's full document (entry, nodes), for a client that wants to do its own node-walking.

curl "https://codewright.tools/api/v1/workflows/git.cant_push_branch"

{ workflow: {...} }, or 404 not_found if no workflow has that id. The stateful POST /workflows/{id}/advance route is out of scope until the runtime-interpreter design lands.

This page and the OpenAPI spec are maintained by hand against the live route handlers — when a v1 route's request/response shape changes, both are updated in the same PR.