# Pollers

> Reach systems without webhooks: call an API on a schedule and emit events when what it sees changes.

A poller is a source that calls an API on a schedule and emits events when what it sees is new or has changed. Its request uses the same shape as the [API request action](/docs/actions).

```json
{"kind": "poll", "name": "status-page",
 "poll": {
   "request": {"method": "GET", "url": "https://status.example.com/api/v2/components.json"},
   "select": {"path": "components", "fields": {"id": "id", "name": "name", "status": "status"}},
   "emit": {"type": "status.component", "key": "{{item.id}}", "when": "changed", "compare": ["status"]},
   "interval": "PT5M"}}
```

Create it with `configure` (`{"sources": [<the above>]}`) or `POST /v1/sources/poll`.

| Part | What it does |
| --- | --- |
| `request` | Method, URL, headers, `auth` (`{"bearer": "secret:X"}`, `{"headers": {...}}`, or `{"connection": "github"}` to reuse a connected source's credential), and a JSON `body`. Templates: `{{row.*}}`, `{{poll.last_polled_at}}`. With `{"connection": "github"}`, Coordinate uses the GitHub App installation for the account the URL names (`/repos/{owner}/…`), for any endpoint the app's permissions cover: metadata (stars, releases, topics), contents, pull requests, checks, and Actions. Reads (`GET`) of public repositories the app isn't installed on use Coordinate's own GitHub credentials, so watching any public repository needs no token. Only a private repository the app can't reach needs `{"bearer": "secret:NAME"}` with a personal access token. |
| `for_each` | Optional. `{sql}` over the log: each row (up to 200) fills the request template, so the log decides what to poll. Or `{values: [...]}` for a fixed list: objects are rows as given, and plain values become `{value}` (`{{row.value}}`). |
| `format` | `json` (default), `feed` (RSS/Atom entries), or `page` (the page's visible text). |
| `select` | `path` to the items in the response. Omitted or empty, it's the whole response: a top-level list is the items, and a single object is one item. `entries: true` turns an object keyed by name (like npm's bulk endpoint) into one item per key, `{key, ...value}`. `fields`: names to item paths, or templates like `"https://{{item.url}}"`. |
| `derive` | Extra boolean fields, each a [condition](/docs/triggers) over the fields. |
| `emit` | `type` (templates allowed), `key` naming each item, and `when`: `new` (keys not seen before) or `changed` (the `compare` fields differ from the last emit). `occurred_at` names the field that holds when it happened; without it, the poll time is used. |
| `settle` | `{while: Condition, retry: ["PT10S", ...]}`: while a value is still settling, hold the emit and look again on that backoff. |
| `nudge_on` | `[{event: Matcher, match: {column: "payload.path"}}]`: a matching event polls the rows it is about right away. |
| `interval` | How often to poll everything (`PT1M` to `P7D`). |

The first poll is a quiet baseline: what's already there is recorded as history, which never fires triggers. Every event's `recorded_at` is when Coordinate stored it; `occurred_at` is when it happened, so for a time window over feed items or other imported history, filter on `occurred_at`. Feed entries use their published date, and Vercel deployments their creation time. To check a poller now instead of waiting, run `coordinate poll <source>` (or the `poll_source` tool).

## Built-in pollers

- **GitHub readiness** (on with the GitHub connector): `for_each` open pull requests from the log, a GraphQL request per pull request, `derive` *ready*, `settle` while mergeability is still being computed, and nudges from pull request, review, check, status, and push events.
- **Web pages and feeds**: `format` feed or page.
- **Vercel**: the deployments API, one event per deployment and state.
