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

# Analytics

> Read normalized bank metrics, transactions, positions, screening, and tax cash flow.

Analytics are computed from the current Book every time you request them. The response reflects transaction tags, excluded documents and accounts, excluded positions, organization revenue rules, business-day settings, and screening policy.

## Bank analytics

```http theme={null}
GET /books/{book_id}/analytics
```

Call this after the Book reaches `parse_status: "completed"` and contains bank statement or Plaid data.

<CodeGroup>
  ```python Python theme={null}
  import requests

  API_BASE = "https://api.lendpathway.com/api"
  TOKEN = "pat_your_token_here"
  BOOK_ID = "99cc93e6-1f3f-42b1-9fe4-ba5a95be9c78"

  response = requests.get(
      f"{API_BASE}/books/{BOOK_ID}/analytics",
      headers={"Authorization": f"Bearer {TOKEN}"},
      timeout=60,
  )
  response.raise_for_status()

  analytics = response.json()
  print("True revenue:", analytics["true_revenue"])
  print("Average daily balance:", analytics["average_daily_balance"])
  print("Debt positions:", len(analytics["positions"]))
  ```

  ```javascript JavaScript theme={null}
  const API_BASE = "https://api.lendpathway.com/api";
  const TOKEN = "pat_your_token_here";
  const BOOK_ID = "99cc93e6-1f3f-42b1-9fe4-ba5a95be9c78";

  const response = await fetch(`${API_BASE}/books/${BOOK_ID}/analytics`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });

  if (!response.ok) {
    throw new Error(`${response.status}: ${await response.text()}`);
  }

  const analytics = await response.json();
  console.log({
    trueRevenue: analytics.true_revenue,
    averageDailyBalance: analytics.average_daily_balance,
    positions: analytics.positions.length,
  });
  ```

  ```bash cURL theme={null}
  curl --fail-with-body \
    "https://api.lendpathway.com/api/books/99cc93e6-1f3f-42b1-9fe4-ba5a95be9c78/analytics" \
    -H "Authorization: Bearer pat_your_token_here"
  ```
</CodeGroup>

### How to read the response

The response has several useful levels:

* Book totals such as `true_revenue`, `average_daily_balance`, `total_loan_payments`, and `debt_to_income_ratio`
* `statements`, organized by statement period with an account breakdown inside each period
* `merged_accounts`, containing the transaction history enriched with cleaned tags and position information
* `positions`, containing detected debt relationships and their payment schedules
* `screening_metrics` and `screening_result`, containing the fact sheet and current organization policy decision

All monetary values are dollar amounts. Ratios such as `debt_to_income_ratio` and `holdback_pct` are percentage values, so `12.5` means 12.5%.

## Response models

These models mirror the current public response. Default values are included because older Books can have sparse data.

<AccordionGroup>
  <Accordion title="BookAnalytics">
    ```python theme={null}
    from typing import Any

    from pydantic import BaseModel, Field


    class BookAnalytics(BaseModel):
        statements: list[StatementAnalytics]
        merged_accounts: dict[str, MergedAccount] | None = None
        loan_summary: list[LoanSummary] = Field(default_factory=list)
        positions: list[DebtPosition] = Field(default_factory=list)

        total_deposits: float
        total_withdrawals: float
        total_loan_disbursements: float = 0.0
        total_loan_payments: float = 0.0

        opening_balance: float = 0.0
        closing_balance: float = 0.0
        peak_balance: float = 0.0
        lowest_balance: float = 0.0
        largest_deposit: float = 0.0
        avg_deposit: float = 0.0
        largest_withdrawal: float = 0.0
        avg_withdrawal: float = 0.0
        num_deposits: int = 0
        num_withdrawals: int = 0
        avg_transaction: float = 0.0
        total_days: int = 0
        average_daily_balance: float | None = None
        days_negative_balance: int = 0
        days_under_threshold: int = 0
        low_balance_threshold: float | None = None

        true_revenue: float = 0.0
        num_true_revenue_transactions: int = 0

        nsf_total: float = 0.0
        num_nsf: int = 0
        overdraft_total: float = 0.0
        num_overdraft: int = 0
        owner_transaction_total: float = 0.0
        num_owner_transaction: int = 0
        internal_transfer_total: float = 0.0
        num_internal_transfer: int = 0
        bank_fee_total: float = 0.0
        num_bank_fee: int = 0
        payment_processor_total: float = 0.0
        num_payment_processor: int = 0
        stop_payment_total: float = 0.0
        num_stop_payment: int = 0
        reversal_total: float = 0.0
        num_reversal: int = 0

        num_loan_disbursements: int = 0
        num_loan_payments: int = 0
        debt_to_income_ratio: float | None = None
        num_mca_positions: int = 0
        num_active_mca_positions: int = 0
        num_factoring_positions: int = 0
        num_reversal_credits: int = 0
        num_missed_payments: int = 0
        num_modified_payments: int = 0

        total_mca_daily_remit: float = 0.0
        total_mca_monthly_remit: float = 0.0
        total_mca_paid_net: float = 0.0
        total_mca_holdback_pct: float | None = None

        reconciliation_results: list[ReconciliationResult] = Field(default_factory=list)
        revenue_exclusion_tags: list[str] = Field(default_factory=list)
        excluded_position_ids: list[str] = Field(default_factory=list)
        excluded_document_ids: list[str] = Field(default_factory=list)
        average_statement_metrics: AccountStatementMetrics | None = None
        counterparty_clusters: list[CounterpartyCluster] = Field(default_factory=list)

        deposits_by_weekday: dict[str, float] = Field(default_factory=dict)
        withdrawals_by_weekday: dict[str, float] = Field(default_factory=dict)
        deposit_count_by_weekday: dict[str, int] = Field(default_factory=dict)
        withdrawal_count_by_weekday: dict[str, int] = Field(default_factory=dict)
        bank_holidays: list[dict[str, str]] = Field(default_factory=list)

        most_recent_transaction_date: str | None = None
        most_recent_statement_end_date: str | None = None
        most_recent_mca_disbursement_date: str | None = None
        most_recent_mca_payment_date: str | None = None

        screening_metrics: ScreeningMetrics | None = None
        screening_result: ScreeningResult | None = None
    ```
  </Accordion>

  <Accordion title="Statements and account metrics">
    ```python theme={null}
    from typing import Literal

    from pydantic import BaseModel, Field


    class AccountStatementMetrics(BaseModel):
        account_id: int
        account_name: str
        account_number: str
        document_id: str | None = None
        starting_balance: float
        ending_balance: float
        num_deposits: int
        total_deposits: float
        num_withdrawals: int
        total_withdrawals: float

        is_reconciled: bool
        reconciliation_skipped: bool = False
        discrepancy: float | None = None
        reconciliation_message: str | None = None
        computed_ending_balance: float | None = None
        expected_ending_balance: float | None = None

        loan_disbursements: float = 0.0
        loan_payments: float = 0.0
        average_daily_balance: float | None = None
        days_negative_balance: int = 0
        days_under_threshold: int = 0
        days_in_period: int = 0
        true_revenue: float = 0.0
        num_true_revenue_transactions: int = 0

        nsf_total: float = 0.0
        num_nsf: int = 0
        overdraft_total: float = 0.0
        num_overdraft: int = 0
        owner_transaction_total: float = 0.0
        num_owner_transaction: int = 0
        internal_transfer_total: float = 0.0
        num_internal_transfer: int = 0
        bank_fee_total: float = 0.0
        num_bank_fee: int = 0
        payment_processor_total: float = 0.0
        num_payment_processor: int = 0
        stop_payment_total: float = 0.0
        num_stop_payment: int = 0
        reversal_total: float = 0.0
        num_reversal: int = 0
        debt_to_income_ratio: float | None = None


    class StatementAnalytics(BaseModel):
        document_id: str
        document_ids: list[str] = Field(default_factory=list)
        document_name: str
        document_type: str | None = None
        statement_start_date: str
        statement_end_date: str
        statement_period: str
        accounts: list[AccountStatementMetrics]


    class LoanSummary(BaseModel):
        loan_type: str
        total_disbursements: float
        total_payments: float
        disbursement_count: int
        payment_count: int
        is_excluded: bool = False


    class CounterpartyCluster(BaseModel):
        cluster_id: str
        counterparty: str
        direction: Literal["credit", "debit"]
        total: float
        count: int
        transaction_ids: list[int]


    class ReconciliationResult(BaseModel):
        month: str
        account_name: str
        reconciled: bool
        reconciliation_skipped: bool = False
        discrepancy: float
        attempts_made: int
        reason: str | None = None
    ```

    Each statement normally contains individual account rows plus an `account_id: 0` row named `COMBINED`. The combined row represents the period-wide cash line across included accounts.
  </Accordion>

  <Accordion title="Transactions">
    ```python theme={null}
    from typing import Literal

    from pydantic import BaseModel, Field


    class TransactionPosition(BaseModel):
        position_id: str
        position_name: str
        loan_type: str
        funder_title: str | None = None


    class EnrichedTransaction(BaseModel):
        transaction_id: int
        document_id: str | None = None
        transaction_date: str
        description: str
        amount: float
        transaction_type: Literal["credit", "debit"]
        ledger_balance: float | None = None
        tag: list[str] = Field(default_factory=list)
        position: TransactionPosition | None = None


    class MergedAccount(BaseModel):
        account_number: str
        account_name: str | None = None
        transactions: list[EnrichedTransaction] = Field(default_factory=list)
    ```

    `merged_accounts` is keyed by the account ID serialized as a string. Transaction tags have already been cleaned for credit/debit direction. Qualifying credits also receive the synthetic `true_revenue` tag, and transactions without another tag receive `untagged`.
  </Accordion>

  <Accordion title="Debt positions and payment schedules">
    ```python theme={null}
    from typing import Literal

    from pydantic import BaseModel, Field

    Frequency = Literal["daily", "weekly", "monthly", "irregular"]
    ScheduleState = Literal["active", "closed"]
    EpisodeState = Literal["pending", "active", "closed"]
    EpisodeRole = Literal["initial", "renewal", "stack", "orphan"]
    PositionStatus = Literal["just_funded", "active", "closed"]


    class Disbursement(BaseModel):
        disbursement_id: str
        transaction_ids: list[int] = Field(default_factory=list)
        date: str
        amount: float
        is_merged: bool = False


    class ReversalCredit(BaseModel):
        transaction_id: int
        date: str
        amount: float
        description: str = ""
        attributed_schedule_id: str | None = None


    class Miss(BaseModel):
        evidence: Literal["reversal", "gap"]
        date: str | None = None
        date_window: tuple[str, str] | None = None
        expected_amount: float
        count_estimate: int = 1
        reversal_txn_id: int | None = None
        severity: Literal["single", "multi", "long"] = "single"
        schedule_id: str | None = None


    class Modification(BaseModel):
        date: str
        before_amount: float
        after_amount: float
        delta_pct: float
        type: Literal["cross_stream", "within_stream"]
        schedule_id: str


    class PaymentSchedule(BaseModel):
        schedule_id: str
        transaction_ids: list[int] = Field(default_factory=list)
        avg_amount: float = 0.0
        frequency: Frequency = "irregular"
        pull_day: str | None = None
        remit_daily: float | None = None
        amount_variance: float = 0.0
        is_holdback_style: bool = False
        first_payment: str = ""
        last_payment: str = ""
        payment_count: int = 0
        total_paid: float = 0.0
        total_paid_net: float = 0.0
        term_est_days: int | None = None
        state: ScheduleState = "active"
        misses: list[Miss] = Field(default_factory=list)
        modifications: list[Modification] = Field(default_factory=list)


    class Episode(BaseModel):
        episode_id: str
        advance: Disbursement | None = None
        schedules: list[PaymentSchedule] = Field(default_factory=list)
        role: EpisodeRole = "initial"
        state: EpisodeState = "pending"
        first_payment: str | None = None
        last_payment: str | None = None


    class PositionTransaction(BaseModel):
        transaction_id: str
        document_id: str | None = None
        date: str
        description: str
        amount: float
        type: str


    class DebtPosition(BaseModel):
        position_id: str
        name: str
        loan_type: str
        is_excluded: bool = False

        funder_uuid: str | None = None
        funder_title: str | None = None
        funder_link: str | None = None
        funder_contact: str | None = None
        funder_email: str | None = None
        transactions: list[PositionTransaction] = Field(default_factory=list)

        total_disbursements: float = 0.0
        total_payments: float = 0.0
        transaction_count: int = 0
        disbursement_count: int = 0
        payment_count: int = 0
        avg_disbursement: float = 0.0
        avg_payment: float = 0.0
        first_disbursement_date: str | None = None
        last_disbursement_date: str | None = None
        last_payment_date: str | None = None
        returns_count: int = 0
        returns_total: float = 0.0

        episodes: list[Episode] = Field(default_factory=list)
        reversal_credits: list[ReversalCredit] = Field(default_factory=list)
        status: PositionStatus = "closed"
        daily_remit_burden: float = 0.0
        monthly_remit_burden: float = 0.0
        holdback_pct: float | None = None
        n_active_schedules: int = 0
        n_active_episodes: int = 0
        est_total_payback: float | None = None
        total_paid_net: float = 0.0
        progress_pct: float | None = None
        has_renewal: bool = False
        has_stack: bool = False
        potential_missed_payments: list[Miss] = Field(default_factory=list)
        potential_modified_payments: list[Modification] = Field(default_factory=list)
    ```

    An episode represents one observed advance and its payment streams. `renewal` and `stack` describe how a later advance relates to earlier active schedules. `orphan` means payments are visible but the original advance predates the statement window.
  </Accordion>

  <Accordion title="Screening">
    ```python theme={null}
    from typing import Any, Literal

    from pydantic import BaseModel, Field


    class ScreeningMetrics(BaseModel):
        avg_daily_balance: float | None = None
        negative_days: float | None = None
        days_under_threshold: float | None = None
        true_revenue: float | None = None
        avg_monthly_revenue: float | None = None
        avg_monthly_deposits: float | None = None
        total_loan_payments: float | None = None
        debt_to_income_ratio: float | None = None
        closing_balance: float | None = None
        opening_balance: float | None = None
        total_days: float | None = None
        lowest_balance: float | None = None
        num_deposits: float | None = None
        num_withdrawals: float | None = None
        num_true_revenue_transactions: float | None = None
        num_mca_positions: float | None = None
        time_in_business_in_days: float | None = None
        account_holder_ownership_percentage: float | None = None
        requested_loan_amount: float | None = None
        most_recent_month_revenue: float | None = None
        most_recent_month_negative_days: float | None = None
        most_recent_month_avg_daily_balance: float | None = None
        most_recent_month_deposits: float | None = None
        most_recent_month_loan_payments: float | None = None
        num_nsf: float | None = None
        num_overdraft: float | None = None
        num_active_mca_positions: float | None = None
        num_factoring_positions: float | None = None
        days_since_last_transaction: float | None = None
        days_since_last_statement_end: float | None = None
        days_since_last_mca_disbursement: float | None = None
        days_since_last_mca_payment: float | None = None
        total_deposits: float | None = None
        total_withdrawals: float | None = None
        avg_monthly_withdrawals: float | None = None
        avg_monthly_deposit_count: float | None = None
        avg_monthly_negative_days: float | None = None
        avg_monthly_loan_payments: float | None = None
        nsf_total: float | None = None
        overdraft_total: float | None = None
        num_reversal_credits: float | None = None
        num_missed_payments: float | None = None
        num_modified_payments: float | None = None
        business_name: str | None = None
        state_code: str | None = None
        industry: str | None = None


    class ResolvedScreeningRule(BaseModel):
        target_type: Literal["state", "industry", "all"]
        target_value: str
        equation: dict[str, Any] | None = None
        deny_all: bool = False
        lendsaas_decline_reason_ids: list[str] | None = None
        result: Literal["PASS", "REJECT"]
        reason: str
        rule_as_variables: str
        rule_after_substituting: str


    class ScreeningResult(BaseModel):
        result: Literal["PASS", "REJECT"]
        num_passed: int
        num_total: int
        resolved_rules: list[ResolvedScreeningRule] = Field(default_factory=list)
    ```

    `screening_result` can be absent when screening is disabled or when a fact sheet cannot be built. A missing metric causes the individual rule that needs it to pass.
  </Accordion>
</AccordionGroup>

## Account-first statement view

```http theme={null}
GET /books/{book_id}/statements
```

This view groups data by account and then by month. It includes daily balance maps and monthly lender activity. It does not apply organization-level document, account, or revenue exclusions.

```python theme={null}
from typing import Any

from pydantic import BaseModel, Field


class SimpleFunder(BaseModel):
    position_id: str
    name: str
    loan_type: str
    funder_uuid: str | None = None
    funder_title: str | None = None
    funder_link: str | None = None
    funder_contact: str | None = None
    funder_email: str | None = None
    transaction_count: int
    total_disbursements: float
    total_payments: float
    funded_date: str | None = None
    first_payment_date: str | None = None
    last_payment_date: str | None = None
    payment_count: int = 0
    avg_payment_amount: float | None = None
    payment_frequency: str | None = None


class SimpleStatement(BaseModel):
    document_id: str
    document_name: str
    statement_start_date: str
    statement_end_date: str
    starting_balance: float
    ending_balance: float
    min_balance: float = 0.0
    max_balance: float = 0.0
    sum_credits: float
    sum_debits: float
    net_deposits: float
    num_deposits: int
    num_withdrawals: int
    average_daily_balance: float | None = None
    days_negative_balance: int = 0
    days_in_period: int = 0
    revenue_credits: float = 0.0
    loan_disbursements: float = 0.0
    total_mca_disbursements: float = 0.0
    loan_payments: float = 0.0
    debt_to_income_ratio: float | None = None
    transactions: list[dict[str, Any]]
    daily_balances: dict[str, float] = Field(default_factory=dict)
    funders: list[SimpleFunder] = Field(default_factory=list)


class SimpleAccount(BaseModel):
    account_id: int
    account_name: str
    account_number: str
    bank_name: str
    routing_number: str | None = None
    account_type: str
    business_name: str
    business_address: dict[str, Any] | None = None
    statements: list[SimpleStatement]
```

## Tax cash-flow analysis

```http theme={null}
GET /books/{book_id}/tax-analytics
```

Returns qualifying-income analysis derived from the tax forms stored on the Book.

```python theme={null}
from typing import Any, Literal

from pydantic import BaseModel, Field


class TaxEntity(BaseModel):
    name: str
    tin: str | None = None
    address: str | None = None


class IncomeSource(BaseModel):
    source_type: Literal["wages", "schedule_c", "partnership", "s_corp"]
    source_name: str
    entity: TaxEntity | None = None
    ownership_pct: float | None = None
    ordinary_income: float = 0
    rental_income: float = 0
    guaranteed_payments: float = 0
    depreciation_addback: float = 0
    other_income: float | None = None
    other_deductions: float | None = None
    subtotal: float = 0
    has_k1: bool = False
    has_return: bool = False
    k1_return_match: bool | None = None


class ReconciliationWarning(BaseModel):
    entity_name: str
    warning_type: Literal[
        "missing_k1",
        "missing_return",
        "income_mismatch",
        "missing_from_schedule_e",
    ]
    message: str


class ParsedFormField(BaseModel):
    key: str
    label: str
    value: Any
    format: Literal["currency", "percent", "text", "count"]


class ParsedFormCard(BaseModel):
    title: str
    entity_name: str | None = None
    entity_tin: str | None = None
    badge: str | None = None
    fields: list[ParsedFormField] = Field(default_factory=list)


class TaxYearAnalysis(BaseModel):
    tax_year: int
    borrower: TaxEntity | None = None
    wage_sources: list[IncomeSource] = Field(default_factory=list)
    schedule_c_sources: list[IncomeSource] = Field(default_factory=list)
    partnership_sources: list[IncomeSource] = Field(default_factory=list)
    s_corp_sources: list[IncomeSource] = Field(default_factory=list)
    schedule_e_entity_count: int = 0
    k1_count: int = 0
    warnings: list[ReconciliationWarning] = Field(default_factory=list)
    parsed_forms: list[ParsedFormCard] = Field(default_factory=list)
    total_wages: float = 0
    total_schedule_c: float = 0
    total_partnership: float = 0
    total_s_corp: float = 0
    total_depreciation_addback: float = 0
    total_qualifying_income: float = 0
    reported_agi: float | None = None


class TaxCashFlowAnalysis(BaseModel):
    years: list[TaxYearAnalysis] = Field(default_factory=list)
    most_recent_year: int | None = None
    avg_qualifying_income: float | None = None
```

## Response conditions

| Status | Meaning                                                                  |
| ------ | ------------------------------------------------------------------------ |
| `400`  | The Book has not completed parsing or does not contain the required data |
| `401`  | Missing or invalid PAT                                                   |
| `403`  | The token cannot access this Book                                        |
| `404`  | The Book does not exist in the token's organization                      |

For practical selection and aggregation patterns, continue with [Working with analytics data](/cookbook/working-with-analytics).
