Bank analytics
GET /books/{book_id}/analytics
parse_status: "completed" and contains bank statement or Plaid data.
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"]))
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,
});
curl --fail-with-body \
"https://api.lendpathway.com/api/books/99cc93e6-1f3f-42b1-9fe4-ba5a95be9c78/analytics" \
-H "Authorization: Bearer pat_your_token_here"
How to read the response
The response has several useful levels:- Book totals such as
true_revenue,average_daily_balance,total_loan_payments, anddebt_to_income_ratio statements, organized by statement period with an account breakdown inside each periodmerged_accounts, containing the transaction history enriched with cleaned tags and position informationpositions, containing detected debt relationships and their payment schedulesscreening_metricsandscreening_result, containing the fact sheet and current organization policy decision
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.BookAnalytics
BookAnalytics
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
Statements and account metrics
Statements and account metrics
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
account_id: 0 row named COMBINED. The combined row represents the period-wide cash line across included accounts.Transactions
Transactions
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.Debt positions and payment schedules
Debt positions and payment schedules
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)
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.Screening
Screening
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.Account-first statement view
GET /books/{book_id}/statements
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
GET /books/{book_id}/tax-analytics
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 |