# Making requests

## Response format

Successful responses are JSON with `data`, `meta`, and `links`. Fields we add later appear without notice, so ignore fields you don't recognize.

- **Dates** are `YYYY-MM-DD`; timestamps are ISO 8601 in UTC. Date filters take `from` and `to` (inclusive).
- **Members** are identified by ward number (1-50). Members who left during the term use 100 + ward (for example `135`).
- **Legislation** can be looked up by the Clerk's matter id or its record number (`O2026-0028502`).
- **Votes** are spelled out: `yea`, `nay`, `absent`, `recused`, `excused`, `not_voting`, `not_in_office`.
- **Links**: every record has `links.page` (to cite), `links.markdown` (to read), and `links.api` (to follow).
- **AI-written fields** (`aiSummary`, `impactReport`, `aiProfile`, meeting `notes`, `answer`) are labeled and point to their sources. All counts are computed from City data.
- **Freshness**: `meta.dataUpdated` says when the underlying data was last refreshed.

## Pagination

Lists return up to `limit` items (default 50, maximum 200). When there are more, `meta.nextCursor` and `links.next` are set; request `links.next` until it is missing. `meta.total` is the total number of matches.

```javascript
async function getAll(url) {
  const items = [];
  while (url) {
    const res = await fetch(url, { headers: { Authorization: 'Bearer ' + process.env.CHICAGO_POLITICS_KEY } });
    if (!res.ok) throw new Error((await res.json()).detail);
    const body = await res.json();
    items.push(...body.data);
    url = body.links.next; // missing on the last page
  }
  return items;
}

const nays = await getAll('https://www.chicagopolitics.org/api/v1/members/12/votes?vote=nay&limit=200');
```

```python
import os
import requests

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['CHICAGO_POLITICS_KEY']}"

def get_all(url):
    items = []
    while url:
        r = session.get(url)
        r.raise_for_status()
        body = r.json()
        items += body["data"]
        url = body["links"].get("next")  # missing on the last page
    return items

nays = get_all("https://www.chicagopolitics.org/api/v1/members/12/votes?vote=nay&limit=200")
```

## Caching and CORS

Responses send `Cache-Control: private, max-age=60`, and most data changes at most daily (see [data sources](https://www.chicagopolitics.org/developers/data-sources)), so cache responses on your side. CORS is open (`Access-Control-Allow-Origin: *`) and the rate-limit headers are exposed to browsers, but keep keys on a server.

---
Source: https://www.chicagopolitics.org/developers/requests · Chicago Politics, an independent, nonpartisan tracker of the Chicago City Council. Guide for agents: https://www.chicagopolitics.org/llms.txt
