# Retrieve Bills

Fetch bills for linked accounts, filter by date and status, check sync progress, and trigger on-demand synchronization.

## 1. Fetch Bills for a Linked Account

Once link exchange returns an `account_link_id`, call `GET /v1/bills` with the `account_link_id` to retrieve bills. Because this is a billable read, send an `Idempotency-Key`. Reuse it only when retrying the same page.

**GET /v1/bills**

**JavaScript**

```javascript
const response = await fetch(
  'https://sandbox.api.billerapi.com/v1/bills?account_link_id=' + linkId,
  {
    headers: {
      'Authorization': `Bearer ${process.env.BILLERAPI_API_KEY}`,
      'Idempotency-Key': crypto.randomUUID(),
    },
  }
);
const data = await response.json();
console.log(data.bills);
// Returns: array of bills with merchant_name, status, canonical total_amount
// ({ value, currency }) and due_date_iso (YYYY-MM-DD).
// This REST endpoint also still returns the deprecated amount/currency/due_date
// trio; read the canonical fields (the 2.0 Node SDK no longer exposes the trio).
// Pagination: has_more, next_cursor
```

**Python**

```python
import requests
import os
import uuid

response = requests.get(
    f'https://sandbox.api.billerapi.com/v1/bills?account_link_id={link_id}',
    headers={
        'Authorization': f'Bearer {os.environ["BILLERAPI_API_KEY"]}',
        'Idempotency-Key': str(uuid.uuid4()),
    },
)
data = response.json()
print(data['bills'])
# Returns: array of bills with merchant_name, status, canonical total_amount
# ({ value, currency }) and due_date_iso (YYYY-MM-DD).
# This REST endpoint also still returns the deprecated amount/currency/due_date
# trio; read the canonical fields (the 2.0 Node SDK no longer exposes the trio).
# Pagination: has_more, next_cursor
```

**Go**

```go
url := fmt.Sprintf(
    "https://sandbox.api.billerapi.com/v1/bills?account_link_id=%s", linkID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("BILLERAPI_API_KEY"))
req.Header.Set("Idempotency-Key", uuid.New().String())

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var data map[string]interface{}
json.NewDecoder(resp.Body).Decode(&data)
// Returns: array of bills with merchant_name, status, canonical total_amount
// ({ value, currency }) and due_date_iso (YYYY-MM-DD).
// This REST endpoint also still returns the deprecated amount/currency/due_date
// trio; read the canonical fields (the 2.0 Node SDK no longer exposes the trio).
```

> **Note**
> Listing bills is a server-to-server operation: authenticate with your API key and scope the query with the durable `account_link_id`. Link-scoped access tokens are accepted only by supported single-resource operations such as bill detail and statements. See the [Getting Started](/docs/guides/getting-started) guide for the full Link flow.

## 2. Filter by Date and Status

Narrow results using query parameters. All filters are optional and can be combined.

**Query Parameters**

| Field | Type | Description |
| --- | --- | --- |
| `account_link_id` * | string | ID of the linked account |
| `start_date` | string | ISO 8601 date — only return bills on or after this date |
| `end_date` | string | ISO 8601 date — only return bills on or before this date |
| `status` | string | Filter by bill status: PENDING, PAID, OVERDUE, CANCELLED |
| `source` | string | Filter by data source: BILLER_DIRECT, SCRAPING, OCR, EMAIL |
| `limit` | number | Max bills per page (default 25, max 100) |
| `cursor` | string | Pagination cursor from a previous response |

**Filtered request**

**JavaScript**

```javascript
const params = new URLSearchParams({
  account_link_id: linkId,
  start_date: '2026-01-01',
  end_date: '2026-03-31',
  status: 'PENDING',
  limit: '50',
});

const response = await fetch(
  `https://sandbox.api.billerapi.com/v1/bills?${params}`,
  {
    headers: {
      'Authorization': `Bearer ${process.env.BILLERAPI_API_KEY}`,
    },
  }
);
const { bills, has_more, next_cursor } = await response.json();
```

**Python**

```python
response = requests.get(
    'https://sandbox.api.billerapi.com/v1/bills',
    params={
        'account_link_id': link_id,
        'start_date': '2026-01-01',
        'end_date': '2026-03-31',
        'status': 'PENDING',
        'limit': 50,
    },
    headers={'Authorization': f'Bearer {os.environ["BILLERAPI_API_KEY"]}'},
)
data = response.json()
bills = data['bills']
has_more = data['has_more']
```

**Go**

```go
params := url.Values{}
params.Set("account_link_id", linkID)
params.Set("start_date", "2026-01-01")
params.Set("end_date", "2026-03-31")
params.Set("status", "PENDING")
params.Set("limit", "50")

reqURL := "https://sandbox.api.billerapi.com/v1/bills?" + params.Encode()
req, _ := http.NewRequest("GET", reqURL, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("BILLERAPI_API_KEY"))

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
```

> **Tip**
> Use cursor-based pagination for large result sets. When `has_more` is `true`, pass `next_cursor` as the `cursor` parameter in the next request.

## 3. Check Sync Status

Before fetching bills, you may want to check whether the account's data is up to date. Call `GET /v1/bills/sync` to get the current sync state.

**GET /v1/bills/sync**

**JavaScript**

```javascript
const response = await fetch(
  'https://sandbox.api.billerapi.com/v1/bills/sync?account_link_id=' + linkId,
  {
    headers: {
      'Authorization': `Bearer ${process.env.BILLERAPI_API_KEY}`,
    },
  }
);
const { sync_state } = await response.json();
console.log(sync_state.status);     // "SYNCED" | "SYNCING" | "FAILED"
console.log(sync_state.last_synced); // ISO 8601 timestamp
console.log(sync_state.progress);    // 0-100 when SYNCING
```

**Python**

```python
response = requests.get(
    f'https://sandbox.api.billerapi.com/v1/bills/sync?account_link_id={link_id}',
    headers={'Authorization': f'Bearer {os.environ["BILLERAPI_API_KEY"]}'},
)
sync_state = response.json()['sync_state']
print(sync_state['status'])      # "SYNCED" | "SYNCING" | "FAILED"
print(sync_state['last_synced']) # ISO 8601 timestamp
print(sync_state['progress'])   # 0-100 when SYNCING
```

**Go**

```go
url := fmt.Sprintf(
    "https://sandbox.api.billerapi.com/v1/bills/sync?account_link_id=%s", linkID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("BILLERAPI_API_KEY"))

resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()

var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
// result["sync_state"]["status"] — "SYNCED" | "SYNCING" | "FAILED"
```

## 4. Trigger a Sync

If the data is stale or you need the latest bills immediately, trigger an on-demand sync with `POST /v1/bills/sync/trigger`.

**POST /v1/bills/sync/trigger**

**JavaScript**

```javascript
const response = await fetch(
  'https://sandbox.api.billerapi.com/v1/bills/sync/trigger',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.BILLERAPI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      account_link_id: linkId,
      sync_type: 'INCREMENTAL', // or 'FULL'
      priority: 'normal',
    }),
  }
);
const { sync_task_id, status, estimated_duration } = await response.json();
// Poll GET /v1/bills/sync until status is "SYNCED"
```

**Python**

```python
response = requests.post(
    'https://sandbox.api.billerapi.com/v1/bills/sync/trigger',
    headers={
        'Authorization': f'Bearer {os.environ["BILLERAPI_API_KEY"]}',
        'Content-Type': 'application/json',
    },
    json={
        'account_link_id': link_id,
        'sync_type': 'INCREMENTAL',  # or 'FULL'
        'priority': 'normal',
    },
)
result = response.json()
print(result['sync_task_id'])
# Poll GET /v1/bills/sync until status is "SYNCED"
```

**Go**

```go
body := map[string]string{
    "account_link_id": linkID,
    "sync_type":       "INCREMENTAL", // or "FULL"
    "priority":        "normal",
}
jsonBody, _ := json.Marshal(body)

req, _ := http.NewRequest("POST",
    "https://sandbox.api.billerapi.com/v1/bills/sync/trigger",
    bytes.NewBuffer(jsonBody))
req.Header.Set("Authorization", "Bearer "+os.Getenv("BILLERAPI_API_KEY"))
req.Header.Set("Content-Type", "application/json")

resp, _ := http.DefaultClient.Do(req)
// Parse sync_task_id from response
// Poll GET /v1/bills/sync until status is "SYNCED"
```

> **Tip**
> Use `INCREMENTAL` syncs for day-to-day updates. Reserve `FULL` syncs for initial onboarding or when you suspect missing data.

## 5. Webhook Events

Instead of polling, register a webhook to receive real-time notifications when bills change. The key bill-related events are:

- `bill.created` — a new bill was retrieved
- `bill.updated` — an existing bill changed (status, amount, due date)
- `connection.ready` — a linked account finished its first sync; the payload
  carries `first_sync_completed_at`
- `link.disconnected` — the link stopped syncing and needs attention. This is
  the event to watch for expired credentials and for links dropped by the
  biller; `data.change` says which

> **Note**
> For full webhook documentation including registration, signature verification, retry policy, and all event types, see the [Webhooks](/docs/guides/webhooks) guide.

## Related

- [Bills API Reference](/docs/api/bills) — Full endpoint documentation and response schemas
- [Link Account Guide](/docs/guides/link-account) — Account linking and token exchange
- [Webhooks](/docs/guides/webhooks) — Event types, signatures, and testing
- [Authentication](/docs/guides/authentication) — Access tokens, security, and best practices
