{"openapi":"3.1.0","info":{"title":"POST /api/v1/credit/programs/{program_id}/loans/import/from-partner-pull","version":"1.0.0","description":"Build and stage the migration file from a stored pull"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/credit/programs/{program_id}/loans/import/from-partner-pull":{"post":{"tags":["credit"],"summary":"Build and stage the migration file from a stored pull","description":"Sandbox only: pulls are kept on a sandbox business (`CREDIT_090` elsewhere), so this is the console's rehearsal of the cut-over; the live book is built from the same files by an operator script. Reads the pull's merchants and transactions tables and builds the launch import (Appendix E): one row per loan with a balance, keyed on the Brex transaction id; its paid amounts are what Tie's system assessed less what it reports outstanding, per loan or from the merchant's balance split over its loans oldest first (D-58); its interest good through `pull_date` and, above the program's ceiling, loaded at the ceiling (D-59). With `since`, the tail: the transactions that posted from that date, loaded as they posted (D-60). The file is staged on the program's loan import, and `run` is what to send with `staging_id` to `POST /credit/programs/{id}/loans/import` for the dry run and the apply. `report` is the exceptions report: rows refused and why, the lines and which are not open yet, interest reduced to the ceiling, loans repaid before cut-over (phase 2), and the control totals. `open_lines` also opens each merchant's line with its own terms, a merchant with no balance included, and sets its email, legal name and address; send it before the apply. `400` `CREDIT_770` when `since` is after `pull_date`, `CREDIT_771` when nothing would be imported, `CREDIT_772` for a mapping field the builder does not read, `CREDIT_773` for a cut-over pull with no merchants table.","operationId":"credit_migration_build_from_pull","parameters":[{"name":"program_id","in":"path","required":true,"schema":{"type":"string","description":"The program to stage the file on.","title":"Program Id"},"description":"The program to stage the file on."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigrationFromPullRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_MigrationBuildResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"error":"validation_error","message":"Validation failed: due_date: Input should be a valid date","errors":{"due_date":"Input should be a valid date"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Missing or expired token, an MCP OAuth token used against the REST API, or no access to this business","content":{"application/json":{"example":{"error":"unauthorized","message":"Authentication required","code":"AUTH_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"error":"forbidden","message":"You don't have permission to access this resource","code":"AUTH_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - Resource does not exist","content":{"application/json":{"example":{"error":"not_found","message":"Resource not found","code":"NOT_FOUND_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"validation_error","message":"Validation failed: amount: Input should be a valid integer","errors":{"amount":"Input should be a valid integer"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"}}}}},"x-fastapi-operation-id":"credit_migration_build_from_pull_api_v1_credit_programs__program_id__loans_import_from_partner_pull_post"}}},"components":{"schemas":{"ImportBatchTotals":{"properties":{"row_count":{"type":"integer","title":"Row Count","description":"Rows in the file.","default":0},"importable_count":{"type":"integer","title":"Importable Count","description":"Rows with no error.","default":0},"error_row_count":{"type":"integer","title":"Error Row Count","description":"Rows that will be rejected.","default":0},"warning_count":{"type":"integer","title":"Warning Count","description":"Warnings across every row.","default":0},"unassigned_row_count":{"type":"integer","title":"Unassigned Row Count","description":"Rows with no line key, so on no line.","default":0},"line_count":{"type":"integer","title":"Line Count","description":"Distinct line keys in the file.","default":0},"unmatched_line_count":{"type":"integer","title":"Unmatched Line Count","description":"Line keys with no line on the program.","default":0},"principal_cents":{"type":"integer","title":"Principal Cents","description":"Original principal over importable rows.","default":0},"outstanding_cents":{"type":"integer","title":"Outstanding Cents","description":"Outstanding over importable rows.","default":0}},"type":"object","title":"ImportBatchTotals","description":"What a staged file holds under its current mapping."},"ImportColumn":{"properties":{"name":{"type":"string","title":"Name","description":"The header as it appears in the file."},"samples":{"items":{"type":"string"},"type":"array","title":"Samples","description":"Up to three non-empty values from the top of the file."}},"type":"object","required":["name"],"title":"ImportColumn","description":"One column of the uploaded file, with a taste of what it holds."},"ImportExpectedTotals":{"properties":{"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count","description":"Expected row count."},"principal_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Principal Cents","description":"Expected principal."},"outstanding_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Outstanding Cents","description":"Expected outstanding."}},"type":"object","title":"ImportExpectedTotals","description":"The control totals the operator gave from the partner's book."},"ImportFieldMapping":{"properties":{"target":{"type":"string","title":"Target","description":"The loan import field (or line key) to fill."},"kind":{"type":"string","title":"Kind","description":"id, text, date, money, integer, rate, boolean or last_four."},"required":{"type":"boolean","title":"Required","description":"A row cannot load without this field."},"description":{"type":"string","title":"Description","description":"What the field means.","default":""},"source_column":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Column","description":"The file column it is read from; null for none."},"confidence":{"type":"number","title":"Confidence","description":"0 to 1. 1 is a column set by hand; below 0.5 deserves a look."},"reason":{"type":"string","title":"Reason","description":"Why this column, in words."},"samples":{"items":{"type":"string"},"type":"array","title":"Samples","description":"Up to three values of the source column, as they arrived."}},"type":"object","required":["target","kind","required","confidence","reason"],"title":"ImportFieldMapping","description":"Where one target field is read from, and how sure the guess is."},"ImportLineDefaults":{"properties":{"credit_limit_cents":{"type":"integer","minimum":0.0,"title":"Credit Limit Cents","description":"The new line's limit."},"term_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Term Days","description":"Defaults to the program's first term."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"The signing date of the merchant's agreement."}},"additionalProperties":false,"type":"object","required":["credit_limit_cents"],"title":"ImportLineDefaults","description":"Terms for a line the import opens for a key the program does not have."},"ImportLineGroup":{"properties":{"line_key":{"type":"string","title":"Line Key","description":"The line key value the rows carry."},"credit_line_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credit Line Id","description":"The program's line with that key; null when none."},"exists":{"type":"boolean","title":"Exists","description":"A line with this key exists on the program."},"row_count":{"type":"integer","title":"Row Count","description":"Rows in the file for this key."},"importable_count":{"type":"integer","title":"Importable Count","description":"Rows with no error."},"principal_cents":{"type":"integer","title":"Principal Cents","description":"Original principal over importable rows."},"outstanding_cents":{"type":"integer","title":"Outstanding Cents","description":"Outstanding over importable rows, as the loans would carry it."}},"type":"object","required":["line_key","exists","row_count","importable_count","principal_cents","outstanding_cents"],"title":"ImportLineGroup","description":"The rows of the file that belong to one credit line."},"ImportLineResult":{"properties":{"line_key":{"type":"string","title":"Line Key","description":"The line key."},"credit_line_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credit Line Id","description":"The line; null when none could be found or opened."},"created":{"type":"boolean","title":"Created","description":"The line was opened by this import.","default":false},"imported":{"type":"integer","title":"Imported","description":"New loans.","default":0},"duplicates":{"type":"integer","title":"Duplicates","description":"Rows whose loan was already on the line.","default":0},"rejected":{"type":"integer","title":"Rejected","description":"Rows that did not load.","default":0},"principal_cents":{"type":"integer","title":"Principal Cents","description":"Original principal over rows that loaded.","default":0},"outstanding_cents":{"type":"integer","title":"Outstanding Cents","description":"Outstanding over rows that loaded.","default":0},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the whole line did not load: no such line, or opening it was refused."}},"type":"object","required":["line_key"],"title":"ImportLineResult","description":"What the import did on one line."},"ImportOpeningJournal":{"properties":{"entry_date":{"type":"string","format":"date","title":"Entry Date","description":"The import date the entry is dated."},"receivable_cents":{"type":"integer","title":"Receivable Cents","description":"Debit to Notes Receivable: principal and fee outstanding."},"interest_receivable_cents":{"type":"integer","title":"Interest Receivable Cents","description":"Debit to Interest Receivable: interest outstanding."},"funding_cents":{"type":"integer","title":"Funding Cents","description":"Credit to the program's funding account: the two together."},"posted":{"type":"boolean","title":"Posted","description":"The entry was posted by this run. False on a dry run, when the run wrote nothing, and when nothing is outstanding."},"journal_entry_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Journal Entry Id","description":"The journal entry, once posted."}},"type":"object","required":["entry_date","receivable_cents","interest_receivable_cents","funding_cents","posted"],"title":"ImportOpeningJournal","description":"The opening-balance journal of an import on a GL-posting program (item 25).\n\nOne entry at the import date: the receivable for principal and fee\noutstanding and the interest receivable for interest outstanding, against\nthe funding account the program names. On a dry run, what would post."},"ImportParseResponse":{"properties":{"staging_id":{"type":"string","title":"Staging Id","description":"The batch id. Also the report id."},"program_id":{"type":"string","title":"Program Id","description":"The program the file is for."},"kind":{"type":"string","enum":["loans","payments","statements"],"title":"Kind","description":"What the file holds: `loans` (this wizard), or a history file attached beside it, `payments` or `statements` (`POST /credit/programs/{id}/history/import`).","default":"loans"},"filename":{"type":"string","title":"Filename","description":"The file's name as uploaded."},"sha256":{"type":"string","title":"Sha256","description":"SHA-256 of the file as uploaded."},"row_count":{"type":"integer","title":"Row Count","description":"Rows in the file, header excluded."},"status":{"type":"string","enum":["parsed","running","dry_run","applied","failed","expired"],"title":"Status","description":"parsed, running (a run is in progress), dry_run, applied, failed (control totals did not tie, or the run stopped: see `run.error`) or expired."},"amounts_in":{"type":"string","enum":["dollars","cents"],"title":"Amounts In","description":"The unit of every money column."},"line_key_field":{"anyOf":[{"type":"string","enum":["external_line_id","external_merchant_id"]},{"type":"null"}],"title":"Line Key Field","description":"Which key groups rows into lines: `external_line_id` when mapped, else `external_merchant_id`. Null when neither is mapped."},"mapping":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Mapping","description":"Target field → source column in force."},"totals":{"$ref":"#/components/schemas/ImportBatchTotals","description":"What the file holds under the mapping in force."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the file was staged (UTC)."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"Seven days after staging. An unapplied batch is refused after it."},"applied_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Applied At","description":"When the file was written to the book."},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Who staged the file."},"last_run":{"anyOf":[{"$ref":"#/components/schemas/ImportRunSummary"},{"type":"null"}],"description":"The last finished dry run or apply, from its report; null before the first."},"run":{"anyOf":[{"$ref":"#/components/schemas/ImportRun"},{"type":"null"}],"description":"The current or last run, with its progress: poll this while `status` is `running`. Null before the first."},"result":{"anyOf":[{"$ref":"#/components/schemas/ProgramLoanImportResponse"},{"type":"null"}],"description":"Once the last run has finished: the body the inline call returns (per-line results, totals against the control totals, `applied`, `report_id`). Null while a run is in progress, after a run that stopped, and in the list."},"columns":{"items":{"$ref":"#/components/schemas/ImportColumn"},"type":"array","title":"Columns","description":"Every column of the file."},"fields":{"items":{"$ref":"#/components/schemas/ImportFieldMapping"},"type":"array","title":"Fields","description":"Every target field with its source column and confidence."},"mapping_source":{"type":"string","title":"Mapping Source","description":"`inferred`, `given`, `saved:<name>` or `template:<name>`: where the mapping started from."},"amounts_in_reason":{"type":"string","title":"Amounts In Reason","description":"Why the money reads in this unit."},"lines":{"items":{"$ref":"#/components/schemas/ImportLineGroup"},"type":"array","title":"Lines","description":"The rows grouped by line key."},"unmatched_keys":{"items":{"type":"string"},"type":"array","title":"Unmatched Keys","description":"Line keys with no line on the program (create them at apply)."},"problems":{"items":{"$ref":"#/components/schemas/ImportRowProblem"},"type":"array","title":"Problems","description":"Row problems, the first 200; `problem_count` has them all."},"problem_count":{"type":"integer","title":"Problem Count","description":"Every row problem in the file.","default":0},"available_mappings":{"items":{"type":"string"},"type":"array","title":"Available Mappings","description":"Saved mapping names on the program, then built-in templates."}},"type":"object","required":["staging_id","program_id","filename","sha256","row_count","status","amounts_in","created_at","expires_at","mapping_source","amounts_in_reason"],"title":"ImportParseResponse","description":"A staged file, with everything needed to confirm or correct its mapping."},"ImportRowProblem":{"properties":{"row_number":{"type":"integer","title":"Row Number","description":"Position in the file, from 1, header excluded."},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field","description":"The target field at fault, when there is one."},"message":{"type":"string","title":"Message","description":"What is wrong, in words."},"severity":{"type":"string","enum":["error","warning"],"title":"Severity","description":"`error`: the row will be rejected. `warning`: it loads as described."}},"type":"object","required":["row_number","message","severity"],"title":"ImportRowProblem","description":"Something wrong with one row of the file."},"ImportRowRejection":{"properties":{"row_number":{"type":"integer","title":"Row Number","description":"Position in the file, from 1."},"line_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line Key","description":"The row's line key."},"external_settlement_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Settlement Id","description":"The row's issuer transaction id, when it has one."},"reason":{"type":"string","title":"Reason","description":"Why it was rejected."}},"type":"object","required":["row_number","reason"],"title":"ImportRowRejection","description":"A row that did not load, and why."},"ImportRun":{"properties":{"id":{"type":"string","title":"Id","description":"The run. A new start gets a new id."},"kind":{"type":"string","enum":["dry_run","apply"],"title":"Kind","description":"`dry_run` or `apply`."},"mode":{"type":"string","enum":["sync","async"],"title":"Mode","description":"`sync`: ran in the request. `async`: ran as a background job."},"started_at":{"type":"string","format":"date-time","title":"Started At","description":"When it was started (UTC)."},"finished_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Finished At","description":"When it finished or stopped; null while running."},"heartbeat_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Heartbeat At","description":"The job's last sign of life (UTC)."},"elapsed_seconds":{"type":"integer","title":"Elapsed Seconds","description":"Seconds from start to finish, or to now while it runs."},"stale":{"type":"boolean","title":"Stale","description":"Still `running`, but the job has not reported for 10 minutes: its worker stopped. Starting the batch again takes it over.","default":false},"started_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Started By","description":"Who started it."},"inputs":{"$ref":"#/components/schemas/ImportRunInputs","description":"What the run was started with: control totals, as-of date, line options."},"progress":{"$ref":"#/components/schemas/ImportRunProgress","description":"How far it got: rows and lines done of the total, and the phase."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the run stopped before finishing (a crash, a timeout, or a run taken over). Nothing was written; run it again."}},"type":"object","required":["id","kind","mode","started_at","elapsed_seconds","progress"],"title":"ImportRun","description":"A batch's current or last dry run or apply."},"ImportRunInputs":{"properties":{"as_of":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"As Of","description":"Loan status assessed as at this date; null is today."},"expected_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Count","description":"Control count."},"expected_principal_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Principal Cents","description":"Control principal."},"expected_outstanding_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expected Outstanding Cents","description":"Control outstanding."},"create_missing_lines":{"type":"boolean","title":"Create Missing Lines","description":"Lines are opened for keys the program lacks.","default":false},"line_defaults":{"anyOf":[{"$ref":"#/components/schemas/ImportLineDefaults"},{"type":"null"}],"description":"Terms for the lines the run opens."},"allow_cross_line_duplicates":{"type":"boolean","title":"Allow Cross Line Duplicates","description":"Rows repeating a transaction id held on another line load.","default":false},"history":{"type":"boolean","title":"History","description":"History mode: repaid loans only, no events.","default":false}},"type":"object","title":"ImportRunInputs","description":"What the run was asked to do: the options of the request that started it."},"ImportRunProgress":{"properties":{"phase":{"type":"string","enum":["queued","loading","importing","checking","saving","done","failed"],"title":"Phase","description":"`queued` (waiting for a worker), `loading` (reading the staged rows), `importing` (line by line), `checking` (reading the book back for the report), `saving`, then `done`, or `failed`."},"rows_done":{"type":"integer","title":"Rows Done","description":"Rows processed so far.","default":0},"rows_total":{"type":"integer","title":"Rows Total","description":"Rows in the file.","default":0},"lines_done":{"type":"integer","title":"Lines Done","description":"Lines processed so far.","default":0},"lines_total":{"type":"integer","title":"Lines Total","description":"Distinct line keys in the file.","default":0}},"type":"object","required":["phase"],"title":"ImportRunProgress","description":"How far a dry run or apply has got."},"ImportRunSummary":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","description":"The run was a dry run."},"applied":{"type":"boolean","title":"Applied","description":"The run wrote to the book."},"run_at":{"type":"string","format":"date-time","title":"Run At","description":"When it ran (UTC)."},"run_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run By","description":"Who ran it."},"mismatches":{"items":{"type":"string"},"type":"array","title":"Mismatches","description":"Everything that did not tie: control totals, then lines. After an apply the control totals refused, a last entry says nothing was written and what to do."}},"type":"object","required":["dry_run","applied","run_at"],"title":"ImportRunSummary","description":"The last dry run or apply of a batch."},"ImportTotals":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows that loaded: new plus duplicates.","default":0},"principal_cents":{"type":"integer","title":"Principal Cents","description":"Original principal.","default":0},"outstanding_cents":{"type":"integer","title":"Outstanding Cents","description":"Outstanding.","default":0}},"type":"object","title":"ImportTotals","description":"Counts and money over the rows that loaded (or would)."},"ImportWarning":{"properties":{"code":{"type":"string","title":"Code","description":"`no_interest_ceiling`: the program has no ceiling on a loan's interest, and the ceiling is frozen onto each loan when it is created, so these loans stay uncapped for good. `repeats_id_on_other_line`: the row loaded although its transaction id is already on another line."},"message":{"type":"string","title":"Message","description":"What it means and what to do, in words."},"row_number":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Number","description":"Position in the file, from 1; null for the run."},"line_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line Key","description":"The row's line key."},"external_settlement_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Settlement Id","description":"The row's issuer transaction id."}},"type":"object","required":["code","message"],"title":"ImportWarning","description":"Something the operator should read before applying; the run did not stop."},"MigrationBuildResponse":{"properties":{"staging_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Staging Id","description":"The staged batch; null when no loan has a balance (lines only)."},"batch":{"anyOf":[{"$ref":"#/components/schemas/ImportParseResponse"},{"type":"null"}],"description":"The staged batch, as the parse step returns it."},"run":{"$ref":"#/components/schemas/MigrationRunInputs","description":"Send these, with `staging_id`, to dry-run and then apply."},"report":{"$ref":"#/components/schemas/MigrationReport","description":"The exceptions report for Tie."}},"type":"object","required":["run","report"],"title":"MigrationBuildResponse","description":"The staged file, the inputs to run it with, and the exceptions report."},"MigrationCeilingReduction":{"properties":{"merchant_id":{"type":"string","title":"Merchant Id","description":"Tie's merchant id."},"external_settlement_id":{"type":"string","title":"External Settlement Id","description":"The Brex transaction id."},"principal_cents":{"type":"integer","title":"Principal Cents","description":"The loan's principal."},"ceiling_cents":{"type":"integer","title":"Ceiling Cents","description":"The most interest the loan can carry."},"interest_given_cents":{"type":"integer","title":"Interest Given Cents","description":"Tie's interest to date."},"excess_cents":{"type":"integer","title":"Excess Cents","description":"The amount above the ceiling, not loaded."},"interest_paid_given_cents":{"type":"integer","title":"Interest Paid Given Cents","description":"Interest Tie shows paid."},"interest_paid_loaded_cents":{"type":"integer","title":"Interest Paid Loaded Cents","description":"Interest paid as loaded: at most the ceiling."}},"type":"object","required":["merchant_id","external_settlement_id","principal_cents","ceiling_cents","interest_given_cents","excess_cents","interest_paid_given_cents","interest_paid_loaded_cents"],"title":"MigrationCeilingReduction","description":"A loan loaded with its interest at the ceiling (D-59)."},"MigrationControlTotals":{"properties":{"count":{"type":"integer","title":"Count","description":"Rows in the file."},"principal_cents":{"type":"integer","title":"Principal Cents","description":"Original principal over the rows."},"outstanding_cents":{"type":"integer","title":"Outstanding Cents","description":"Outstanding over the rows."}},"type":"object","required":["count","principal_cents","outstanding_cents"],"title":"MigrationControlTotals","description":"The figures the import's control-total check ties to."},"MigrationFieldMapping":{"properties":{"amounts_in":{"anyOf":[{"type":"string","enum":["dollars","cents"]},{"type":"null"}],"title":"Amounts In","description":"The unit of the money in the pull. Dollars."},"merchants":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Merchants","description":"Builder field → the merchants table's attribute: `id`, `name`, `legal_name`, `limit_cents`, `term_days`, `disbursement_fee_bps`, `rate_bps`, `start_date`, `email`, `balance_cents` (the balance Tie holds for the merchant), `address` (a map) or `address_line1`, `address_line2`, `city`, `state`, `postal_code`, `country`."},"transactions":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Transactions","description":"Builder field → the transactions table's attribute: `id` (the Brex transaction id), `merchant_id`, `amount_cents`, `posted_on`, `merchant_name`, `card_last_four`, `fee_cents`, `term_days`, `due_date`, `rate_bps`, `interest_cents` (interest assessed to date), `outstanding_cents` (what the loan still owes), or by bucket `principal_outstanding_cents`, `fee_outstanding_cents`, `interest_outstanding_cents`; `written_off`, `written_off_on`, `description`."}},"additionalProperties":false,"type":"object","title":"MigrationFieldMapping","description":"Which of the partner's fields is which; every entry optional."},"MigrationFromPullRequest":{"properties":{"pull_id":{"type":"string","title":"Pull Id","description":"The pull (`GET /credit/sandbox/partner-pulls`)."},"pull_date":{"type":"string","format":"date","title":"Pull Date","description":"The day the pull is as at the close of. Every row's interest is good through it (`accrued_through`, D-59), and the import runs as at the day after."},"since":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Since","description":"The tail (D-60): only transactions that posted on or after this date, each loaded as it posted. Null for the cut-over import."},"roles":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"title":"Roles","description":"Role → table name, overriding the pull's: `transactions`, `merchants`."},"mapping":{"anyOf":[{"$ref":"#/components/schemas/MigrationFieldMapping"},{"type":"string","const":"auto"},{"type":"null"}],"title":"Mapping","description":"The partner's field names, laid over the defaults; `auto` works them out from the tables (a money field only on a header that is one of its names)."},"balance_basis":{"type":"string","enum":["auto","loan","merchant"],"title":"Balance Basis","description":"Where the outstanding figures are read (D-58): `loan` from each transaction, `merchant` from the merchant's balance (split over its loans oldest first), `auto` per loan for a merchant whose every loan carries a figure, else the merchant's.","default":"auto"},"line_key":{"type":"string","enum":["external_line_id","external_merchant_id"],"title":"Line Key","description":"Which line id carries Tie's merchant id. `external_line_id` (the default) opens each line under it, idempotently.","default":"external_line_id"},"open_lines":{"type":"boolean","title":"Open Lines","description":"Also open each merchant's line with its own terms, and set its email, legal name and address from the merchants table (as `PATCH /credit/lines/{id}` does). Off: nothing is written but the staged file, and `run` opens placeholder lines inside the dry run.","default":false}},"additionalProperties":false,"type":"object","required":["pull_id","pull_date"],"title":"MigrationFromPullRequest","description":"Build the import file from a stored pull and stage it on the program."},"MigrationLineResult":{"properties":{"merchant_id":{"type":"string","title":"Merchant Id","description":"Tie's merchant id; the line's key."},"credit_line_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Credit Line Id","description":"The line, when it exists or was opened."},"exists":{"type":"boolean","title":"Exists","description":"The program had the line before this run.","default":false},"opened":{"type":"boolean","title":"Opened","description":"This run opened it.","default":false},"legal_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Name","description":"The merchant's legal name, else its name."},"credit_limit_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credit Limit Cents","description":"The limit in Tie's merchants table."},"term_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Term Days","description":"Its term; the program's first when Tie holds none."},"disbursement_fee_bps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Disbursement Fee Bps","description":"Its fee; null is the program's default."},"rate_bps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Bps","description":"Its rate; null is the program's default."},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"The signing date in Tie's table (D-18)."},"loans":{"type":"integer","title":"Loans","description":"Rows in the file for the merchant.","default":0},"outstanding_cents":{"type":"integer","title":"Outstanding Cents","description":"What those rows owe.","default":0},"contacts_set":{"items":{"type":"string"},"type":"array","title":"Contacts Set","description":"The contact fields set on the line."},"contact_notes":{"items":{"type":"string"},"type":"array","title":"Contact Notes","description":"Contact details not set, and why."},"problems":{"items":{"type":"string"},"type":"array","title":"Problems","description":"Why the line cannot be opened; its rows are refused with it."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why opening it or setting its details was refused."}},"type":"object","required":["merchant_id"],"title":"MigrationLineResult","description":"A merchant's line: its terms, its loans in the file, and what the run did."},"MigrationRefusal":{"properties":{"where":{"type":"string","title":"Where","description":"The table and row."},"code":{"type":"string","title":"Code","description":"`unreadable`, `missing_field`, `duplicate_merchant`, `refund`, `zero_amount`, `after_pull_date`, `duplicate_id`, `unknown_merchant`, `line_refused`, `no_outstanding_figure`, `outstanding_above_assessed`, `balance_above_assessed` or `import_would_refuse`."},"reason":{"type":"string","title":"Reason","description":"Why, in words."},"merchant_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Id","description":"Tie's merchant id."},"external_settlement_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Settlement Id","description":"The Brex transaction id."},"amount_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amount Cents","description":"The row's amount."}},"type":"object","required":["where","code","reason"],"title":"MigrationRefusal","description":"A row of Tie's tables the file does not carry, and why."},"MigrationRepaidLoan":{"properties":{"merchant_id":{"type":"string","title":"Merchant Id","description":"Tie's merchant id."},"external_settlement_id":{"type":"string","title":"External Settlement Id","description":"The Brex transaction id."},"originated_on":{"type":"string","format":"date","title":"Originated On","description":"The day it posted."},"principal_cents":{"type":"integer","title":"Principal Cents","description":"Its principal."}},"type":"object","required":["merchant_id","external_settlement_id","originated_on","principal_cents"],"title":"MigrationRepaidLoan","description":"A loan repaid before cut-over: history, loaded in phase 2."},"MigrationReport":{"properties":{"mode":{"type":"string","enum":["full","tail"],"title":"Mode","description":"Cut-over import or the tail."},"pull_date":{"type":"string","format":"date","title":"Pull Date","description":"Every row's interest is good through it."},"as_of":{"type":"string","format":"date","title":"As Of","description":"The import date: the day after the pull date."},"since":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Since","description":"The tail's first posting date; null at cut-over."},"control_totals":{"$ref":"#/components/schemas/MigrationControlTotals","description":"Count, principal and outstanding over the file's rows."},"rows_by_basis":{"additionalProperties":{"type":"integer"},"type":"object","title":"Rows By Basis","description":"Rows by where their paid amounts came from: loan, merchant, tail."},"lines":{"items":{"$ref":"#/components/schemas/MigrationLineResult"},"type":"array","title":"Lines","description":"Every merchant's line, a merchant with no balance included."},"lines_to_open":{"type":"integer","title":"Lines To Open","description":"Lines the program does not have yet.","default":0},"refused":{"items":{"$ref":"#/components/schemas/MigrationRefusal"},"type":"array","title":"Refused","description":"Every row of Tie's tables not in the file."},"interest_reduced_to_ceiling":{"items":{"$ref":"#/components/schemas/MigrationCeilingReduction"},"type":"array","title":"Interest Reduced To Ceiling","description":"Every loan loaded with its interest at the ceiling (D-59)."},"repaid_before_cut_over":{"items":{"$ref":"#/components/schemas/MigrationRepaidLoan"},"type":"array","title":"Repaid Before Cut Over","description":"Loans with no balance: phase 2 history."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Anything else to read, in words."},"mapping":{"additionalProperties":true,"type":"object","title":"Mapping","description":"The field names used."}},"type":"object","required":["mode","pull_date","as_of","control_totals"],"title":"MigrationReport","description":"The exceptions report that goes back to Tie (Appendix E.3)."},"MigrationRunInputs":{"properties":{"as_of":{"type":"string","format":"date","title":"As Of","description":"The day after the pull date."},"expected_count":{"type":"integer","title":"Expected Count","description":"Rows in the file."},"expected_principal_cents":{"type":"integer","title":"Expected Principal Cents","description":"Their principal."},"expected_outstanding_cents":{"type":"integer","title":"Expected Outstanding Cents","description":"What they owe once imported, interest at the ceiling."},"create_missing_lines":{"type":"boolean","title":"Create Missing Lines","description":"True when the lines are not open yet: the dry run opens a placeholder for each, rolled back with it. Open the lines (`open_lines`) before the apply."},"line_defaults":{"anyOf":[{"$ref":"#/components/schemas/ImportLineDefaults"},{"type":"null"}],"description":"The placeholder lines' terms, with the above."}},"type":"object","required":["as_of","expected_count","expected_principal_cents","expected_outstanding_cents","create_missing_lines"],"title":"MigrationRunInputs","description":"What to send with `staging_id` to `POST /credit/programs/{id}/loans/import`."},"ProgramLoanImportResponse":{"properties":{"staging_id":{"type":"string","title":"Staging Id","description":"The batch."},"report_id":{"type":"string","title":"Report Id","description":"The import report: `GET /credit/import-batches/{id}/report`."},"dry_run":{"type":"boolean","title":"Dry Run","description":"Nothing was meant to be written."},"applied":{"type":"boolean","title":"Applied","description":"Every line was written together. False on a dry run, and when a control total did not match: then nothing was written."},"as_of":{"type":"string","format":"date","title":"As Of","description":"The date loan status was assessed as at."},"lines":{"items":{"$ref":"#/components/schemas/ImportLineResult"},"type":"array","title":"Lines","description":"What the run did on each line."},"lines_created":{"type":"integer","title":"Lines Created","description":"Lines opened by the import.","default":0},"imported":{"type":"integer","title":"Imported","description":"New loans over every line.","default":0},"duplicates":{"type":"integer","title":"Duplicates","description":"Duplicates over every line.","default":0},"rejected":{"type":"integer","title":"Rejected","description":"Rejected rows over the file.","default":0},"totals":{"$ref":"#/components/schemas/ImportTotals","description":"What loaded (or would): checked against `expected`."},"expected":{"$ref":"#/components/schemas/ImportExpectedTotals","description":"The control totals the run was given."},"mismatches":{"items":{"type":"string"},"type":"array","title":"Mismatches","description":"Each control total that did not match the file, in words. On an apply they refused, a last entry says that nothing was written and what to do: fix or leave out the rejected rows, or correct the totals, then run the dry run again."},"rejections":{"items":{"$ref":"#/components/schemas/ImportRowRejection"},"type":"array","title":"Rejections","description":"Rejected rows, the first 200; `rejected` counts them all. Every one is in the report (`GET /credit/import-batches/{id}/report`), which is available after a dry run too."},"warnings":{"items":{"$ref":"#/components/schemas/ImportWarning"},"type":"array","title":"Warnings","description":"Things to read before applying: run-level warnings first (a program with no interest ceiling), then rows. The first 200; `warning_count` has them all."},"warning_count":{"type":"integer","title":"Warning Count","description":"Every warning of the run.","default":0},"dry_run_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dry Run Token","description":"On a dry run: a digest of the batch, its mapping and this run's inputs (with `as_of` resolved). Send it on the apply as `dry_run_token` to apply exactly what this dry run checked. Null on an apply."},"opening_journal":{"anyOf":[{"$ref":"#/components/schemas/ImportOpeningJournal"},{"type":"null"}],"description":"On a program that posts to the general ledger: the opening-balance journal this run posted at the import date, or on a dry run would post. Null on a program that does not post: nothing is posted, now or later."}},"type":"object","required":["staging_id","report_id","dry_run","applied","as_of"],"title":"ProgramLoanImportResponse","description":"The outcome of a program-level dry run or apply."},"SuccessEnvelope_MigrationBuildResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/MigrationBuildResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[MigrationBuildResponse]"},"ErrorResponse":{"title":"ErrorResponse","type":"object","description":"Envelope returned by every non-2xx response. Branch on `code` (stable) rather than `message` (human-readable, may change).","required":["error","message","code"],"properties":{"error":{"type":"string","description":"Error category: `validation_error`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `server_error`, or `service_unavailable`."},"message":{"type":"string","description":"Human-readable explanation, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code in `CATEGORY_NNN` form (e.g. `NOT_FOUND_006`, `AUTH_010`, `GEN_002`)."},"request_id":{"type":"string","description":"Correlation id for support requests. Echoes the `x-request-id` request header when one was supplied."},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages keyed by field name. Present on 400/422 validation failures only."}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}