Partner Credit Lines
DayZero keeps the credit ledger for a partner's card-backed lending product. The partner builds the merchant interface, executes every movement of money through its own processor, and owns the lending decision. DayZero recognises each posted card transaction, originates the loan, computes fees, interest, balances and payoff figures, allocates payments, closes the month, produces statements, posts the accounting, and pushes available credit back to the card issuer as a spend limit.
Nothing here moves money. Read every "payment" below as recording and calculation, never as a transfer.
Every amount is in integer cents.
4545000= $45,450.00. Every figure the merchant sees is returned by the API; never derive balances, due dates, or statuses in the client.
Connecting
https://api.ondayzero.com/api/v1/credit/...
Authorization: Bearer dz_your_token_here # token type "api"
x-business-id: YOUR_BUSINESS_ID # the operator (lender) business
Content-Type: application/jsonTokens are issued under Settings › Developers. The credit surface sits behind the CFO Suite add-on and returns 403 until it is enabled for the business.
Every response is wrapped: { "success": true, "message": null, "code": null, "data": { ... } }. Unwrap data before use.
| Status | Meaning | Handling |
|---|---|---|
400 |
Invalid payload, unknown filter value, a rule the program has not decided | Correct the request |
401 |
Expired token, or an MCP OAuth token used against REST — the two are indistinguishable in the response | Do not retry. Use an API token from Settings › Developers |
403 |
Add-on not enabled for the business; a credit-scoped token outside /api/v1/credit; an API token on an operator action (below) |
Escalate; not retryable |
404 |
No such line, loan, payment, or assessment. For a line, this is how the "no credit line yet" state is detected | Check the id |
409 |
Idempotency conflict | See the call's notes |
5xx |
Transient | Retry with backoff (below) |
Timeouts and retries
Requests complete well inside 30 seconds; treat anything longer as a timeout. On a timeout or 5xx, retry with exponential backoff (1s, 2s, 4s, 8s, 16s, five attempts). Retrying is safe on every write in this guide because each is idempotent on a caller-supplied key: settlements on external_settlement_id, payments on external_payment_id, card issue on idempotency_key, and every internal sweep per loan per day. Show the merchant the last figures you have with their as_of timestamp rather than zeroes while a call is failing.
Operator actions
Four endpoints are operator actions performed in the DayZero console by a signed-in person, not calls your product makes: POST /credit/programs, PATCH /credit/programs/{id}, POST /credit/lines/{id}/loans/import, and POST /credit/loans/{loan_id}/write-off. An API token receives 403 on them whoever created it. Tell us and we run them.
The /api/v1/loans/ endpoints are a different product — a company's own borrowings (the business-loan subledger) — and are not part of this API.
Pagination
Page sizes and the paging mechanism differ by endpoint:
| Endpoint | Default | Max | Paging |
|---|---|---|---|
GET /credit/programs, GET /credit/lines |
100 | 500 | offset / limit |
GET /credit/lines/{id}/subledger (loan rows) |
200 | 1,000 | cursor |
GET /credit/lines/{id}/loans |
100 | 1,000 | cursor |
GET /credit/lines/{id}/payments |
50 | 500 | cursor |
GET /credit/events |
100 | 500 | after (sequence number) |
GET /credit/lines/{id}/ledger |
500 | 5,000 | after (sequence number) |
Loans, payments, events, and ledger rows return items, next_cursor, has_next, and limit. Pass next_cursor back as cursor (or after on the event feed and ledger). has_next looks one row past the page, so a full last page is not "more". The loan page inside the subledger response carries the same cursor under loans, loans_next_cursor, and loans_has_next.
total exists on one endpoint only: GET /credit/lines/{id}/loans, and only when include_total_count=true is passed, because it costs a count query. No other list response carries a total.
GET /credit/programs and GET /credit/lines are offset/limit and return a JSON array (not { items, next_cursor }). For a merchant dashboard, call GET /credit/lines/{id} — do not list every line.
Lending models
A program is one partner's configuration: the parameter envelope, the policy positions, statement branding, GL posting, and card issuing. Lines open under a program and inherit from it. The program's lending_model chooses how credit is billed:
| Model | Unit of borrowing | Interest |
|---|---|---|
per_transaction |
Every posted card transaction is its own loan with its own term, due date, and disbursement fee | Accrues daily on remaining principal from the day after the loan's due date, applied to the balance at each month-end close |
cycle_draw |
Settlements accumulate into a monthly cycle that resolves into one draw at close | Computed once at close for the full term |
The rest of this guide describes per_transaction, which is the default for new programs.
curl -X POST https://api.ondayzero.com/api/v1/credit/programs \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"slug": "tie",
"display_name": "Tie Capital",
"status": "active",
"lending_model": "per_transaction",
"allowed_term_days": [30, 45, 60, 90],
"default_rate_bps": 2000,
"default_disbursement_fee_bps": 100,
"allocation_order": "interest_fee_principal",
"loan_ordering": "oldest_first",
"minimum_payment_cents": 2500,
"due_soon_days": 3,
"early_payoff_terms": {"interest_rebate": "none", "fee_rebate": "none"},
"legal_lending_entity": "Revenue Roll Lending dba Tie Capital",
"statement_branding": {"accent_hex": "#5B21B6", "footer_text": "Questions? support@example.com"},
"covenants": {"cash_covenant_bps": 5000}
}'Program parameters
| Field | Meaning |
|---|---|
allowed_term_days |
Payback terms a line may select. Applied per loan from its posting date |
default_rate_bps |
Annual rate. 2000 = 20% APR, applied as a daily rate of rate ÷ 365 |
default_disbursement_fee_bps |
Fee assessed once at origination on each transaction. Overridable per line |
allocation_order |
Bucket order a payment fills within one loan: interest_fee_principal (default), principal_fee_interest, fee_interest_principal |
loan_ordering |
Which open loan a payment reaches first: oldest_first (default), earliest_due_first, newest_first |
interest_accrual_start |
due_date (default: no interest inside the term) or origination |
minimum_payment_cents |
Minimum per-loan payment on the per-row Pay action; the outstanding amount when smaller |
late_terms |
Optional: grace_days, day_count (actual_365 default), cap_cents or cap_pct_of_principal_bps on total interest |
interest_cap_pct_of_principal_bps |
Optional ceiling on total interest per loan as a share of its principal. Unset by default, so interest accrues until the loan is repaid (D-07). 2000 (20%) means a $12,000 loan never accrues more than $2,400. The lower of this and any late_terms cap applies |
card_management |
dayzero (default): DayZero provisions cards and pushes available credit as the spend limit. partner: you provision cards in the issuer, register them with POST /credit/cards/register, and push limits yourself from credit.line.limit_changed |
event_delivery_mode |
polling (default): write the outbox only. webhook or both: also POST to a registered endpoint. GET /credit/events works in every mode |
issuing_provider |
ramp, or mock for an in-memory issuer on staging (Ramp has no sandbox) |
deferred_fee_recognition |
Book the disbursement fee to deferred income at origination and recognise it straight-line over the term |
email_statements |
Email the statement PDF to the line's merchant_email when it issues |
alert_slack_channel, alert_emails |
Route this program's operator alerts to a named Slack channel and/or email addresses, in addition to the normal channels |
interchange_source, interchange_bps |
Interchange recognition (D-32): estimate earns interchange_bps of every cleared settlement and is trued up when the issuer's report arrives; issuer_report recognises reported rows only; none (default) recognises nothing |
underwriting_criteria |
The scorecard. Validate it first with POST /credit/scorecards/validate; the program response reports policy.criteria_coverage |
allowance_policy |
Loan-loss buckets for the portfolio report: {"buckets":[{"label":"1–30","from_days":1,"to_days":30,"rate_bps":200}, …]}. Defaults: 2% (1–30), 10% (31–60), 25% (61–90), 50% (90+) |
due_soon_days |
Days ahead of a loan's due date the credit.loan.due_soon event fires |
gl_posting_enabled |
Post balanced journal entries on the operator's books for every ledger event |
legal_lending_entity |
Printed on every statement PDF |
statement_branding |
Four keys, all optional: logo_data_uri (a data:image/... URI — PNG or SVG — embedded in the document), accent_hex (one colour, used for rules and the amount-due tile), footer_text (e.g. a support contact), remittance_text (payment instructions). Fonts and layout are not configurable per partner |
Rules the program does not state are not guessed. Where a path needs one, it returns 400 naming the missing rule.
Open a credit line
Called once, on approval. Returns the id every later call needs. Unspecified terms inherit the program defaults.
curl -X POST https://api.ondayzero.com/api/v1/credit/lines \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"program_id": "PROGRAM_ID",
"external_merchant_id": "merchant_00417",
"external_line_id": "line_00417",
"credit_limit_cents": 25000000,
"term_days": 30,
"disbursement_fee_bps": 100,
"start_date": "2026-03-18",
"linked_account_name": "Business Checking",
"linked_account_mask": "3397",
"plaid_account_id": "acc_31x",
"autopay_enabled": false
}'start_date is the signing date: the effective date named in the credit agreement. PATCH /credit/lines/{id} changes the repayment account and AutoPay.
The dashboard
GET /credit/lines/{id} returns everything the summary cards and metadata row need:
| Field | Meaning |
|---|---|
total_balance_cents (also current_balance_cents, the v1 name) |
Everything owed across every open loan: unpaid principal, unpaid fees, accrued interest. Includes transactions since the last close. Label the dashboard card Total Balance |
statement_balance_cents |
What moved onto the most recent statement at close. Never restated. The statement document labels this Statement Balance and never shows Total Balance, so the two labels do not clash |
amount_due_cents |
What remains of that statement |
unbilled_cents |
Outstanding on loans originated since the last close |
past_due_cents |
Outstanding on past-due loans |
available_credit_cents |
Limit minus Total Balance. Never below zero |
utilisation_bps |
Total Balance over limit, in basis points |
credit_balance_cents |
Carried-forward credit from refunds or overpayments, consumed by the next loan |
next_due_date |
Earliest upcoming loan due date, else the earliest past due |
as_of |
When the figures were computed. Show it |
card_management |
Copied from the program: dayzero or partner |
issuing_provider |
Copied from the program: ramp or mock |
A card transaction posts
Push settled transactions, never authorisations. Idempotent on external_settlement_id; a re-pushed batch reports duplicates rather than double-counting. Up to 1,000 per batch. A negative amount is a refund.
curl -X POST https://api.ondayzero.com/api/v1/credit/card-settlements \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"credit_line_id": "LINE_ID",
"settlements": [{
"external_settlement_id": "rmp_exp_9f3k2a",
"settled_on": "2026-05-20",
"amount_cents": 740000,
"merchant_name": "Google Ads",
"card_last_four": "9721",
"merchant_category_code": "7311"
}]
}'Each accepted result carries the loan it originated:
{
"external_settlement_id": "rmp_exp_9f3k2a",
"settlement_id": "...",
"loan_id": "...",
"principal_cents": 740000,
"disbursement_fee_cents": 7400,
"term_days": 30,
"due_date": "2026-06-19",
"status": "accepted"
}A refund result says which branch applied: refund_disposition is reduced_loan when it reduced the loan it relates to (send refund_of_external_settlement_id to name it), or carried_forward when the loan was already repaid or the refund exceeded what was outstanding. Carried-forward credit is applied against the merchant's next loans and never paid out.
Transactions belong to the statement period containing the date they posted at the issuer.
Loans
GET /credit/lines/{id}/loans returns one row per card transaction, oldest first, with server-side filters and a cursor:
| Parameter | Type | Description |
|---|---|---|
status |
string | Comma-separated: active, past_due, paid, written_off |
start, end |
date | Inclusive range |
date_basis |
string | transaction (default) or due |
cycle_id |
string | Loans originated in one statement period |
limit, cursor |
Paging |
Each loan carries transaction_date, merchant_name, principal_cents, disbursement_fee_cents, term_days, due_date, interest_accrued_cents, the paid buckets, outstanding_cents, and status. The status is authoritative.
| Status | Meaning |
|---|---|
active |
Within its term, not yet due. No interest accruing |
past_due |
Past its due date with an amount outstanding. Accruing daily |
paid |
Principal, fee, and interest all zero |
written_off |
Charged off under the collections policy |
GET /credit/lines/{id}/subledger returns the Transactions tab in one call: the summary band, the loan rows (same filters), and the period history. GET /credit/lines/{id}/subledger/export returns the same rows as CSV.
Build the summary band from its summary object alone: total_balance_cents, statement_balance_cents, amount_due_cents, statement_period_start, statement_period_end, statement_closed_on, statement_due_date, statement_status (open or paid), and statement_download_url, plus unbilled_cents, past_due_cents, credit_balance_cents, available_credit_cents, utilisation_bps, and next_due_date.
Interest
daily interest = remaining principal × (rate ÷ 10,000) ÷ 365
remaining principal = original principal − principal repaid − credits appliedEach day is stored as its own record at full precision; the loan's interest is the sum of those records rounded once. Interest is charged on principal only: fees do not accrue interest and unpaid interest does not compound. A loan repaid on or before its due date carries no interest at all. There is no grace period unless the program sets one, and accrual runs until the loan is repaid in full or the program's cap is reached.
Statements
The month closes on its last calendar day. Close applies the month's accrued interest, freezes the Statement Balance (everything outstanding across open loans at the period end), and issues the statement. An issued statement is never restated; a refund landing afterwards is applied as a credit against the balance.
Due date. At close, the statement's due date is the earliest due date, among the loans on that statement, that falls on or after the day close runs — or the close date itself when every loan on it is already due. It is stored at close and never recomputed from the issued date. In the worked example below, the March statement (closed 1 April) is due 4 April, the first loan's due date; the April statement (closed 1 May) is due 1 May, because every remaining loan is already past due — a statement can be due the day it issues.
GET /credit/lines/{id}/statements lists periods with amount_due_cents (the statement balance), paid_cents, outstanding_cents, period_status (open or paid), and download links. GET /credit/lines/{id}/statements/{cycle_id} returns the document:
format |
Returns |
|---|---|
json (default) |
The statement payload with one row per loan and its period activity |
pdf |
The rendered document with the program's branding and lending entity |
csv |
The transaction-level spreadsheet for reconciliation |
html |
The printable page |
The CSV has one row per transaction: amount (principal), disbursement_fee, payback_term_days, due_date, interest_applied_this_statement, interest_accrued_to_period_end, and the cumulative principal_paid_to_period_end, fee_paid_to_period_end, interest_paid_to_period_end, then outstanding_at_period_end, status, and originated_this_period. It does not break out individual payments and has no paid-this-period column; per-payment allocation by loan is on GET /credit/lines/{id}/payments.
Payments
DayZero never debits an account. Request a payoff figure, execute the debit through your processor, then report it. The response carries the allocation and corrected balances, so the UI updates without a second round trip.
# 1. Quote. Never use a displayed balance as the payoff figure.
curl "https://api.ondayzero.com/api/v1/credit/lines/LINE_ID/payoff-quote?as_of=2026-05-20" \
-H "Authorization: Bearer dz_your_token_here" -H "x-business-id: YOUR_BUSINESS_ID"
# 2. Execute the debit on your side.
# 3. Report it. paid_on is the date the money landed.
curl -X POST https://api.ondayzero.com/api/v1/credit/payments \
-H "Authorization: Bearer dz_your_token_here" \
-H "x-business-id: YOUR_BUSINESS_ID" \
-H "Content-Type: application/json" \
-d '{
"credit_line_id": "LINE_ID",
"external_payment_id": "pay_0091",
"amount_cents": 3000000,
"paid_on": "2026-04-15",
"method": "ach_payliance"
}'Allocation is not selectable. A payment is applied to the oldest open loan first and moves to the next only once that loan is fully repaid; within a loan it retires accrued interest, then the disbursement fee, then principal. Because interest is retired first, a loan cannot reach zero principal with interest still owing. Overpayment beyond everything outstanding is carried forward as credit.
- Per-row Pay: pass
loan_idto allocate to one loan; the program's per-loan minimum applies, or the outstanding amount when smaller. Quote one loan withpayoff-quote?loan_ids=LOAN_ID. - Pending debits: pass
"pending": trueto record an initiated debit without allocating, then confirm withPOST /credit/payments/{id}/settlementandsettled_on. The payment counts from the settled date. - Backdated payments: a payment dated before the last processing date removes the accrual days after it and re-accrues them on the reduced principal.
- Returns:
POST /credit/payments/{id}/reversalwithreversed_onandreasonreopens the loans at the balances they held. The merchant is not charged interest for the interval the debit was in flight. - AutoPay: where
autopay_enabledis true, acredit.autopay.dueevent is emitted for each loan on its due date, carrying the amount to debit. Execute it and report it as a payment.
GET /credit/lines/{id}/payments lists payments newest first.
A worked example
Five transactions post in March on a 30-day term with a 1% fee at 20% APR. Nothing accrues in March; the March statement bills $45,450.00 of principal and fees. By 15 April two loans are past their due dates and have accrued $72.33 and $23.29. A $30,000 payment on 15 April retires those two loans and a third in full (interest, fee, principal), clears the fee on the fourth and pays $6,824.38 of its principal, and does not touch the fifth. The April statement bills $15,581.97: the remaining principal, the fifth loan's fee, and $36.35 of interest accrued to 30 April.
Underwriting
Request an assessment during the application; it returns pending. Bank backfill, categorisation, and enough history for trailing ratios can take up to three days. Poll GET /credit/underwriting/assessments/{id} for status, completed_at, and failure_reason, or watch the event feed for credit.underwriting.completed.
DayZero measures every criterion in the program's underwriting_criteria that has a registered measurer (trailing revenue, average cash balance, existing debt facilities, debt service coverage, revenue concentration, months of history) from the categorised ledger, the bank feed, and the debt schedule, and returns per-criterion value, threshold, comparator, passed, source, and evidence, plus a weighted score_bps. passed: null means the figure could not be measured yet, which is different from a failure. decision is always null: the lending decision is yours.
Cards
Cards are issued through Ramp; DayZero orchestrates and mirrors, and card credentials never transit DayZero. POST /credit/cards/{id}/embed-token mints a short-lived issuer token so the issuer renders the number inside its own component; each disclosure is audited.
After every origination, repayment, reversal, and write-off, DayZero pushes available credit to the issuer as the card's spend limit, so the card declines at the limit. POST /credit/lines/{id}/cards/spend-limit-sync forces it; POST /credit/cards/sync reconciles state and limits against the issuer and reports drift.
Partner-managed cards
If you provision cards in the issuer yourself (card_management: partner on the program), DayZero never touches the card. Register each card once so cleared transactions can be attributed to the line:
POST /credit/cards/register
{ "credit_line_id": "...", "provider_card_id": "<issuer card id>", "provider_fund_id": "<issuer fund id>", "last_four": "4417" }Idempotent on the issuer card id. From then on, every balance change emits credit.line.limit_changed with available_credit_cents; push that to the issuer as the card's limit. DayZero pushes nothing to a registered card.
Dropped settlement webhooks
For DayZero-managed cards, POST /credit/cards/reconcile-settlements ({"days": 14}) lists the issuer's cleared transactions per card and originates any it never received. The nightly sweep runs it over the last 7 days. A recorded settlement whose issuer state is no longer cleared is reported in voided_or_declined, never reversed automatically. For partner-managed cards, re-push the settlement; ingest is idempotent on the settlement id.
Events
GET /credit/events is the polled feed. Pass the last sequence_no as after; sequence numbers are unique and gapless per business. Program event_delivery_mode is polling (default: outbox only), webhook, or both. Signed POSTs to a registered endpoint fire only in webhook and both. Polling remains the source of truth after a missed delivery.
| Event | When |
|---|---|
credit.line.opened |
A line was opened |
credit.loan.originated |
A transaction posted and became a loan (also on import) |
credit.loan.due_soon |
A loan is due_soon_days from its due date |
credit.loan.past_due |
A loan passed its due date with an amount outstanding |
credit.loan.paid |
A loan was repaid in full |
credit.loan.written_off |
A loan was charged off |
credit.autopay.due |
AutoPay should debit a loan today; carries the amount |
credit.cycle.closed |
A statement period closed and interest was applied |
credit.statement.issued |
A statement is available |
credit.statement.emailed |
The statement PDF was emailed to the merchant (email_statements programs) |
credit.line.limit_changed |
Available credit changed on a partner-managed line; carries available_credit_cents to push to the issuer |
credit.cycle.paid |
A statement period was settled in full |
credit.payment.recorded |
A payment was recorded |
credit.payment.settled |
A pending payment settled and was allocated |
credit.payment.reversed |
A payment was returned or failed |
credit.underwriting.completed |
An assessment finished |
credit.card.issued, credit.card.suspended, credit.card.terminated |
Card lifecycle |
credit.card.credentials_requested |
Card credentials were displayed |
Launching with existing loans
POST /credit/lines/{id}/loans/import loads loans that already exist on a previous system with their paid buckets and interest to date, keyed by the original issuer transaction id so attribution survives the migration. Accrual resumes the day after accrued_through.
Ledger
GET /credit/lines/{id}/ledger is the append-only register every balance is derived from, one page at a time. GET /credit/lines/{id}/ledger/verify recomputes the hash chain, the running balance, and the sequence, and is what settles a balance dispute against the posted accounting rather than a screen. DayZero runs it nightly for every active line.
What runs on DayZero's schedule
Nightly at 02:15 UTC, per business: daily accrual and past-due flags, month-end close for any ended period, AutoPay due-today events, bank balance snapshots, deferred fee recognition, interchange estimates, settlement reconciliation against the issuer, statement emails, the issuer spend-limit push (or credit.line.limit_changed for partner-managed cards), pending underwriting measurement, and ledger verification. POST /credit/sweeps/daily runs the same sequence on demand, and POST /credit/accruals/run runs accrual alone, for any date that has already happened. On a live program these endpoints — with POST /credit/cycles/close and POST /credit/cycles/assess-late — refuse a date in the future with 400; on a sandbox tenant they run the clock forward, which is what the end-to-end walk relies on.
Portfolio and loan tape
GET /credit/portfolio (optionally program_id, as_of) sums the book: outstanding by component, past-due aging in the program's allowance buckets with the reserve each implies, month-to-date interest and fee income, originations, payments, write-offs, and the lifetime default rate. GET /credit/portfolio/loan-tape?format=csv is one row per loan with every balance and date; it ties to the portfolio figures exactly.
Interchange
Interchange the issuer shares on card spend is DayZero's to recognise (D-32). Two sources, per program: an estimate at interchange_bps on every cleared settlement (nightly), and the issuer's report:
POST /credit/interchange/report
{ "rows": [ { "external_reference": "<issuer row id>", "external_settlement_id": "<issuer transaction id>", "interchange_cents": 20510, "recognized_on": "2026-04-30" } ] }A row may be attributed by issuer transaction id, provider_card_id, or credit_line_id; with none of those, pass program_id and the row lands on the program unattributed. A reported row for a settlement already estimated supersedes the estimate and the operator's books move by the difference (Interchange Receivable / Interchange Income). Idempotent on external_reference. GET /credit/interchange returns month-to-date estimated and reported figures, year-to-date, lifetime, and a per-line split; the portfolio carries interchange_mtd_cents.
The scorecard
The scorecard is the partner's (O-01): keys, comparators, thresholds, weights, and which criteria are required. DayZero measures. Check a draft before it goes on a program:
POST /credit/scorecards/validate
{ "underwriting_criteria": [ { "key": "trailing_12m_revenue", "comparator": "gte", "threshold": 500000, "required": true }, … ] }The response says whether the set is well-formed, which keys DayZero can measure today, and which required keys it cannot (an assessment can never pass those). GET /credit/underwriting/measurers lists every measurable key with its unit and source. Put the scorecard on the program with PATCH /credit/programs/{id} (underwriting_criteria); the program response carries policy.criteria_coverage. An example that measures fully is at scripts/admin/credit/scorecard_example.json.
Underwriting measurers
Criteria keys with a built-in measurer: trailing_12m_revenue, trailing_3m_revenue, average_daily_cash_balance, existing_debt_facilities, debt_service_coverage_ratio, revenue_concentration_top_customer, months_of_history, months_in_business, trailing_12m_ad_spend, ad_spend_to_revenue_ratio, shopify_trailing_12m_revenue, shopify_refund_rate, shopify_chargeback_rate, nsf_count_12m. Ratios are decimals (0.05 = 5%); money is in whole currency units. A key without a measurer is reported unmeasured, never failed. average_daily_cash_balance is a true 90-day daily average: the nightly sweep snapshots every linked bank account's balance for businesses on a lending program, and days without a snapshot are reconstructed from bank-ledger postings; the evidence says how many days came from each source.
API tokens
Create a token under Settings › Developers. Use a separate key per job:
| Job | Scope | IP restriction |
|---|---|---|
| Merchant dashboard | credit:read |
Optional |
| Payment posting (Payliance / ACH) | credit:payments |
Restrict to the job's egress IPs |
| Broader credit integration | credit |
Optional; prefer IPs on any write token |
credit / credit:read / credit:payments tokens are refused (403) outside their surface. Tokens with no scope keep full access. Creating a token requires two-factor authentication: the DayZero account must have a second factor enrolled, and the web app asks you to verify both factors when you generate the key (it cannot be done with an existing API token). Listing and revoking tokens do not. To rotate, mint the new key, swap the caller, then revoke the old one.
Two credentials never work here: an MCP OAuth token, which the REST API rejects with the same 401 as an expired token, and any API token on the four operator actions listed under Operator actions, which return 403.
Validate end to end
Replay the FRD Appendix B book (five March swipes, March close, a $30,000 payment on 15 April, April close) against a sandbox tenant. Every check prints PASS or FAIL; the process exits 0 only when the figures match.
python scripts/admin/credit/validate_tie_e2e.py \
--base-url https://api.ondayzero.com \
--token "$DZ_TOKEN" \
--business-id "$OPERATOR_BUSINESS_ID" \
--applyThe same walk is the CI contract in app/api/v1/tests/test_credit_tie_lifecycle.py. If program creation is refused, pass --program-id. Omit --apply for a dry run of the plan.