Region groups · Published 2026-05-24

Custom regions: build a territory map and tag any list in 6 calls

Define your own hierarchical region layout — campaign zones, sales territories, underwriting books, school clusters, anything you'd organize on a whiteboard — then tag a donor file, customer list, or policy book against it. Every row comes back with its leaf region plus the full ancestor chain. Aggregate, render, publish. The whole loop is six MCP calls. Premium feature.

What "region group" means

A region group is a named, hierarchical layout built on top of any boundary set you can query — system collections (us-states, us-counties, us-cd119, us-tracts, us-zcta, us-school-unified, us-sldu / us-sldl) or workspace-owned custom polygons you uploaded via ingest_geojson.

Inside a group: as many regions as you want, organized as a tree. Top-level "master" regions can contain "sub-regions"; sub-regions can contain their own sub-regions. Each leaf region owns a list of base-shape external IDs (county FIPS, CD number, ZCTA, or polygon IDs — whichever the group's base collection uses). Re-shape it any time without re-ingesting the underlying boundaries.

The pitch in one example

A field-organizing director runs a 50,000-row donor file. They want every donor tagged by:

The whole layout is six steps; the resulting /v/<slug> map plus tagged dataset stays around forever as the team's source of truth.

Step 1 — Start from a template

The Census Bureau's 4 regions × 9 divisions × 51 states (50 + DC) is the canonical demographic hierarchy. Clone it as a starting point; rename and reshape as needed.

mcp.call("clone_region_template", {
  template: "census-regions-and-divisions",
  new_slug: "campaign-2026-territory",
  new_name: "Campaign 2026 territory",
})
  → region_group: "campaign-2026-territory"
    13 regions created (4 masters + 9 sub-regions)
    51 members added (every state + DC, FIPS '01' through '56')

Step 2 — Customize the layout to your team

Maybe your "East" team covers more than the Census definition — you want NJ + NY + PA + DC + MD + DE in the same sub-region under East. Drop a region's members, re-add the right ones, done.

mcp.call("remove_shapes_from_region", {
  region_group: "campaign-2026-territory", region: "mid-atlantic",
  shape_external_ids: ["34", "36", "42"],  // remove NJ, NY, PA from default mid-atlantic
})

mcp.call("define_region", {
  region_group: "campaign-2026-territory",
  slug: "east-coast-corridor", name: "East-Coast Corridor",
  parent_region: "northeast",
})

mcp.call("add_shapes_to_region", {
  region_group: "campaign-2026-territory", region: "east-coast-corridor",
  shape_external_ids: ["34","36","42","11","24","10"],  // NJ, NY, PA, DC, MD, DE
})

Or build your layout from scratch with create_region_group + define_region + add_shapes_to_region. The dashboard at /dashboard/regions/<slug> has a click-to-add gallery so you don't have to type FIPS codes by hand on mobile.

Step 3 — Render the layout for sanity check

mcp.call("render_region_group_map", {
  region_group: "campaign-2026-territory",
  color_by: "master_region",   // East / Midwest / South / West get distinct colors
  title: "Campaign 2026 territory",
})
  → SVG showing the U.S. colored by 4 master regions

Color assignment is deterministic per region slug — re-rendering the same group produces the same colors every time. Citation-grade.

Step 4 — Geocode your list

Donor file, customer list, policy book, whatever. The Census batch geocoder handles 10K addresses per call; chain multiple if you have more.

mcp.call("bulk_match_addresses", {
  records: donors,
  collection: "us-states", version: "tiger-2024",
})
  // → one row per donor, each tagged with the containing state FIPS

Step 5 — Tag the list against your region group (the "code my list" verb)

mcp.call("tag_records_with_region_group", {
  region_group: "campaign-2026-territory",
  records: geocoded_donors,   // each carries shape_external_id from step 4
})
  // → records: each annotated with its containing region:
  //   [
  //     {
  //       id: "",
  //       shape_external_id: "37",   // e.g. NC
  //       region: {
  //         slug: "south-atlantic",
  //         name: "South Atlantic",
  //         ancestor_slugs: ["south"],
  //         ancestor_names: ["South"]
  //       }
  //     },
  //     ...
  //   ]
  // Unmatched rows (shape_external_id outside the region group) are
  // returned in a separate unmatched array.

Each row now carries both levels: the leaf (sub-region) and the full ancestor chain. Pivot in either direction.

Step 6 — Aggregate + render

Group the tagged records by region slug, count donors, sum donations. Ingest the aggregate as a dataset, render a choropleth.

// Client-side aggregation:
const byRegion = {};
for (const d of tagged_donors) {
  const key = d.region.slug;
  byRegion[key] = (byRegion[key] || 0) + d.amount;
}

mcp.call("ingest_dataset", {
  slug: "donations-by-region-2026q1",
  records: Object.entries(byRegion).map(([region_slug, total]) => ({
    region_slug, total_donations: total,
  })),
  collection: "us-states", version: "tiger-2024",  // match the base
})

mcp.call("render_map", {
  collection: "us-states", version: "tiger-2024",
  dataset: "donations-by-region-2026q1", field: "total_donations",
  palette: "sequential",
  title: "Q1 2026 donations by campaign region",
  subtitle: "Per-region totals, palette by quantile",
})

mcp.call("publish_map", { view_id: …, freeze: true })
  → /v/campaign-2026-donations-q1

What this replaces

Tagging a contact file by hand-rolled territory layout used to mean: export to a spreadsheet, write a state → region VLOOKUP table, hand-update it when the regions shift, re-merge with the contact file every refresh cycle. Mid-cycle re-shaping (a single state moves teams) breaks the linkage; the operations director ends up doing weekly reconciliation. The region group lives in the database; reshape it once and every dataset that tagged against it remains queryable.

Where it scales beyond political

What's actually under the hood

Try it yourself

Free tier — 50 calls / month, no card.

Get an API key

Tools used: clone_region_template, remove_shapes_from_region, define_region, add_shapes_to_region, render_region_group_map, bulk_match_addresses, tag_records_with_region_group, ingest_dataset, render_map, publish_map. The "write" half is premium-gated; reading existing groups stays standard.