{"openapi":"3.1.0","info":{"title":"Civic Tech Field Guide — Directory API","version":"1.0.0","description":"Public, read-only API over ~12,000 curated civic/democracy tech projects. No API key\nrequired for reads. Also exposed as an MCP server at POST /api/mcp. Data is CC BY 4.0 —\nreuse it, including commercially, with attribution.\n\nRATE LIMITS (per client IP, 60-second windows):\n  * 300 requests/min for ordinary reads\n  * 15 requests/min for /api/v1/projects/export (a full corpus sweep is ~25 pages,\n    so a complete mirror fits comfortably inside one window)\n  * 6 requests/min for POST endpoints that submit content\nExceeding a limit returns 429 with Retry-After and RateLimit-* headers. Every response\ncarries RateLimit-Limit / -Remaining / -Reset so a client can pace itself rather than\ndiscover the ceiling by hitting it. /images/* and /wp-content/* are not rate limited.\n\nCHANGELOG\n  2026-09-18 — ADDED `description` and `grandparent` to /api/v1/categories, which now\n    returns {name, slug, parent, grandparent, description, count}. `description` is the\n    curator's definition of what belongs in the category, filled on 505 of 507 and\n    carrying whatever markdown the upstream base holds; surrounding whitespace is\n    stripped. `grandparent` is the name two levels up the curated hierarchy, null at the\n    top of it, derived from the record links rather than the upstream text column.\n    The MCP list_categories tool gained slug, parent and grandparent, and two optional\n    arguments: query (substring of a name or description) and slug. Called bare it lists\n    every category without descriptions, because all of them together are about 45,000\n    tokens; narrowed by either argument it returns the matching categories with the\n    description in full. No REST field was renamed or removed.\n  2026-09-08 — REMOVED `name`, a byte-identical duplicate of `title`; use `title`.\n    `socials.github` no longer repeats `repository_url`; a github link that differs\n    from it is still returned. A handful of other fields with no upstream source were\n    dropped; each was null on every record, so no response changes shape.\n    ADDED `features` (comma-separated feature names, previously null on every row\n    because the importer read a field name the base does not have), and\n    `hostOrganizationUrl`, resolved from the parent organization's website.\n    `socials` no longer seeds from eleven per-network columns the base never fed;\n    those handles come from the Links table, which is where every value it served\n    already came from.\n    The MCP get_project tool gained images, features, openSourceLicense, language,\n    added, lastModified, closed, shutdownReason, postmortem, funding, founders,\n    impact, resources, parent and children.\n\nAPI STABILITY: the normalized top-level fields are the supported surface. `raw_fields`,\n`linked_names`, `raw_data`, `id` and `airtable_id` are provided for downstream mirrors\nand are explicitly NOT covered by a compatibility promise — `raw_fields` in particular\nmirrors an upstream Airtable base whose columns change without notice. Build against the\nnormalized fields; use the mirror only if you can tolerate it shifting.","license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"},"contact":{"email":"info@civictech.guide"}},"servers":[{"url":"https://civictech.guide"}],"paths":{"/api/v1/projects/search":{"get":{"summary":"Search & filter listings","operationId":"searchProjects","description":"Comma-separated values inside one filter are OR-ed; separate filters are AND-ed, and so is a repeated filter (category=Govtech&category=Open data means both). Unrecognised query params are ignored rather than rejected, so a misspelt filter name returns the unfiltered set — check meta.total against an unfiltered call when a filter looks like it did nothing.","parameters":[{"name":"q","in":"query","description":"Full-text query","schema":{"type":"string"}},{"name":"category","in":"query","description":"Category name or slug (comma-separated)","schema":{"type":"string"}},{"name":"tags","in":"query","description":"Tag name (comma-separated). Matched in full, case-insensitively — \"CTFG partners\" hits, \"CTFG\" does not. See /api/v1/tags for the 309 values.","schema":{"type":"string"}},{"name":"issue","in":"query","description":"Issue area name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/issues for the names.","schema":{"type":"string"}},{"name":"community","in":"query","description":"Community name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/communities for the names.","schema":{"type":"string"}},{"name":"sdg","in":"query","description":"UN Sustainable Development Goal name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/sdgs for the names.","schema":{"type":"string"}},{"name":"platform","in":"query","description":"Platform name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/platforms for the names.","schema":{"type":"string"}},{"name":"politicalUnit","in":"query","description":"Political unit name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/political-units for the names.","schema":{"type":"string"}},{"name":"format","in":"query","description":"Format name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/formats for the names.","schema":{"type":"string"}},{"name":"network","in":"query","description":"Network name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/networks for the names.","schema":{"type":"string"}},{"name":"place","in":"query","description":"Place name (comma-separated): a city, region, state, country or continent, matched against the listing's headquarters and geographic focus. Matched in full, case-insensitively; an unknown name returns 0 results. A place matches only listings linked to it directly, so use country to reach every listing headquartered anywhere in a country. See /api/v1/places for the names.","schema":{"type":"string"}},{"name":"feature","in":"query","description":"Feature name (comma-separated). Matched in full, case-insensitively; an unknown name returns 0 results. See /api/v1/features for the names.","schema":{"type":"string"}},{"name":"country","in":"query","description":"Country (comma-separated). Prefix-matched, so the clean name \"Kenya\" also matches the stored facet value \"Kenya 🇰🇪\".","schema":{"type":"string","example":"Kenya"}},{"name":"projectType","in":"query","description":"Exact projectTypes facet value (comma-separated); see /facets.","schema":{"type":"string","example":"Organization"}},{"name":"orgType","in":"query","description":"Exact orgTypes facet value (comma-separated); see /facets. Values are long — \"Non-profit / charity / foundation\", not \"Non-profit\".","schema":{"type":"string"}},{"name":"openSource","in":"query","schema":{"type":"string","enum":["Yes","No","Partially"]}},{"name":"networkGeo","in":"query","description":"Geographic level of a network the listing belongs to.","schema":{"type":"string","enum":["Continental","Global","Metro","National"]}},{"name":"status","in":"query","description":"Project status filter. Default: active-only. Values: Active, Inactive, N/A (comma-separated, case-insensitive), or 'all' for every status. Unknown values return 400.","schema":{"type":"string","example":"all"}},{"name":"hasImage","in":"query","description":"Set to 1 to return only listings that have an image.","schema":{"type":"string","enum":["1"]}},{"name":"newLaunch","in":"query","description":"Set to 1 for new launches only: listings flagged as newly launched that were added in the last `launchWindow` days. The flag is never cleared, so the window is what keeps a 2022 launch out of 2026 results.","schema":{"type":"string","enum":["1"]}},{"name":"launchWindow","in":"query","description":"Days after being added that a launch still counts as new. Used with newLaunch.","schema":{"type":"integer","default":30,"minimum":0,"maximum":3650}},{"name":"sort","in":"query","description":"Result order. \"relevance\" (default) ranks by the query and otherwise puts listings with images first; \"newest\" orders by the date the listing was added to the directory.","schema":{"type":"string","enum":["relevance","newest"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0,"maximum":100000}}],"responses":{"200":{"description":"Matching projects","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string","description":"Stable URL key. The identifier to use for lookups."},"id":{"type":"string","description":"Internal database UUID. Stable, but prefer `slug`; this is not part of the compatibility promise."},"airtable_id":{"type":"string","nullable":true,"description":"Record id in the upstream Airtable base this directory is curated in. Exposed so downstream mirrors can reconcile against the same source of truth."},"title":{"type":"string"},"url":{"type":"string","nullable":true},"description":{"type":"string","description":"The one-line summary. Alias: `introduction`."},"introduction":{"type":"string","nullable":true,"description":"Alias of `description`."},"longDescription":{"type":"string","nullable":true},"repository_url":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","inactive"],"description":"Normalized to two values: anything whose upstream status is not Inactive reports as active, including N/A and listings with no status at all. Read `status_raw` to tell those apart."},"status_raw":{"type":"string","nullable":true,"description":"Un-normalized upstream status: Active, Inactive, N/A, or null. Some listings carry no status, so the three /facets status counts do not sum to the corpus; those listings are reachable only via status=all or /projects/export."},"founded":{"type":"string","nullable":true},"categories":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"images":{"type":"array","items":{"type":"string","description":"Absolute image URL"}},"location":{"type":"object","nullable":true},"socials":{"type":"object"},"organizationType":{"type":"array","items":{"type":"string"}},"projectTypes":{"type":"array","items":{"type":"string"}},"openSource":{"type":"string","nullable":true},"openSourceLicense":{"type":"string","nullable":true},"language":{"type":"string","nullable":true},"secondaryLanguage":{"type":"string","nullable":true},"builtWith":{"type":"string","nullable":true},"pricing":{"type":"string","nullable":true},"features":{"type":"string","nullable":true,"description":"Feature names for this project, comma-separated. Sourced from the linked Features table."},"employees":{"type":"string","nullable":true},"usedBy":{"type":"string","nullable":true},"resources":{"type":"array","nullable":true},"impact":{"type":"array","nullable":true},"funding":{"type":"array","nullable":true},"founders":{"type":"array","nullable":true,"description":"Named founders/authors, where the directory records them — largely authorship credits on book and podcast listings. Personal names of public figures in their public capacity; populated on roughly 800 of ~12,400 listings."},"parent":{"type":"object","nullable":true},"children":{"type":"array","nullable":true},"added":{"type":"string","nullable":true,"description":"When the listing entered the directory."},"created_at":{"type":"string","nullable":true,"description":"When this ROW was written to our database, not when the project joined the directory. It clusters on migration dates and is useless for sorting by recency. Use `added` for that."},"lastModified":{"type":"string","nullable":true},"raw_data":{"type":"object","description":"Legacy compatibility shim: a subset of the curated fields re-keyed to their original Airtable column names, for older consumers. Prefer the normalized top-level fields above; treat this as frozen."},"raw_fields":{"type":"object","description":"The listing's full mirrored row from the upstream Airtable base, keyed by original column name. Provided so downstream local editions can mirror complete profiles. CAVEATS: the key set varies per record and across time (139 distinct columns observed across the corpus); it is a mirror, not a contract; and it is NOT a stable API — see the API-stability note in the top-level description."},"linked_names":{"type":"object","description":"Resolution table mapping Airtable record ids appearing in `raw_fields` to their human-readable names, so linked-record references in the mirror can be rendered without a second lookup."}}}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}}}},"400":{"description":"Invalid parameter (e.g. unknown status)"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/projects/{slug}":{"get":{"summary":"Full project detail","operationId":"getProject","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Project","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","description":"Stable URL key. The identifier to use for lookups."},"id":{"type":"string","description":"Internal database UUID. Stable, but prefer `slug`; this is not part of the compatibility promise."},"airtable_id":{"type":"string","nullable":true,"description":"Record id in the upstream Airtable base this directory is curated in. Exposed so downstream mirrors can reconcile against the same source of truth."},"title":{"type":"string"},"url":{"type":"string","nullable":true},"description":{"type":"string","description":"The one-line summary. Alias: `introduction`."},"introduction":{"type":"string","nullable":true,"description":"Alias of `description`."},"longDescription":{"type":"string","nullable":true},"repository_url":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","inactive"],"description":"Normalized to two values: anything whose upstream status is not Inactive reports as active, including N/A and listings with no status at all. Read `status_raw` to tell those apart."},"status_raw":{"type":"string","nullable":true,"description":"Un-normalized upstream status: Active, Inactive, N/A, or null. Some listings carry no status, so the three /facets status counts do not sum to the corpus; those listings are reachable only via status=all or /projects/export."},"founded":{"type":"string","nullable":true},"categories":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"images":{"type":"array","items":{"type":"string","description":"Absolute image URL"}},"location":{"type":"object","nullable":true},"socials":{"type":"object"},"organizationType":{"type":"array","items":{"type":"string"}},"projectTypes":{"type":"array","items":{"type":"string"}},"openSource":{"type":"string","nullable":true},"openSourceLicense":{"type":"string","nullable":true},"language":{"type":"string","nullable":true},"secondaryLanguage":{"type":"string","nullable":true},"builtWith":{"type":"string","nullable":true},"pricing":{"type":"string","nullable":true},"features":{"type":"string","nullable":true,"description":"Feature names for this project, comma-separated. Sourced from the linked Features table."},"employees":{"type":"string","nullable":true},"usedBy":{"type":"string","nullable":true},"resources":{"type":"array","nullable":true},"impact":{"type":"array","nullable":true},"funding":{"type":"array","nullable":true},"founders":{"type":"array","nullable":true,"description":"Named founders/authors, where the directory records them — largely authorship credits on book and podcast listings. Personal names of public figures in their public capacity; populated on roughly 800 of ~12,400 listings."},"parent":{"type":"object","nullable":true},"children":{"type":"array","nullable":true},"added":{"type":"string","nullable":true,"description":"When the listing entered the directory."},"created_at":{"type":"string","nullable":true,"description":"When this ROW was written to our database, not when the project joined the directory. It clusters on migration dates and is useless for sorting by recency. Use `added` for that."},"lastModified":{"type":"string","nullable":true},"raw_data":{"type":"object","description":"Legacy compatibility shim: a subset of the curated fields re-keyed to their original Airtable column names, for older consumers. Prefer the normalized top-level fields above; treat this as frozen."},"raw_fields":{"type":"object","description":"The listing's full mirrored row from the upstream Airtable base, keyed by original column name. Provided so downstream local editions can mirror complete profiles. CAVEATS: the key set varies per record and across time (139 distinct columns observed across the corpus); it is a mirror, not a contract; and it is NOT a stable API — see the API-stability note in the top-level description."},"linked_names":{"type":"object","description":"Resolution table mapping Airtable record ids appearing in `raw_fields` to their human-readable names, so linked-record references in the mirror can be rendered without a second lookup."}}}}}},"404":{"description":"Not found"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/feed.xml":{"get":{"summary":"Infinite Feed: any search as RSS","operationId":"feed","description":"RSS 2.0 of the listings matching the same filters as /api/v1/projects/search (all of them except sort and offset), newest first, each item dated the day the listing was added. Combine filters to build a feed: category=Govtech&network=BetaNYC&community=Youth&newLaunch=1. Unlike search, an unknown parameter is a 400, so a misspelt filter cannot silently turn a feed into every new listing.","parameters":[{"name":"kind","in":"query","description":"What the feed carries, comma-separated: listings (default), events, jobs, links, impact, funding, subcategories, issues, communities, sdgs, networks, platforms, formats, political-units, features, locations, or all. Filters apply to every kind through the listings it links to: an event through its host, a job through its hiring organization, an issue through the listings filed under it. Items are dated the day they were created and keyed by their record id, so an edit never re-sends one.","schema":{"type":"string","example":"listings,events,jobs"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"RSS 2.0 feed","content":{"application/rss+xml":{"schema":{"type":"string"}}}},"400":{"description":"Unknown parameter or invalid value"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/projects/export":{"get":{"summary":"Bulk export (paginated)","operationId":"exportProjects","description":"Paginate with limit/offset until offset + data.length >= meta.total to ingest the whole directory.\n\nNOTE: export items are a SUBSET of the single-listing shape. These 13 fields are\nreturned by GET /api/v1/projects/{slug} but NOT here: builtWith, children,\nemployees, founders, funding, impact, openSource, openSourceLicense, parent,\npricing, resources, secondaryLanguage, usedBy. Fetch the detail endpoint if you\nneed them. (Documented after an external review, 2026-08-20, because referencing\nthe full schema here promised fields export does not deliver.)","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":500,"minimum":1,"maximum":1000}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"include","in":"query","schema":{"type":"string","enum":["raw_fields"]},"description":"raw_fields → attach each listing's full mirrored Airtable fields (raw_fields) + linked-record name map (linked_names)"}],"responses":{"200":{"description":"A page of projects","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string","description":"Stable URL key. The identifier to use for lookups."},"id":{"type":"string","description":"Internal database UUID. Stable, but prefer `slug`; this is not part of the compatibility promise."},"airtable_id":{"type":"string","nullable":true,"description":"Record id in the upstream Airtable base this directory is curated in. Exposed so downstream mirrors can reconcile against the same source of truth."},"title":{"type":"string"},"url":{"type":"string","nullable":true},"description":{"type":"string","description":"The one-line summary. Alias: `introduction`."},"introduction":{"type":"string","nullable":true,"description":"Alias of `description`."},"longDescription":{"type":"string","nullable":true},"repository_url":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","inactive"],"description":"Normalized to two values: anything whose upstream status is not Inactive reports as active, including N/A and listings with no status at all. Read `status_raw` to tell those apart."},"status_raw":{"type":"string","nullable":true,"description":"Un-normalized upstream status: Active, Inactive, N/A, or null. Some listings carry no status, so the three /facets status counts do not sum to the corpus; those listings are reachable only via status=all or /projects/export."},"founded":{"type":"string","nullable":true},"categories":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"images":{"type":"array","items":{"type":"string","description":"Absolute image URL"}},"location":{"type":"object","nullable":true},"socials":{"type":"object"},"organizationType":{"type":"array","items":{"type":"string"}},"projectTypes":{"type":"array","items":{"type":"string"}},"openSource":{"type":"string","nullable":true},"openSourceLicense":{"type":"string","nullable":true},"language":{"type":"string","nullable":true},"secondaryLanguage":{"type":"string","nullable":true},"builtWith":{"type":"string","nullable":true},"pricing":{"type":"string","nullable":true},"features":{"type":"string","nullable":true,"description":"Feature names for this project, comma-separated. Sourced from the linked Features table."},"employees":{"type":"string","nullable":true},"usedBy":{"type":"string","nullable":true},"resources":{"type":"array","nullable":true},"impact":{"type":"array","nullable":true},"funding":{"type":"array","nullable":true},"founders":{"type":"array","nullable":true,"description":"Named founders/authors, where the directory records them — largely authorship credits on book and podcast listings. Personal names of public figures in their public capacity; populated on roughly 800 of ~12,400 listings."},"parent":{"type":"object","nullable":true},"children":{"type":"array","nullable":true},"added":{"type":"string","nullable":true,"description":"When the listing entered the directory."},"created_at":{"type":"string","nullable":true,"description":"When this ROW was written to our database, not when the project joined the directory. It clusters on migration dates and is useless for sorting by recency. Use `added` for that."},"lastModified":{"type":"string","nullable":true},"raw_data":{"type":"object","description":"Legacy compatibility shim: a subset of the curated fields re-keyed to their original Airtable column names, for older consumers. Prefer the normalized top-level fields above; treat this as frozen."},"raw_fields":{"type":"object","description":"The listing's full mirrored row from the upstream Airtable base, keyed by original column name. Provided so downstream local editions can mirror complete profiles. CAVEATS: the key set varies per record and across time (139 distinct columns observed across the corpus); it is a mirror, not a contract; and it is NOT a stable API — see the API-stability note in the top-level description."},"linked_names":{"type":"object","description":"Resolution table mapping Airtable record ids appearing in `raw_fields` to their human-readable names, so linked-record references in the mirror can be rendered without a second lookup."}}}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}}}},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/tables/{table}":{"get":{"summary":"Records from other public mirrored tables","operationId":"tableRecords","description":"Allowlisted tables only (currently: \"Civic tech community calendar\"). Returns {id, tableName, fields} rows, where fields carries only the reviewed public columns: Title, Description, Start, End, All Day, Recurring Event, Location, Event location, Event Link, Open in Google Calendar, Parent org, Parent Org Location, Link to location page(s), Created, Updated. Linked fields are record ids.","parameters":[{"name":"table","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"A page of records"},"404":{"description":"Unknown or non-public table"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/facets":{"get":{"summary":"Filter option values","operationId":"facets","description":"Lists the distinct values behind each filter. Built from non-null values, so the status counts do not sum to the corpus — see the status_raw field note.","responses":{"200":{"description":"Facet lists (categories, tags, orgTypes, projectTypes, statuses, countries, networkGeo)"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/categories":{"get":{"summary":"Category index + counts","operationId":"categories","responses":{"200":{"description":"Categories with name, slug, parent, grandparent, description, count. parent/grandparent come from the curated hierarchy; description is the curator blurb, markdown as stored but trimmed of surrounding whitespace, null where unwritten"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/tags":{"get":{"summary":"Tag index + counts","operationId":"tags","responses":{"200":{"description":"Tags with name, count"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/issues":{"get":{"summary":"Issue area index","operationId":"issues","responses":{"200":{"description":"Issue area values with name, description, count, parents, softrUrl, relatedIssues, sdgs, communities (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as issue= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/communities":{"get":{"summary":"Community index","operationId":"communities","responses":{"200":{"description":"Community values with name, description, count, parents, softrUrl (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as community= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/sdgs":{"get":{"summary":"UN Sustainable Development Goal index","operationId":"sdgs","responses":{"200":{"description":"UN Sustainable Development Goal values with name, description, count, parents, order, softrUrl, icon, relatedIssues, relatedIssueSoftrUrls (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as sdg= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/platforms":{"get":{"summary":"Platform index","operationId":"platforms","responses":{"200":{"description":"Platform values with name, description, count, parents, order, logo, impact (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as platform= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/political-units":{"get":{"summary":"Political unit index","operationId":"political_units","responses":{"200":{"description":"Political unit values with name, description, count, parents, image, projects (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as politicalUnit= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/formats":{"get":{"summary":"Format index","operationId":"formats","responses":{"200":{"description":"Format values with name, description, count, parents, order, projects (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as format= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/features":{"get":{"summary":"Feature index","operationId":"features","responses":{"200":{"description":"Feature values with name, description, count (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing linking to it, across every status). Read-only reference data. Pass a name as feature= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/networks":{"get":{"summary":"Network index","operationId":"networks","responses":{"200":{"description":"Network values with name, description, count, parents, order, geoLevel, logo, homeProjects, homeOneLiner, projects (description markdown as stored, trimmed of surrounding whitespace, null where unwritten; count is every listing in the network, across every status). Read-only reference data. Pass a name as network= on /projects/search."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/places":{"get":{"summary":"Place index","operationId":"places","responses":{"200":{"description":"Places with a level (City, County, District, State or Province, Region, Country, Continent), with name, description, count, level, country; count is every listing headquartered in or focused on it, across every status."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/impact":{"get":{"summary":"Impact statements","operationId":"impact","description":"Impact statements recorded for projects, newest first. date is stored as year and month text (\"2024-12\"), null where unknown.","parameters":[{"name":"project","in":"query","description":"Project slug","schema":{"type":"string"}},{"name":"platform","in":"query","description":"Platform name, case-insensitive; see /api/v1/platforms","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"{ data: [{ statement, url, date, projects: [{ slug, name }], platforms: [name], a2iUsers, requestsMade, fulfilledRequests, releasedPages, continent, softrListingUrl }], meta: { total, limit, offset, returned } }. The last 6 are null where unrecorded."},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/v1/map":{"get":{"summary":"Geolocated points (honors search filters)","operationId":"map","responses":{"200":{"description":"Array of {slug, name, lat, lng}"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}},"/api/mcp":{"post":{"summary":"Model Context Protocol (Streamable HTTP, JSON-RPC 2.0)","operationId":"mcp","description":"Stateless MCP server, also aliased at POST /mcp (the public path). Tools: search_projects, get_project, list_categories (pass query or slug for the curated descriptions), list_facets, list_issues, list_communities, list_sdgs, list_platforms, list_political_units, list_formats, list_features, list_networks, list_places, list_impact. Speaks Streamable HTTP directly, so HTTP-capable clients need no mcp-remote bridge. Body ≤ 1MB; batches ≤ 20.","responses":{"200":{"description":"JSON-RPC response"},"202":{"description":"Accepted (notifications)"},"413":{"description":"Body too large"},"429":{"description":"Rate limit exceeded. Retry after the number of seconds in `Retry-After`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the window resets."},"RateLimit-Limit":{"schema":{"type":"integer"}},"RateLimit-Remaining":{"schema":{"type":"integer"}},"RateLimit-Reset":{"schema":{"type":"integer"},"description":"Seconds until the window resets."}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"limit":{"type":"integer"},"window_seconds":{"type":"integer"},"retry_after_seconds":{"type":"integer"}}}}}}}}}}}