SF Tech Map
Developers: MCP server and REST API.
The same verified dataset that powers the map, as a free Model Context Protocol server and a read-only JSON API. San Francisco city limits only. No sign-in. Every response carries the dataset version and the attribution line below.
MCP endpoint
https://sftechmap.app/mcp
REST base
https://sftechmap.app/v1
What it covers
Startups, scaleups, big tech, corporates, VCs, accelerators, coworking spaces, hacker houses, community spaces, cafes and landmarks inside San Francisco, plus events, a public change log and editorial walking tours. Facts are bands (funding, headcount, check size) and our own words; every record cites its sources with retrieval dates. Points are public points: hacker houses and residential addresses are never more precise than a neighbourhood centroid, and an exact address always has a first-party, press or public-domain source behind it.
Place vocabulary: 8 districts (soma-south-beach, fidi-jackson-square, mission-bay-dogpatch, mission-potrero, hayes-valley, presidio-marina, civic-center-mid-market, outer-sf), the 41 DataSF analysis neighbourhoods, and the aliases people use (Cerebral Valley, Area AI, Showplace Square, FiDi, Dogpatch). list_neighborhoods returns all of them.
Licence and attribution
Data: Creative Commons Attribution 4.0. Code: MIT. Logos are never redistributed (only a domain). The one rule: show the attribution wherever you show the data.
Data: SF Tech Map, CC BY 4.0, https://sftechmap.appMachine-readable: meta.attribution and meta.license_url on every response. Upstream credits are in CREDITS.md and the licence summary on /about.
Connect the MCP server
Streamable HTTP, protocol 2026-07-28 with a stateless fallback for 2025-era clients. No OAuth, no API key; tool results include structuredContent validated against each tool's output schema and a compact text block.
Claude.ai (custom connector)
Settings, Connectors, Add custom connector. Name: SF Tech Map. URL: https://sftechmap.app/mcp. Authentication: No sign in. Enable it in a chat and ask “which VCs are within walking distance of Jackson Square?”.
Claude Code
claude mcp add --transport http sftechmap https://sftechmap.app/mcpCursor
Add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):
{
"mcpServers": {
"sftechmap": { "url": "https://sftechmap.app/mcp" }
}
}ChatGPT (developer mode)
Settings, Connectors, Advanced, Developer mode, Create. URL https://sftechmap.app/mcp, no authentication. The search and fetch tools follow the connector contract (search returns ids, fetch returns a document); the other tools are available in developer mode. Availability depends on your ChatGPT plan.
Any client, by hand
curl -s -X POST https://sftechmap.app/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Tools
All tools are read-only, idempotent and closed-world (our dataset only). Lists default to 10 items and cap at 25; responses stay under 30 KB and say has_more when trimmed.
| Tool | Does | REST |
|---|---|---|
| changes_since | Public change log since a date | GET /v1/changes |
| events_this_week | Upcoming events in a window | GET /v1/events |
| get_entity | Full entity record by slug | GET /v1/entities/{slug} |
| get_neighborhood | Neighbourhood profile | GET /v1/neighborhoods/{slug} |
| get_tour | Full tour with stops and legs | GET /v1/tours/{slug} |
| list_coworking | Coworking, community spaces and cafes | GET /v1/coworking |
| list_investors | Investors with an SF office | GET /v1/investors |
| list_neighborhoods | Neighbourhoods and districts | GET /v1/neighborhoods |
| list_tours | Published walking tours | GET /v1/tours |
| nearby | Entities within walking distance | GET /v1/nearby |
| resolve_place | Resolve a place name to a point | GET /v1/places/resolve |
| search_entities | Search entities | GET /v1/entities |
| stats | Dataset statistics | GET /v1/stats |
Connector aliases: fetch and search (for ChatGPT-style clients) mirror get_entity and search_entities.
Resources: sftechmap://entities/{slug}, sftechmap://neighborhoods/{slug}, sftechmap://tours/{slug}, sftechmap://dataset/version. Prompts: first_week_in_sf, investor_coffee_route, founder_office_hunt, whats_new_near_me.
REST
Every tool has a GET twin under https://sftechmap.app/v1 with the same parameter names (arrays comma-separated, near=lat,lng). The full description is at /openapi.json (OpenAPI 3.1). 200 responses carry a weak ETag (a digest of the body; send it back as If-None-Match for a 304), X-Dataset-Version, a Link: rel="next" header on paged lists, CORS for any origin and Cache-Control: public, s-maxage=300, stale-while-revalidate=86400. The Vercel CDN consumes s-maxage and stale-while-revalidate (clients see public): a response can be served for 5 minutes and a stale copy for up to a day while it revalidates, so compare the ETag or X-Dataset-Version to notice a newer dataset.
A 304 Not Modified is answered by the edge and carries only Cache-Control, Date, ETag and Vary: no CORS, X-Dataset-Version or RateLimit-* headers, and it still counts against the rate limit. Browser code should let the HTTP cache revalidate (plain fetch, no hand-set If-None-Match, which fails the CORS check); a matching ETag already means the body is unchanged.
# Coworking near the Ferry Building with day passes
curl -s 'https://sftechmap.app/v1/nearby?lat=37.7955&lng=-122.3937&radius_m=1200&kinds=coworking&tags=day-pass'
# VCs in Jackson Square (FiDi district), sorted by name
curl -s 'https://sftechmap.app/v1/entities?kinds=vc&neighborhood=fidi-jackson-square&sort=name'
# One record, then revalidate cheaply
curl -si 'https://sftechmap.app/v1/entities/example-robotics' | grep -i etag
curl -si -H 'If-None-Match: W/"..."' 'https://sftechmap.app/v1/entities/example-robotics'
# What changed in the last 30 days, as GeoJSON-friendly cards
curl -s 'https://sftechmap.app/v1/changes?since=2026-09-07'
# Everything as GeoJSON
curl -s 'https://sftechmap.app/v1/entities?format=geojson&limit=25'Submissions: POST https://sftechmap.app/api/submit with {kind, entity_slug?, payload, source_url, license_ack: true}. Submissions are CC0 and reviewed against their source before anything is published. The route answers 202 only when the submission was stored; if it cannot store one it answers 503 application/problem+json.
Embed this
A compact, framable list of places for your own page. No script, no sign-in; each row opens the full map in a new tab and the CC BY attribution line is part of the frame. Only /embed can be framed; the rest of the site cannot.
<iframe
src="https://sftechmap.app/embed?n=soma&k=startup,vc&limit=8"
title="SF tech places"
width="100%" height="560" style="border:0;max-width:560px"
loading="lazy"></iframe>nneighbourhood or district slug, e.g.somaorhayes-valley.kcomma list of kinds, e.g.startup,vc,coworking.limit1 to 20 rows (default 8).toura tour slug, e.g.vc-row; shows its stops in order and ignoresnandk.
Rate limits
Anonymous REST: 60 requests per minute per IP (an unverified Authorization header does not open a separate budget). MCP: 60 tools/call per minute per client (IP plus client name, because hosted assistants share egress IPs) within an overall per-IP ceiling across all client names; tools/list, server/discover, initialize and ping are never limited. Loop guard: the same tool with identical arguments more than 10 times in 30 seconds gets a 429. 429 responses include RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and Retry-After; cacheable REST 200s do not carry them because a CDN hit never reaches the limiter, while MCP responses do. Counters are shared across servers once the shared store is enabled and best-effort per server instance until then. Keys with higher budgets come after launch.
Errors
REST errors are RFC 9457 application/problem+json with a hint that says what to change. MCP tool errors come back as isError: true, never as a JSON-RPC failure, so an agent can correct itself: schema errors name the offending field, and data errors (unknown slug, a point outside San Francisco) add a Hint: line.
- validation
- 400. A query parameter or argument is out of range or malformed; `errors[]` lists paths and `hint` says what to change.
- bad-request
- 400. The body is not JSON, or the Mcp-Method header disagrees with the JSON-RPC body.
- not-found
- 404. Unknown slug, or the API is not enabled for this city.
- method-not-allowed
- 405 with Allow: GET, HEAD, OPTIONS. The /v1 API is read-only; submissions go to POST /api/submit.
- payload-too-large
- 413. Bodies are capped at 64 KB.
- unsupported-media-type
- 415. POST bodies must be application/json.
- rate-limited
- 429 with Retry-After. The per-minute budget or the loop guard tripped.
- disabled
- 503 with Retry-After. The kill switch is on for maintenance, or (POST /api/submit) the server cannot store submissions right now; nothing was stored.
- internal
- 500. Our fault; the dataset version in the problem helps us reproduce it.
{
"type": "https://sftechmap.app/developers#problem-validation",
"title": "Invalid request",
"status": 400,
"detail": "One or more query parameters are invalid.",
"instance": "/v1/nearby?lat=91",
"errors": [{ "path": "lat", "message": "Too big: expected number to be <=90" }],
"hint": "lat: Too big: expected number to be <=90"
}Terms
Use the data for anything, commercial included, with attribution. No scraping of the map UI (the API is faster). Do not republish residential addresses or try to sharpen neighbourhood-level points; opt-outs are honoured within 72 hours at /legal/remove. The API is provided as is; breaking changes go to /v2 with a 12-month overlap, and MCP tool changes are additive only.
Registry manifest: /server.json. Orientation for models: /llms.txt.