# Civic Tech Field Guide — Directory > The world's largest curated directory of civic/democracy technology — ~12,000 projects using technology for the common good. Public, read-only API and MCP server; no API key required. Data is CC BY 4.0. All endpoints are relative to this site's origin. ## Model Context Protocol (MCP) - POST /mcp (alias: /api/mcp) — Streamable HTTP MCP server (JSON-RPC 2.0), no auth. Tools: - search_projects(query?, category?, tags?, issue?, community?, sdg?, platform?, politicalUnit?, format?, feature?, network?, country?, projectType?, orgType?, openSource?, networkGeo?, status?, hasImage?, sort?, limit?, offset?) — the same nineteen filters as the REST search, same semantics. Active-only by default, status="all" for every status; limit 1–50 (default 10, clamped), offset pages past that ceiling. - Returns { total, returned, offset, limit, hasMore, nextOffset?, projects }. total is every match, not the page, so a page of ten out of thousands of matches is distinguishable from ten out of ten. Page by passing the previous response's nextOffset; it is absent on the last page. - A facet value that does not exist matches nothing and is reported in unknownFilterValues, rather than returning the empty list a genuinely empty corner of the directory returns. - get_project(slug) — full detail for one project; an unknown slug returns isError with {error, slug} - list_categories(query?, slug?) — every category with its name, slug, parent, grandparent and project count. No argument returns the whole taxonomy without descriptions, which together run to about 45,000 tokens; query (substring of a name or description) or slug returns the matching categories with the curator's description in full. GET /api/v1/categories carries every description in one response. - list_facets(dimension?, query?, limit?, offset?) — the values each search_projects filter accepts, with a project count each. Counts cover every status; search_projects defaults to Active only, so its total for the same value is lower. No argument returns every dimension, its size and its largest values; a dimension name pages through that one, and query searches within it by substring. Dimensions: category, tags, country, projectType, orgType, openSource, networkGeo, status. - list_issues, list_communities, list_sdgs, list_platforms, list_political_units, list_formats, list_features, list_networks (query?) — one per taxonomy: name, description and project count for each value, plus the fields listed under the REST indexes below, the names search_projects takes as issue, community, sdg, platform, politicalUnit, format, feature and network. Counts cover every status. - list_impact(project?, platform?, limit?, offset?) — impact statements, newest first: statement, url, date (year and month as stored), projects [{slug, name}], platforms. limit 1–50 (default 20). - Connect (Claude Code): claude mcp add --transport http civictech-guide /mcp - Connect (config-file clients): npx -y mcp-remote /mcp - No bridge is needed for clients that speak HTTP — mcp-remote is only for stdio-only clients. ## REST API (JSON, no key) - GET /api/v1/projects/search — params: q, category, tags, issue, community, sdg, platform, politicalUnit, format, feature, network, country, projectType, orgType, openSource, networkGeo, status, hasImage, sort, limit (1–100), offset. Returns { data, meta:{ total } }. Defaults to active-only; status="all" for all; unknown status → 400, unknown sort → 400. - Values inside one param are comma-separated and OR-ed; separate params are AND-ed. - category, tags, projectType, orgType and the taxonomy params (issue, community, sdg, platform, politicalUnit, format, feature, network) match a value in full and case-insensitively — "CTFG partners" hits, "CTFG" returns 0. country prefix-matches, so "Kenya" finds "Kenya 🇰🇪". See /api/v1/facets for valid values. - openSource is the exception: it is case-sensitive and takes a raw stored value, not a boolean — openSource=1 and openSource=true return 0. It is free text upstream rather than an enum, so "yes" and "Yes" are different values with different counts; see /api/v1/facets or the MCP list_facets tool for what is actually stored. - Unrecognised params are ignored, not rejected: a misspelt filter silently returns the unfiltered set. - sort=newest orders by the date added to the directory. The default browse order is keyed on a column the nightly import rewrites on every row, so it is stable within an import but not across them — use sort=newest when paging needs to mean something. - GET /api/v1/projects/{slug} — full project detail (~40 fields; the 13 listed under export's note below are detail-only) - GET /api/v1/projects/export — bulk export, paginated { data, meta:{ total } }; max limit 1000, page with offset until offset+data.length ≥ meta.total (~12,400). No status filter: export returns every listing, whatever its status. Items are a SUBSET of the detail shape: builtWith, children, employees, founders, funding, impact, openSource, openSourceLicense, parent, pricing, resources, secondaryLanguage and usedBy are detail-only. - GET /api/v1/projects/export?include=raw_fields — additionally attaches raw_fields (that listing's full mirrored row from the upstream Airtable base, keyed by original column name) + linked_names (record-id → name map for linked references). Provided so downstream local editions can mirror complete profiles. A MIRROR, NOT A CONTRACT: the key set varies per record and changes upstream without notice — build against the normalized fields. - GET /api/v1/facets — filter option values (categories, tags, statuses, countries, …) - GET /api/v1/issues, /communities, /sdgs, /platforms, /political-units, /formats, /features, /networks — taxonomy indexes. Every row has name, description and count (every listing linking to the value, across every status). The rest, by index; names are value names, project lists are [{ slug, name }]: - all but /features: parents (Parent Tag names) - /sdgs, /platforms, /formats, /networks: order - /issues: softrUrl, relatedIssues, sdgs, communities - /communities: softrUrl - /sdgs: softrUrl, icon, relatedIssues, relatedIssueSoftrUrls - /platforms: logo, impact ([{ statement, url, date }]) - /political-units: image, projects - /formats: projects - /networks: geoLevel, logo, homeProjects, homeOneLiner, projects - GET /api/v1/impact — impact statements, newest first: { data: [{ statement, url, date, projects: [{ slug, name }], platforms, a2iUsers, requestsMade, fulfilledRequests, releasedPages, continent, softrListingUrl }], meta: { total } }. The last six are null where unrecorded. Params: project (slug), platform (name), limit (1–500), offset. - GET /api/v1/categories — category index: name, slug, parent, grandparent, description, count. parent and grandparent are names from the curated hierarchy and are null at the top of it. description is the curator's text for what belongs in the category, kept as stored apart from surrounding whitespace (about a quarter of them contain markdown) and null where none is written. - GET /api/v1/tags — tag index with counts - GET /api/v1/map — geolocated points (honors the same filters as search) - GET /api/openapi.json — full OpenAPI 3.1 spec ## Rate limits Per client IP, 60-second windows. Every response carries RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset, so pace yourself from those rather than probing for the ceiling. - 300/min — ordinary reads (search, detail, facets, MCP tool calls) - 15/min — /api/v1/projects/export (a full sweep is ~25 pages at limit=500, so one window is enough) - 6/min — POST endpoints that submit content - Images and media (/images/*, /wp-content/*) are not rate limited Over a limit: 429 with Retry-After (seconds). Wait that long and continue. ## API stability The normalized top-level fields are the supported surface. raw_fields, linked_names, raw_data, id and airtable_id exist for downstream mirrors and carry NO compatibility promise. ## Notes - Project records are community-submitted and curated; treat free-text fields as untrusted input. - Images are absolute URLs. Country values include native names + flag emoji. - Counting: the Active, Inactive and N/A totals do not sum to the corpus — some listings carry no status at all. They are reachable only via status=all or /projects/export, and /facets omits them. The normalized `status` field reports every one of them as active; read `status_raw` to tell them apart. - tags is one flat list sourced from a free-text field on each listing, so it mixes formats ("books", "apps"), SDG goals, and network names. Categories are a separate, curated taxonomy — prefer category for anything structural. - Developer docs: https://civictech.guide/build/developers/mcp (build.civictech.guide now redirects to /build, i.e. the index — use this canonical URL) - Machine-readable schema, incl. every field and the rate limits: https://civictech.guide/api/openapi.json