Spatial Join
Enrich a set of points with attributes from a polygon dataset. For each point, find which polygon(s) match and attach the polygon’s properties to the point. Think of it as batch point-in-polygon with the results merged back into your input data.
Request
Section titled “Request”POST /v1/spatial-joinParameters (query string)
Section titled “Parameters (query string)”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
system_dataset | string | One required | — | System dataset slug (e.g., us-counties) |
dataset | string | One required | — | Your dataset slug |
dataset_id | string | One required | — | Dataset UUID |
join_type | string | No | intersects | How to test point-polygon matches. See Join types. |
match_mode | string | No | first | How many matches to return per point. See Match modes. |
radius | number | No | — | Expand the search area around each point (meters, up to 500,000). See Radius expansion. |
format | string | No | json | Response format: json or csv |
Provide exactly one of system_dataset, dataset, or dataset_id.
Request body
Section titled “Request body”An array of point objects. See Batch Requests for all supported input formats (JSON, GeoJSON, CSV, NDJSON).
Example
Section titled “Example”curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[ {"lat": 40.7128, "lng": -74.006, "id": "nyc"}, {"lat": 34.0522, "lng": -118.2437, "id": "la"} ]' | jq .Response
Section titled “Response”{ "summary": { "total": 2, "joined": 2, "unjoined": 0 }, "dataset": "us-counties", "version": 1, "join_type": "intersects", "match_mode": "first", "results": [ { "index": 0, "input": {"lat": 40.7128, "lng": -74.006, "id": "nyc"}, "joined": true, "match_count": 1, "matches": [ { "properties": { "STATEFP": "36", "COUNTYFP": "061", "GEOID": "36061", "NAME": "New York", "NAMELSAD": "New York County" }, "boundary": false, "distance_m": 0, "distance_mi": 0 } ] }, { "index": 1, "input": {"lat": 34.0522, "lng": -118.2437, "id": "la"}, "joined": true, "match_count": 1, "matches": [ { "properties": { "STATEFP": "06", "COUNTYFP": "037", "GEOID": "06037", "NAME": "Los Angeles", "NAMELSAD": "Los Angeles County" }, "boundary": false, "distance_m": 0, "distance_mi": 0 } ] } ], "latency_ms": 12, "api_version": "v1", "semantics_version": "0.1.0"}Response fields
Section titled “Response fields”| Field | Description |
|---|---|
summary | Counts of total, joined, and unjoined points |
results[].index | Position in the input array (for matching results back to your data) |
results[].input | Your original input point with any passthrough properties |
results[].joined | true if at least one polygon matched |
results[].match_count | Number of polygons matched |
results[].matches[].properties | The matched polygon’s attributes |
results[].matches[].boundary | true if the point falls exactly on the polygon boundary (intersects mode only) |
results[].matches[].distance_m | Distance to polygon in meters (0 if point is inside) |
results[].matches[].distance_mi | Distance to polygon in miles |
Join types
Section titled “Join types”The join_type parameter controls how a point is tested against each polygon.
intersects (default)
Section titled “intersects (default)”A point matches a polygon if it is inside the polygon or on its boundary. This is the standard point-in-polygon test. A point sitting exactly on a shared boundary between two polygons matches both (when match_mode=all).
The boundary field in each match tells you whether the point was on the boundary (true) or strictly inside (false).
Use this for most enrichment workflows — “which county is this address in?”, “what school district does this location belong to?”
within
Section titled “within”A point matches a polygon only if it is strictly inside — points on the boundary are excluded. This is useful when boundary ambiguity matters, such as when a point on a shared border between two polygons should match neither rather than both.
The boundary field is always false in within mode (boundary points are excluded from results).
Match modes
Section titled “Match modes”first (default)
Section titled “first (default)”Returns only the first matching polygon for each point. Fastest. Use for the common case where you need one result per point.
Returns every matching polygon for each point. Use when polygons overlap or when you need complete coverage information (e.g., “what are ALL the school districts near this point?”). The match_count field tells you how many polygons matched.
curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties&match_mode=all" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[{"lat": 40.7128, "lng": -74.006}]' | jq .Radius expansion
Section titled “Radius expansion”The radius parameter expands the search area around each point to find additional polygons within the specified distance (in meters, up to 500,000). This is independent of join_type — it works with both intersects and within.
Without radius, the join only finds polygons that the point falls inside. With radius, it also finds polygons whose boundary is within the given distance of the point, even if the point isn’t inside them.
Each additional match found via radius expansion includes a non-zero distance_m showing how far the polygon boundary is from the point.
# Find the containing county AND any counties within 5 kmcurl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties&match_mode=all&radius=5000" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[{"lat": 40.7128, "lng": -74.006}]' | jq .CSV output
Section titled “CSV output”Add format=csv to get results as CSV:
curl -s -X POST "https://api.terranode.co/v1/spatial-join?system_dataset=us-counties&format=csv" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '[{"lat": 40.7128, "lng": -74.006}]'Batch limits
Section titled “Batch limits”Up to 100 points per request on the Free tier. Each point counts as one request against your monthly quota.