> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deck.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Fetch Latest Bill

> Link several tasks together to fetch utility bills from credentials that contain many sub accounts.

<img src="https://mintcdn.com/decksoftwareinc/dbrKq5gPOifVywqx/images/bill-fetch-hero.png?fit=max&auto=format&n=dbrKq5gPOifVywqx&q=85&s=fccadf3728f146d9f1915493635582e3" alt="Fetched invoices shown as a list next to the structured JSON response" width="2862" height="1648" data-path="images/bill-fetch-hero.png" />

Bill fetching can be a single task: log in and fetch the latest bill. When a credential holds many accounts, a single task returns one combined result, with no way to tell which accounts contributed, which are closed, or where a partial delivery came from.

This guide splits the work into three narrow tasks:

1. **Extract accounts**: runs once per credential. Returns a list of accounts behind a single credential.
2. **Check for a new bill**: runs against a single account. Checks for the latest bill and reports whether a bill newer than your last known date exists.
3. **Fetch latest bill**: per-account download that delivers exactly one bill, with [extraction](/guides/storage#document-extraction) returning its fields as structured data.

Splitting the bill fetch into several tasks gives you a few things:

* **Per-account attribution.** If a bill is missing for an account, there is one run to inspect, not a single long run across every account.
* **Retry granularity.** A failed run is tightly scoped and can be retried on its own.
* **Lower cost when nothing is new.** When there's no new bill, you pay for only the task run rather than deduplication and extraction.
* **A durable account list.** You store the list and drive per-account work from it. If an account disappears from a later discovery run, you notice.

## Prerequisites

This guide assumes you have an agent, a source, and a stored credential; the [Quickstart](/guides/quickstart) covers those steps. The fetch task uses [storage and extraction](/guides/storage), available as an add-on on paid plans.

## Create the tasks

All three tasks belong to the same agent. See [Tasks](/concepts/tasks) for guidance on writing prompts.

<Steps>
  <Step title="Extract accounts">
    Runs once per credential per refresh cycle, typically monthly, or on demand when a user adds or removes accounts on the source. You store the returned list. The task returns metadata only, so leave storage off.

    ```bash Request theme={null}
    curl -X POST https://api.deck.co/v2/tasks \
      -H "Authorization: Bearer sk_live_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Extract accounts",
        "agent_id": "agt_a1b2c3d4...",
        "prompt": "Log in and return the list of active accounts visible on the source. For each account, return the account number exactly as displayed, preserving hyphens, spaces, and leading zeros, not a nickname, service address, or any other identifier. If the source shows a latest bill date next to each account, include it.",
        "input_schema": {
          "type": "object",
          "properties": {}
        },
        "output_schema": {
          "type": "object",
          "required": ["accounts"],
          "properties": {
            "accounts": {
              "type": "array",
              "items": {
                "type": "object",
                "required": ["account_number"],
                "properties": {
                  "account_number": { "type": "string" },
                  "latest_bill_date": { "type": "string", "description": "Latest bill date shown on the account list (YYYY-MM-DD), null when the source does not display one." }
                }
              }
            }
          }
        }
      }'
    ```

    Not every source shows a bill date on the account list, so treat `latest_bill_date` as a hint. For an account with no known date yet, run the task that checks for a new bill, with `last_known_bill_date` set far enough back that any bill counts as new.
  </Step>

  <Step title="Check for a new bill">
    Runs once per account per checking cycle. It takes the account number and your last known bill date, and reports whether the source has anything newer. With storage disabled, runs of this task can't return files, so the task stays read-only regardless of prompt wording.

    ```bash Request theme={null}
    curl -X POST https://api.deck.co/v2/tasks \
      -H "Authorization: Bearer sk_live_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Check for a new bill",
        "agent_id": "agt_a1b2c3d4...",
        "prompt": "Log in and check whether account {account_number} has a bill dated after {last_known_bill_date}. Read the bill listing only; do not open or download any files. Return whether a newer bill exists and the newest bill date shown for the account.",
        "input_schema": {
          "type": "object",
          "required": ["account_number", "last_known_bill_date"],
          "properties": {
            "account_number": { "type": "string" },
            "last_known_bill_date": { "type": "string", "description": "Most recent bill date you have for this account (YYYY-MM-DD)." }
          }
        },
        "output_schema": {
          "type": "object",
          "required": ["account_number", "has_new_bill"],
          "properties": {
            "account_number": { "type": "string", "description": "Echoed from the input so downstream code can address the account." },
            "has_new_bill": { "type": "boolean" },
            "latest_available_bill_date": { "type": "string", "description": "Date of the newest bill shown for this account (YYYY-MM-DD), null when the source does not show one." }
          }
        }
      }'
    ```
  </Step>

  <Step title="Fetch latest bill">
    Runs only for accounts where the previous task returned `has_new_bill: true`. It takes the account number alone; the decision to fetch was already made upstream. Storage and extraction are enabled, and the extraction schema pulls the bill's fields from the file.

    ```bash Request theme={null}
    curl -X POST https://api.deck.co/v2/tasks \
      -H "Authorization: Bearer sk_live_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Fetch latest bill",
        "agent_id": "agt_a1b2c3d4...",
        "prompt": "Log in and download the most recent bill for account {account_number}. Deliver exactly one file, the single most recent bill available for that account. Do not download older bills or bills for any other account.",
        "input_schema": {
          "type": "object",
          "required": ["account_number"],
          "properties": {
            "account_number": { "type": "string" }
          }
        },
        "output_schema": {
          "type": "object",
          "required": ["account_number", "bill_date"],
          "properties": {
            "account_number": { "type": "string", "description": "Echoed from the input." },
            "bill_date": { "type": "string", "description": "Date of the delivered bill (YYYY-MM-DD)." }
          }
        },
        "storage": {
          "enabled": true,
          "extraction": true,
          "extraction_schema": {
            "type": "object",
            "properties": {
              "account_number": { "type": "string", "description": "Account number printed on the bill" },
              "bill_date": { "type": "string", "format": "date", "description": "Bill issue date" },
              "due_date": { "type": "string", "format": "date", "description": "Payment due date" },
              "total_amount": { "type": "number", "description": "Total amount due, including tax" }
            }
          }
        }
      }'
    ```

    Deduplication is optional here: the date input on the previous task already prevents most duplicate downloads. As an extra safeguard, enable [deduplication](/guides/storage#deduplication) keyed on the statement number and account number.
  </Step>
</Steps>

## Run the sequence

You run the tasks from your own code. The task that extracts accounts runs once per cycle, the task that checks for a new bill runs for each account on your schedule, and the task that fetches the bill runs only when a newer one exists. Runs are asynchronous: you receive the output through a `task_run.completed` [event](/events/events) or by polling, as in the [Quickstart](/guides/quickstart#run-a-task). The responses below show the completed runs, trimmed to the relevant fields.

### Discover accounts

Run the task that extracts accounts once per credential per cycle:

```bash Request theme={null}
curl -X POST https://api.deck.co/v2/tasks/task_extract.../run \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_id": "cred_a1b2c3d4...",
    "input": {}
  }'
```

```json Response theme={null}
{
  "id": "trun_a1b2c3d4...",
  "object": "task_run",
  "status": "completed",
  "result": "success",
  "session_id": "sess_x9y8z7...",
  "output": {
    "accounts": [
      { "account_number": "58291-44720", "latest_bill_date": "2026-07-22" },
      { "account_number": "58291-44738", "latest_bill_date": null }
    ]
  }
}
```

Persist the account list keyed by `account_number`, alongside the last known bill date for each account. This stored state is what the per-account calls read from and write back to.

### Check each account

For each account on your schedule (weekly is typical), run the task that checks for a new bill. Pass the `session_id` from the previous run to reuse the open [session](/concepts/sessions) instead of logging in again:

```bash Request theme={null}
curl -X POST https://api.deck.co/v2/tasks/task_check.../run \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_id": "cred_a1b2c3d4...",
    "session_id": "sess_x9y8z7...",
    "input": {
      "account_number": "58291-44720",
      "last_known_bill_date": "2026-07-22"
    }
  }'
```

```json Response theme={null}
{
  "id": "trun_e5f6g7h8...",
  "object": "task_run",
  "status": "completed",
  "result": "success",
  "session_id": "sess_x9y8z7...",
  "output": {
    "account_number": "58291-44720",
    "has_new_bill": true,
    "latest_available_bill_date": "2026-08-18"
  }
}
```

When `has_new_bill` is `false`, record the result and move on. Nothing else runs for that account this cycle.

### Fetch where a new bill exists

When the previous task returns `has_new_bill: true`, run the task that fetches the latest bill with the same session:

```bash Request theme={null}
curl -X POST https://api.deck.co/v2/tasks/task_fetch.../run \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_id": "cred_a1b2c3d4...",
    "session_id": "sess_x9y8z7...",
    "input": {
      "account_number": "58291-44720"
    }
  }'
```

When the run completes, the file is in storage with its extraction inline:

```text theme={null}
GET /v2/task-runs/{run_id}?include=storage
```

```json Response theme={null}
{
  "id": "trun_i9j0k1l2...",
  "object": "task_run",
  "status": "completed",
  "result": "success",
  "output": {
    "account_number": "58291-44720",
    "bill_date": "2026-08-18"
  },
  "storage": [
    {
      "id": "stor_x1y2z3...",
      "object": "storage",
      "file_name": "statement_aug_2026.pdf",
      "file_type": "application/pdf",
      "url": "https://files.deck.co/stor_x1y2z3...?signature=...",
      "extraction": {
        "account_number": "58291-44720",
        "bill_date": "2026-08-18",
        "due_date": "2026-09-11",
        "total_amount": 6925.18
      },
      "purpose": "output"
    }
  ]
}
```

Update the account's stored last known bill date from `bill_date`, so the next check compares against it.

### Session reuse

Passing `session_id` keeps a credential's runs in one open environment, so the agent logs in once per cycle instead of once per call. Sessions close after 10 minutes of inactivity; see [Sessions](/concepts/sessions). The saved login applies to runs of the same credential, so working through a credential's accounts sequentially in one session is the simplest approach. Starting a fresh session per account also works, at the cost of a login per account.

## Schedule the work

The discovery task takes no input, so a monthly [trigger](/guides/triggers) can run it across every credential.

The check and fetch tasks need per-account input, and a trigger has a single `input` shared by every run it creates, so create those runs from your own scheduler. See [Inputs under fan-out](/guides/triggers#inputs-under-fan-out).

## Handle failures

Every run names one account, so a transient failure is one run to retry, not a batch to replay. The run's `errors` array says what went wrong; see [Errors](/api/errors).

If the credential goes `invalid` (a changed password, for example), Deck emits a `credential.invalid` [event](/events/events) so you can prompt the user to re-authenticate. Skip that credential's accounts until it recovers; checks resume from the last known dates you stored.
