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

# Idea financial acceptance

# Idea Financial term-loan submit: config readiness and E2E acceptance

Status: acceptance runbook (FM-2496)\
Target: 2026-09-16, contingency 2026-09-17\
Last updated: 2026-09-14

## Scope

PR #1675 shipped the Idea Financial destination in the term-loan Submit dialog
behind the location-scoped Labs card `idea-financial-term-loan` (visible to
every location, off by default). One destination per Submit: the offer
marketplace (Engine, unchanged) or Idea Financial. The Idea branch runs
synchronously on Submit: sign in (client credentials) → create application →
upload the newest bank statements on file (up to 4, one at a time) and a
separate signed application → push.

This runbook covers only that slice. Not in scope: Credibly, line of credit,
offer accept, checkout requirements, Agents UI, webhooks, retries, billing.
Status polling and the offer-page handoff landed later under FM-2711
(`convex/ideaFinancialStatus.ts`): one read right after submit, then a
15-minute sweep for up to 30 days per application.

## What is in the repo

| Piece | Where |
| - | - |
| Mapper + HTTP client (sign-in, create, upload, push) | `convex/lib/ideaFinancial.ts` |
| Labs gate, CRM business read, credit score, documents, row lifecycle | `convex/lib/ideaFinancialSubmit.ts` |
| Destination routing in `submit` / `submitResubmission` | `convex/termLoanSubmissions.ts` |
| Readiness check (env, Labs state, optional sign-in probe) | `convex/ideaFinancialReadiness.ts` |
| Send To pick, consent statement, Idea result state | `components/term-loan/SubmitTermLoanDialog.tsx`, `TermLoanOfferBanner.tsx`, `TermLoanHeaderButton.tsx` |
| Operator docs | `docs/operators/funding/idea-financial.mdx` |
| Tests | `tests/ideaFinancialAdapter.test.ts`, `tests/ideaFinancialSubmitRouting.test.ts`, `tests/ideaFinancialReadiness.test.ts`, `tests/submitTermLoanDialogDisclosures.test.ts`, `tests/labsResolve.test.ts` |

## Configuration

Six Convex deployment variables. Set them on the deployment under test with
`bunx convex env set NAME value` (add `--prod` only with explicit approval for
the production write). While any is unset, Submit to Idea reports "Idea
Financial is not set up for this account yet" and sends nothing.

| Variable | Value | Source |
| - | - | - |
| `IDEA_FINANCIAL_ENVIRONMENT` | `sandbox`. The only value the client accepts; anything else (including `production`) leaves the deployment unconfigured, so the submit path and the status poll fail closed. Switching to Idea's live key is a separate follow-up (see the TODO on `IDEA_FINANCIAL_SUPPORTED_ENVIRONMENTS` in `convex/lib/ideaFinancial.ts`). | This repo |
| `IDEA_FINANCIAL_OAUTH_URL` | `https://oauth.ideafinancial.net` — the sandbox token issuer per Idea's API docs. The production issuer, `https://oauth.ideafinancial.com`, is refused by `readIdeaFinancialEnv` while the client is sandbox-only, even under `IDEA_FINANCIAL_ENVIRONMENT=sandbox`. | Idea Financial API docs |
| `IDEA_FINANCIAL_API_URL` | `https://partner.api.ideafinancial.net` (v1.0) per Idea's API docs, which give one API base for both issuers. | Idea Financial API docs |
| `IDEA_FINANCIAL_CLIENT_ID` | Broker API client id | Idea Financial |
| `IDEA_FINANCIAL_CLIENT_SECRET` | Broker API client secret | Idea Financial |
| `IDEA_FINANCIAL_AGENT_ID` | Numeric agent id the applications are filed under | Idea Financial broker portal |

Deployment state observed 2026-09-14 (read-only):

* Worktree dev deployment: `ENGINE_BEARER_TOKEN` set, no `IDEA_FINANCIAL_*` set.
* Production: not inspectable from the agent machine (no production key). Treat as
  unset until someone with access runs the readiness check there.

### Readiness check

```bash theme={null}
# Env only. Safe anywhere; makes no request.
bunx convex run ideaFinancialReadiness:check '{}'

# Env + Labs state for one location + live sign-in probe (one POST to {oauth}/token,
# creates nothing in Idea's portal). Run only against the environment you are
# authorized to hit.
bunx convex run ideaFinancialReadiness:check '{"locationId":"<locationId>","probeSignIn":true}'
```

The report carries hostnames and booleans only, never the client id, secret,
or token. Read it as:

| Field | Ready when |
| - | - |
| `configured` / `missing` | `true` / `[]` |
| `environment` | `sandbox`. Any other declared value is echoed back; an `IDEA_FINANCIAL_OAUTH_URL` on the production issuer reports `production (oauth.ideafinancial.com)`. Either way the deployment stays unconfigured. |
| `oauthHost`, `apiHost` | `oauth.ideafinancial.net` and `partner.api.ideafinancial.net` for the sandbox (Idea API docs) |
| `labs.enabled` | `true` for the acceptance location |
| `signIn.ok` | `true`. `status: 405` means the token endpoint rejected POST (see dependency 1). `400`/`401` means the credentials or grant were not accepted. |

### Turning the Labs card on

Operator: Settings → Labs → "Idea Financial business funding". Admin: Admin →
Settings → Labs, hand the card to the acceptance location. The card is
visible everywhere and off by default, so no operator sees the Send To pick
until it is turned on for their location.

## E2E acceptance path (synthetic data only)

Run against Idea's test tenant. Brock holds a broker-portal invite for
"\[API-TEST] 7 FIGURES CONSULTANT LLC" (email from Idea, 2026-08-03, subject
tagged STAGING). Never submit a real person. Use a synthetic owner (name,
email, phone, DOB, SSN) that Idea's test tenant accepts and a synthetic
business (name, EIN).

1. **Readiness.** Run the readiness check with `probeSignIn: true` against the
   deployment and location under test. Expect `configured: true`,
   `labs.enabled: true`, `signIn.ok: true`. Save the JSON.
2. **Fixture.** On a dev or preview location, create a contact with the CRM
   custom fields Business Name, EIN (9 digits), Business Structure, Business
   Address, Business City, Business State, Business ZIP, Time in Business, and
   Average Monthly Revenue (monetary). Upload two synthetic PDFs as bank
   statements and a distinct synthetic **Signed Application** in Documents. For
   the application, choose **Verify Signed Application**, confirm that the PDF is
   signed, and enter its signed date before submitting.
   Optionally attach a credit report so FICO is sent.
3. **Positive path.** Open the client → Generate Offers → fill the form →
   Send To: Idea Financial. Confirm the marketplace disclosures are hidden and
   the Idea consent statement is shown. Submit. Expect:
   * dialog closes with success; the client page shows "Application sent to
     Idea Financial" with the Idea application number;
   * `termLoanSubmissions` row: `destination: "idea_financial"`,
     `status: "success"`, `ideaFinancialApplicationId` set, no SSN stored;
   * in Idea's portal: the application exists under the configured agent with
     both statements and the signed application attached, then pushed to
     underwriting.
4. **Zero statements.** Repeat on a contact with no bank statements. Expect
   Submit to stop before a row or partner application is created and ask for a
   bank statement.
5. **Missing signed application.** Repeat with statements but no signed
   application. Expect Submit to stop before a row or partner application is
   created and ask for the signed application.
6. **Rejected signed application.** Reject the verified application. Expect its
   signing evidence to be cleared and ordinary document approval to be refused;
   only a fresh **Verify Signed Application** confirmation can make it eligible
   again.
7. **Missing EIN.** Clear the EIN custom field and Submit. Expect the dialog
   to stop with "Idea Financial needs the following … EIN (9 digits) …", no
   row written, nothing sent.
8. **Idea rejection.** Submit with a value Idea rejects (for example an EIN
   their validation refuses). Expect the row marked `error`, the operator
   message quoting Idea's field message, and `errorDetails` holding Idea's
   response body and step (never our request body).
9. **Labs off.** Turn the card off for the location. Expect the Send To pick to
   disappear and marketplace Submit to behave exactly as before.
10. **Resubmit.** From an Idea-sent file, open Resubmit. Expect no Send To pick
    (resubmission stays on the marketplace path).

Evidence to attach to FM-2496: the readiness JSON, the submission row(s) with
`errorDetails`, portal screenshots for steps 3 and 7, and the test run below.

### Automated evidence (already in the repo)

`bun test tests/ideaFinancialAdapter.test.ts tests/ideaFinancialSubmitRouting.test.ts tests/ideaFinancialReadiness.test.ts`

The routing file includes an end-to-end run of the real `submit` action with
Labs on: a fake CRM answers the contact read and a fake Idea validates the
bearer token, the JSON create body (golden), the six multipart upload fields,
the distinct signed-application upload, the push, and the row lifecycle, plus
the missing-document, missing-business-fact, create-rejected, and
retry-after-failed-push cases. The application lookup regression crosses six
full 100-row pages before finding the signed application, so a 500-document
window cannot silently turn a valid older file into “missing.”

## Dependencies (answers needed, not code)

| # | Question | Owner | Status | How it resolves |
| - | - | - | - | - |
| 1 | **Token endpoint verb.** Collection showed `GET {oauth}/token`; the client POSTs the form. | Idea Financial | Closed 2026-10-02 | Idea's API docs (staging portal, Admin → API Docs) specify `POST /token`, `application/x-www-form-urlencoded`, `grant_type=client_credentials`, `client_id`, `client_secret` → `access_token`, `token_type`, `expires_at`, `refresh_token`. Matches the client. |
| 2 | **`timeInBusiness` enum.** Required on create. | Idea Financial | Closed 2026-10-15 | Idea's API docs list `zero-one-years`, `one-two-years`, `over-two-years`; `entityType` is also required and limited to `sole-proprietorship`, `limited-liability-company`, `corporation`, `limited-partnership`, `general-partnership`, `not-for-profit`, `public`, `legal-partnership`, `other`. `mapTimeInBusiness` (Business Start Date first, lower bucket for a spanning range) and `mapEntityType` translate CRM values. |
| 3 | **OAuth URL.** Issuer seen in the collection JWT is `oauth.ideafinancial.net`; the variable is blank. | Idea Financial | Closed 2026-10-02 | API docs: sandbox token `POST https://oauth.ideafinancial.net/token`, production token `POST https://oauth.ideafinancial.com/token`. The client refuses the production issuer (`IDEA_FINANCIAL_PRODUCTION_OAUTH_HOST`). |
| 4 | **Staging vs production API host.** The portal invite is tagged STAGING but links `partner.ideafinancial.net`; the collection uses `partner.api.ideafinancial.net`. | Idea Financial | Closed 2026-10-02 | API docs give a single base, `https://partner.api.ideafinancial.net` v1.0; sandbox and production differ by token issuer, not API host. Hence the issuer check above. |
| 5 | **Client credentials + agent id** for the test tenant. | Idea Financial / Brock | Open | Set on the dev or preview deployment; readiness check goes green. |
| 6 | **Synthetic identity** Idea's test tenant accepts (owner SSN/DOB, business EIN). | Idea Financial | Open | Needed for steps 3 to 6. Without it, acceptance is limited to the automated suite. |
| 7 | **Consent copy.** `IDEA_FINANCIAL_CONSENT_STATEMENT` replaces the marketplace bullets for Idea. | MFM legal | Open | Legal sign-off on the wording in `lib/config/termLoanDisclosures.ts`. |
| 8 | **Postman collection export** is gitignored and not present on the agent machine. | Brock | Superseded 2026-10-02 | Idea's API docs at [https://partner.ideafinancial.net/admin/api-docs](https://partner.ideafinancial.net/admin/api-docs) are the source of truth; the collection was not downloaded. Documented `GET /v1/applications/{id}` statuses: processing, draft, submission-incomplete, closing-incomplete, conditional-offer, offer, closing, contract-ready, contract-out, open, funded, closed, dormant, abandoned, not-interested, declined (`IDEA_FINANCIAL_APPLICATION_STATUSES`). No webhook, polling interval or sync endpoint is documented; the app reads status on its own 15-minute schedule and on demand. |

Known edge, documented on the operator page: a retry after a failure that
happened after create leaves an earlier application in Idea's portal. The row
keeps its own id, so support can find it.

## 2026-09-17 contingency

If dependency 5 (credentials) is still open — 1, 3 and 4 closed on 2026-10-02 against Idea's API docs:

* Acceptance is limited to the automated suite and the readiness check's
  "not configured" path. No live sign-in probe is run.
* The Labs card stays off (its default), so no operator sees the Send To pick
  and marketplace Submit is unaffected.
* No env is set on production. Record the open items on FM-2496 and re-run
  this runbook once Idea answers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.