Help

DevelopersGetting startedHow to

API quickstart

Get an API key, make your first call to /v1/bill-search, follow the pages to the end of a result, and read your tracked items.

Applies toEditor license

Open in CapitolMath

This gets you from no key to every page of a real result: an API key, a first call to /v1/bill-search, and a loop that follows the pages to the end. Every plan includes the REST API, the free Browse plan too. The endpoint-by-endpoint detail is in the API reference.

Get a key#

API keys are created by members with a Full license, on their own profile.

  1. Open My profile from the menu under your organization's name, and go to Your API keys.

  2. Give the key a New key label — production, say — and leave Read the API ticked under This key may.

  3. Select Create API key and copy the key. It starts vd_live_ and is shown only once.

Keep it somewhere safe, such as an environment variable — the examples below read it from CAPITOLMATH_API_KEY. See Creating an API key for more on keys and their permissions.

Make your first call#

Send the key as a bearer token. This asks for California's housing bills that passed, 25 at a time:

curl -H "Authorization: Bearer $CAPITOLMATH_API_KEY" \
  "https://capitolmath.ai/v1/bill-search?location=CA&topic=housing&outcome=passed&page-size=25"

location is required — a two-letter state code, or US for Congress. topic and outcome are two of many optional filters; the reference lists them all, and its Try it live button runs any endpoint against real data.

Read the response#

A list endpoint returns the page of records in data and where you are in pagination:

{
  "pagination": {
    "page": 1, "page_size": 25, "returned_count": 25,
    "total_count": 140, "total_pages": 6,
    "next_url": "https://capitolmath.ai/v1/bill-search?location=CA&topic=housing&outcome=passed&page-size=25&page=2",
    "prev_url": null
  },
  "data": [
    {
      "session": 2026, "bill_number": "…", "bill_name": "…",
      "topics": ["housing"], "outcome": "Passed", "furthest_stage": "executive_signature",
      "location": "CA",
      "detail_url": "https://capitolmath.ai/v1/bill-detail/CA/2026/…"
    }
  ]
}

The response above is shortened, and its counts are illustrative.

Each bill's detail_url fetches it in full — summary, sponsors, scores and roll calls — from /v1/bill-detail.

Follow the pages#

Pages are numbered from 1. Follow pagination.next_url — a complete URL — until it's null:

url="https://capitolmath.ai/v1/bill-search?location=CA&topic=housing&outcome=passed"
while [ "$url" != "null" ]; do
  page=$(curl -s -H "Authorization: Bearer $CAPITOLMATH_API_KEY" "$url")
  echo "$page" | jq -c '.data[] | {bill_number, outcome}'
  url=$(echo "$page" | jq -r '.pagination.next_url')
done

Without page-size, a page is as large as your plan allows. Each page is one request against your organization's monthly quota, so larger pages use less of it.

Run your saved views#

The views your organization saves in the app run from code too, by name — the same results the app shows, without rebuilding the filters as parameters:

Saved onEndpointReturns
Legislation/v1/saved-view-searchThe bills a bill view or list matches
Elected Officials/v1/saved-view-official-searchThe legislators a legislator view matches
Legislation/v1/saved-view-event-searchThe meetings with bills from bill views on the agenda
Calendar/v1/saved-calendar-view-searchThe meetings a calendar view matches — its window, locations and Legislation included
Tracked items/v1/tracked-items?saved-view=The tracked items a Tracked items view lists, as the key's creator sees them (/v1/saved-view-search returns their bills; add kind=tracked when a Legislation view has the same name, which otherwise wins)

Pass one name as saved-view=Recently%20updated%20housing%20bills, or several as saved-view=[A,B]. A key runs only the views its creator can see; name one it can't, and the error lists those it can. The API reference has each endpoint's parameters.

Read your tracked items#

/v1/tracked-items lists your organization's tracked bills as the key's creator sees them on Tracking › Tracked items: the same items, filters and order, with each item's position, priority, status, owner, watchers, tags and custom fields.

curl -H "Authorization: Bearer $CAPITOLMATH_API_KEY" \
  "https://capitolmath.ai/v1/tracked-items?owner=me&watcher=me&position=oppose"

On the page, REST API shows this request for the filters you've set. Like the page, it starts with items in an open status; add status=all for every status. Status, tag, client and member ids come from the items themselves, and me stands for the key's creator. /v1/tracked-items/{id} returns one item.

/v1/bill-search carries the same tracking on every bill: each row's tracking lists your tracked items for it (one per client, and your own work), and tracked=true keeps only the bills you track.

A key never reaches past its creator. Their permissions are read on every call, so they see what they'd see in the app, Read All included, and a client member's key sees only their client's items and what clients are shown. Custom fields made inactive are still read here, marked "inactive": true. Reading never marks an item read.

With a key that may Change tracked items, the same endpoint tracks a bill, and PATCH /v1/tracked-items/{id} changes an item's fields, as the tracked item panel does:

curl -X PATCH -H "Authorization: Bearer $CAPITOLMATH_API_KEY" -H "Content-Type: application/json" \
  -d '{"position": "oppose", "status": "<status id>", "reason": "Amended in committee"}' \
  "https://capitolmath.ai/v1/tracked-items/<item id>"

Each change is checked as if you made it in the app: reassigning takes being the item's owner, a field your organization has turned off or an inactive custom field is refused, and each change lands in the item's activity, runs your automation rules and is in the audit log under the key's name.

When something goes wrong#

Errors come back with a standard status code and the same envelope:

{ "error": { "code": "invalid_request", "message": "Parameter 'location' is required, e.g. location=TX, location=[TX,US] or location=ALL." } }

A 400 means a parameter is missing, mistyped or not one the endpoint takes: a misspelled filter is refused rather than ignored, so it can't quietly widen your results. A 401 means the key is missing or not valid; a 429 means you've hit the per-minute rate limit or the monthly quota. Authentication and permissions covers both, and the API reference lists every error code.