{"openapi":"3.1.0","info":{"title":"The Directory News public API","version":"1.0.0","description":"Read-only search over published articles and public civic records (governments, officials, meetings, elections, crimes, documents, bills, votes). No authentication; every response mirrors what the public pages show."},"servers":[{"url":"https://thedirectorynews-staging.vercel.app"}],"x-deprecation-policy":"https://thedirectorynews-staging.vercel.app/developers#versioning","x-versioning":"Versioned in the URL path (/api/v1). A breaking change ships as /api/v2 with /api/v1 kept for at least 12 months; deprecation is announced with Deprecation and Sunset response headers and on /developers.","paths":{"/api/v1/search":{"get":{"operationId":"searchSite","summary":"Search articles and civic records","description":"Keyword search across published articles and civic records. Returns canonical page URLs to cite.","parameters":[{"name":"q","in":"query","required":true,"description":"Search text (max 200 characters).","schema":{"type":"string","maxLength":200}},{"name":"type","in":"query","description":"Restrict to articles or civic records.","schema":{"type":"string","enum":["all","articles","civic"],"default":"all"}},{"name":"limit","in":"query","description":"Maximum results (1-25, default 10).","schema":{"type":"integer","minimum":1,"maximum":25,"default":10}},{"name":"cursor","in":"query","description":"Opaque cursor from a previous response's next_cursor. Omit for the first page.","schema":{"type":"string"}}],"responses":{"200":{"description":"Matching records.","content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"type":{"type":"string"},"count":{"type":"integer"},"next_cursor":{"type":["string","null"],"description":"Pass as cursor to fetch the next page; null when there are no more results."},"results":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","description":"article, government, official, meeting, election, crime, document, bill or vote."},"title":{"type":"string"},"url":{"type":"string","format":"uri"},"summary":{"type":["string","null"]},"date":{"type":["string","null"]}},"required":["type","title","url"]}}},"required":["query","count","results"]}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/sandbox/search":{"get":{"operationId":"sandboxSearch","tags":["Sandbox"],"summary":"Sandbox: same shape as /api/v1/search, fixed fixture data","description":"Never touches the database or search index; use it to build and test a client. Responses carry sandbox: true.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Fixture results.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/sandbox/articles":{"get":{"operationId":"sandboxArticles","tags":["Sandbox"],"summary":"Sandbox: same shape as /api/v1/articles, fixed fixture data","responses":{"200":{"description":"Fixture articles.","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/batch":{"post":{"operationId":"batchRead","summary":"Run up to 10 read operations in one request","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"description":"Each item is a search or articles operation; results come back in order and one failure does not fail the others. Read-only, so no Idempotency-Key is needed and a retry is always safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["requests"],"properties":{"requests":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","required":["operation"],"properties":{"operation":{"type":"string","enum":["search","articles"]},"q":{"type":"string","maxLength":200},"type":{"type":"string","enum":["all","articles","civic"]},"limit":{"type":"integer","minimum":1,"maximum":25}}}}}}}}},"responses":{"200":{"description":"One result per request, in order.","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer"},"status":{"type":"integer"},"data":{"type":"object"},"error":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/export":{"post":{"operationId":"startExport","summary":"Start an export job (asynchronous)","description":"Returns 202 Accepted with a job id and a Location header. Poll the status URL for the result. Safe to retry: it only reads.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["q"],"properties":{"q":{"type":"string","maxLength":200},"type":{"type":"string","enum":["all","articles","civic"]},"limit":{"type":"integer","minimum":1,"maximum":25}}}}}},"responses":{"202":{"description":"Job accepted.","headers":{"Location":{"description":"Where to poll for the result.","schema":{"type":"string"}},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["accepted"]},"status_url":{"type":"string"}},"required":["job_id","status","status_url"]}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/jobs/{id}":{"get":{"operationId":"getJob","summary":"Status and result of an export job","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Job status; results are present once status is completed.","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["pending","running","completed"]},"count":{"type":"integer"},"results":{"type":"array","items":{"type":"object"}}},"required":["job_id","status"]}}}},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/articles":{"get":{"operationId":"listLatestArticles","summary":"Newest published articles","parameters":[{"name":"limit","in":"query","description":"Maximum results (1-25, default 10).","schema":{"type":"integer","minimum":1,"maximum":25,"default":10}}],"responses":{"200":{"description":"Articles, newest first.","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"articles":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string","format":"uri"},"summary":{"type":["string","null"]}},"required":["title","url"]}}},"required":["count","articles"]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/Error"}}}}},"components":{"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-chosen key (max 255 characters), echoed in the response. Every operation only reads, so repeating a request is always safe.","schema":{"type":"string","maxLength":255}}},"schemas":{"ErrorBody":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"hint":{"type":["string","null"]},"docs":{"type":"string"}},"required":["code","message"]},"Error":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}},"required":["error"]}},"headers":{"RateLimit-Limit":{"description":"Requests allowed per window (60 per 60 s per client).","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying (429 only).","schema":{"type":"integer"}}},"responses":{"Error":{"description":"A typed error: machine-readable code, message and a hint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}