# `BankingCircle.Reporting`
[🔗](https://github.com/iamkanishka/banking_circle/blob/main/lib/banking_circle/reporting.ex#L25)

Asynchronous report generation: request a report, poll its status, then
download it once ready — the three-step flow Banking Circle uses for
reports too large to return synchronously (reconciliation, account
activity, rejections, bank statements, camt.053, etc).

## The flow

    POST /api/v2/reports/requests/{report-type}    -> 202, Location header with request id
    GET  /api/v2/reports/{request-id}/get-status    -> 202 Processing (Retry-After) | 302 Complete (Location: reportId)
    GET  /api/v1/reports/download-report/{reportId} -> the report body

`request_report/3` performs step 1 and returns just the request id
(read from the `Location` response header, per the documented flow).
`poll_status/2` performs one status check. `fetch_report/3` drives the
whole flow for you, sleeping between polls per the server's
`Retry-After` guidance (falling back to a fixed interval if absent) up
to a caller-supplied timeout — this is almost always what you want
unless you're building your own background job around the individual
steps.

## Report type identifiers

Accepts either a known atom (translated to the slug Banking Circle
expects — see BankingCircle.Reporting.ReportType.known/0) or a raw
string, so you're not blocked on us keeping an exhaustive list current:
pass e.g. `"held-payments-sanctions-screening"` directly if we haven't
added a matching atom yet.

# `client`

```elixir
@type client() :: atom()
```

# `report_type`

```elixir
@type report_type() :: atom() | String.t()
```

# `download`

```elixir
@spec download(String.t(), client()) ::
  {:ok, term()} | {:error, BankingCircle.Error.t()}
```

Downloads a generated report by its report id (obtained from
`poll_status/2`'s `{:complete, report_id}`).

# `fetch_report`

```elixir
@spec fetch_report(report_type(), map(), keyword()) ::
  {:ok, term()} | {:error, :timeout | BankingCircle.Error.t()}
```

Drives the full request → poll → download flow, blocking (via
`Process.sleep/1` between polls) until the report is ready or
`timeout_ms` elapses.

For a web request handler, prefer `request_report/3` plus a background
job that calls `poll_status/2` on a schedule instead of blocking a
request process for a potentially multi-minute report generation —
`fetch_report/3` is meant for scripts, IEx sessions, and background
workers that already expect to block.

# `poll_status`

```elixir
@spec poll_status(String.t(), client()) ::
  {:processing, non_neg_integer() | nil}
  | {:complete, String.t()}
  | {:error, BankingCircle.Error.t()}
```

Checks the status of a previously-requested report.

Returns `{:processing, retry_after_ms}`, `{:complete, report_id}`, or
`{:error, %BankingCircle.Error{}}`.

# `reconciliation_report`

```elixir
@spec reconciliation_report(
  keyword(),
  client()
) :: {:ok, term()} | {:error, BankingCircle.Error.t()}
```

Fetches the **Standard Reconciliation Report synchronously**
(`GET /api/v1/reports/reconciliation-report`) — a direct alternative to
the async `request_report/3` → `poll_status/2` → `download/2` flow,
specifically for reconciliation. Handles up to 50,000 payments per
report inline; for larger date ranges or other report types, use the
async flow (`fetch_report/3` or the individual steps) instead.

# `request_report`

```elixir
@spec request_report(report_type(), map(), client()) ::
  {:ok, String.t()} | {:error, BankingCircle.Error.t()}
```

Kicks off report generation and returns `{:ok, request_id}` — extracted
from the response's `Location` header, per the documented flow (the
request id is not in the response body). Pass the id to `poll_status/2`,
or just use `fetch_report/3` to handle the whole flow.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
