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

# Onboarding

> Answer the company questionnaire, connect banks, and follow the first import through the API or MCP

Onboarding sets up a new company's books: answer a short questionnaire, receive Finta's eligibility decision, connect the selected banks and cards, and wait for the first import. You can complete this through the API or an AI assistant using the MCP tools.

[`GET /onboarding`](https://www.finta.com/docs/api-reference/onboarding/retrieve-onboarding) reads the current setup. [`PATCH /onboarding`](https://www.finta.com/docs/api-reference/onboarding/update-onboarding) saves answers, submits them for a decision, or changes the ready-email preference. The MCP equivalents are `get_onboarding` and `update_onboarding`.

Both endpoints require **Company: Manage**, **Integrations: Manage**, and the usual **API access: Manage**. MCP writes also require `finta:write`; a read-only connection cannot submit answers. Onboarding calls never consume the [monthly API or MCP allowance](https://www.finta.com/docs/api-reference/usage-limits.md#onboarding-calls), including after setup is complete. Existing authentication, billing and burst checks still apply.

## Read current setup

```http theme={null}
GET /api/v1/onboarding HTTP/1.1
Authorization: Bearer finta_your_key_here
```

The response is an `object: "onboarding"` with `status`, saved `answers`, remaining `questions`, the eligibility `decision`, selected `integrations`, first-import `sync` progress, `notify_when_synced`, and `next_step`.

| Value | Meaning |
| - | - |
| `questionnaire` | Required questions are unanswered. |
| `ready_to_submit` | Every required question is answered and Finta hasn't decided yet. |
| `call_required` | Finta needs a call with the company before it can onboard it. Follow-up questions may come first. |
| `not_eligible` | Finta can't onboard the company. `decision.reason` says why. |
| `connect_banks` | Approved. The selected banks and cards are being connected. |
| `syncing` | Finta is importing transactions and setting up the books, or a completed web-started import awaits `finish_in_web`. |
| `complete` | The company is onboarded. |

`decision` is null until Finta decides. Its `outcome` is `approved` (Finta can onboard the company), `call_required` (a call is needed first), or `not_eligible` (Finta cannot onboard it). `reason` explains only a not-eligible outcome and is null otherwise. `decided` is the Unix decision timestamp, or null for older decisions without a recorded time.

## Follow the next step

| Value | What to do |
| - | - |
| `answer_questions` | Ask two or three related `questions` at a time and save quietly with `PATCH /onboarding`. |
| `confirm_and_submit` | Recap every answer, stop, then send `submit: true` in a later turn after a separate explicit yes. |
| `book_call` | Share `next_step.url` (`https://cal.com/finta/intro`) so the user can book a call. |
| `contact_support` | Explain `next_step.message` and share `next_step.email` (`support@finta.com`): Finta either cannot onboard the company (`decision.reason`) or needs to help with a failed import whose connection is still active. |
| `connect_banks` | Connect, or reconnect, the integrations `next_step.message` names. |
| `start_import` | Every selected bank is connected but importing has not started. Send `PATCH /onboarding` with `{"answers":{}}` (MCP: `update_onboarding`), then follow the returned `next_step`. |
| `wait_for_sync` | Wait `sync.check_again_in_seconds` (30 when `sync` is null), then call `GET /onboarding` again. |
| `finish_in_web` | A web-started import is finished. Ask the user to switch to the company named in `next_step.message` in Finta first, then open `next_step.url`, choose See dashboard, and read onboarding again. |
| `none` | Nothing; onboarding is complete. |

`next_step.message` is written for the agent. On an MCP connection it names the MCP tools (`update_onboarding`, `get_onboarding`, `connect_integration`); with an API key it names the endpoints. `url` is present only with `book_call` or `finish_in_web` and `email` only with `contact_support`.

For a new setup, open with: "Welcome to Finta! A few company questions, then connect your banks and we'll set up your books."

Save what you can already answer from chat, local files or connected tools within the user's permissions. Do not re-ask known answers or guess without a source; conflicting sources count as unknown. Ask only missing questions, two or three related questions at a time, and save quietly.

For questions with options, use your app's multiple-choice question tool if available. Otherwise ask in plain text without numbered lists, showing options only if asked. Map the user's answer to the accepted values. Banks and cards have no displayed options: gather them from context or ask in plain text.

Before submitting, recap **every answer**, naming the source of any answer you found, then stop. Send `submit: true` only in a later turn after a separate explicit yes to that recap. A yes selected in the multiple-choice tool counts; an earlier confirmation does not. Tool descriptions are brief: follow the returned `next_step.message` for the conversation and connection workflow.

## Questions and answers

The API names differ from the web form's in places. Each answer is `null` (or an empty list) until answered.

| Key | Type | Meaning |
| - | - | - |
| `incorporation_year` | integer | The year the company incorporated, such as 2024. Any year from 1900 to the current year (the web app's follow-up dropdown stops at 15 years); a company incorporated 3 or more years ago needs a call with Finta. `null` when the user gave only a range in the web app. |
| `legal_entity_type` | string | `c_corp` (C corporation), `pbc` (public benefit corporation), `llc` (limited liability company), `s_corp` (S corporation) or `other`. |
| `legal_entity_state` | string | The USPS 2-letter code of the US state it incorporated in, such as `DE`. New answers accept only the 50 states. Saved legacy values may return `DC`, another mapped code or null; an unchanged echo preserves them. The web form preselects Delaware, the API doesn't. |
| `industry` | string | What it sells to make revenue: `software`, `services`, `marketplace`, `biotech`, `payments_and_financial_products` (financial payments), `crypto` or `hardware`. One value. |
| `international_subsidiaries` | boolean | Whether it has, or will soon have, foreign subsidiaries. |
| `banks` | string\[] | `mercury`, `brex`, `rho`, `svb`, `chase`, `ramp`, `american_express`, `boa`, `citi`, `arc`, `slash`, or `other` with `other_bank`. Recognized "other" names, such as "Bank of America", save the same canonical key (`boa`) accepted directly. |
| `other_bank` | string | The bank typed in under `other`. Required when `banks` includes `other`. |
| `credit_cards` | string\[] | `mercury`, `brex`, `ramp`, `rho`, `chase`, `american_express`, `boa`, `citi`, `arc`, `slash`, or `other` with `other_credit_card`. Optional; empty for none. Recognized "other" names save the same canonical key accepted directly. |
| `other_credit_card` | string | The card typed in under `other`. Required when `credit_cards` includes `other`. |
| `accounting_software` | string | Call follow-up: what it uses for accounting now. |
| `files_taxes_with` | string | Call follow-up: what it uses to file taxes now. |
| `goals` | string | Call follow-up: what it wants to accomplish with Finta. |
| `subsidiary_countries` | string\[] | Call follow-up: the countries it has subsidiaries in, as option values such as `canada`. |

Text answers are up to 500 characters. The web app's "No bank yet" and "No credit cards yet" options aren't offered for new API selections. GET can return their saved values (`no_bank_yet` and `no_credit_cards_yet`). PATCH accepts an unchanged saved bank/card pair containing them, before or after the decision; a new or changed selection containing them returns `400 invalid_request`. Both the OpenAPI request enums and MCP input enums include these values to permit unchanged replay; they remain unavailable for new selections.

New answers can use canonical provider keys directly. These answers save the same values as `banks: ["other"], other_bank: "Bank of America"` and `credit_cards: ["other"], other_credit_card: "American Express"`:

```json theme={null}
{"answers":{"banks":["boa"],"credit_cards":["american_express"]}}
```

Saved API or MCP drafts can also resume in the web app after the first step. The shared eligibility rules recognize exact years and aged ranges: a company incorporated 3 or more years ago still needs a call, while a recent exact year does not itself require one. Follow the returned decision for saved ranges whose age is unknown.

`questions` lists what's still to ask at this stage, in order: the required questions that are unanswered and, while no card is chosen, the optional `credit_cards` question. Each is `{ key, label, description?, type, required, options }`, where `type` is `single_select`, `multi_select`, `boolean`, `year` or `text`, and `options` is a list of `{ value, label }` (empty for `banks`, `credit_cards`, `boolean`, `text` and `year`). Once an answer means Finta can't onboard the company (an S corporation or "other" entity, or a crypto business), the later questions aren't asked. While a call is required, the follow-ups are asked only when Finta needs them: the call-history questions for a company incorporated 3 or more years ago, and `subsidiary_countries` for one with foreign subsidiaries.

The API and MCP show the choices below, in order. Banks and credit cards have `options: []` and keep type `multi_select`; the agent gathers answers from context or asks in plain text and maps them to accepted values. These four questions omit `description`. The shorter displayed lists do not restrict the accepted input values: use the complete API request schema or MCP input enums, including `other` with `other_bank` or `other_credit_card`. State and year questions use their own returned choices and types.

| Question | Shown label (`value`), in order |
| - | - |
| `legal_entity_type` | C Corporation (`c_corp`), LLC (`llc`), Public benefit corporation (`pbc`), S Corporation (`s_corp`) |
| `industry` | Software (`software`), Hardware (`hardware`), Marketplace (`marketplace`), Payments or fintech (`payments_and_financial_products`) |
| `banks` | None (`options: []`) |
| `credit_cards` | None (`options: []`) |

## Save and submit

Send only the answers to change. This example saves a draft without requesting a decision:

```http theme={null}
PATCH /api/v1/onboarding HTTP/1.1
Authorization: Bearer finta_your_key_here
Content-Type: application/json

{"answers":{"industry":"software","banks":["mercury","chase"]}}
```

Once all required questions are answered and the user has separately confirmed the recap, submit:

```http theme={null}
PATCH /api/v1/onboarding HTTP/1.1
Authorization: Bearer finta_your_key_here
Content-Type: application/json

{"submit":true}
```

Both successful updates return HTTP 200 with the current onboarding object.

* Omitted answers keep their saved values. Every request field is optional.
* The API and MCP `update_onboarding` input schema accept `null` for every answer field, including enum answers. This lets clients replay nullable answers or clear an optional answer, such as `{"answers":{"incorporation_year":null}}` while that follow-up is editable. Clearing a required answer is rejected, and answer locks still apply. The `answers` object, array items, `submit`, and `notify_when_synced` do not accept `null`.
* An invalid answer, an unknown key, or the wrong type rejects the whole update with `400 invalid_request`; nothing is saved.
* Submitting with missing required answers returns `400 answers_incomplete` and the `missing` keys. **Valid answers in that request are saved.** Ask only what's missing and submit again after confirmation.
* After the decision, eligibility answers are locked (`409 onboarding_locked`). While a call is required, the exact `incorporation_year`, `accounting_software`, `files_taxes_with`, `goals`, and `subsidiary_countries` follow-ups can still change.
* Repeating already-saved answers or a successful submit returns 200 with the current state. Matching an existing answer is not a change, so retrying a request is safe while onboarding is unfinished.
* After `status: "complete"`, every PATCH returns `409 onboarding_complete`; GET remains available.

### Replay answers saved on the web

After a web eligibility decision, you can send GET's `answers` back unchanged without clearing the underlying web answers or triggering an answer lock:

* A null `incorporation_year` preserves a saved web range when no exact year was answered.
* An unchanged `legal_entity_state` code preserves the saved state, including `DC` for a saved legacy value. If a legacy web value cannot map to a code, echoing the returned null preserves that value too. New `DC` selections are rejected.
* The returned `industry` is the first saved industry; echoing it preserves any additional industries saved on the web.
* Bank/card pairs compare their normalized saved values, including an `other` answer folded into a known bank or card. The returned canonical slugs can be replayed through the API or schema-enforcing MCP clients: unchanged echoes return 200 without rewriting the company or questionnaire, before or after submission. Blank text and subsidiary-country lists use their normalized comparisons too.

These unchanged echoes do not expand the choices for new answers. Actual changes still obey validation and the decision locks. An approved update can still start the first import when all selected banks are connected; follow the returned `next_step` even when no answers changed.

## Connect banks and follow the import

Connection creation and status retrieval become available only after approval (`status` is `connect_banks` or `syncing`). Follow `next_step.message`: use the [integration connect endpoint](https://www.finta.com/docs/api-reference/integrations/connect-integration.md), or MCP `connect_integration`, for new OAuth and Plaid connections, including Chase and SVB. The user approves on the returned provider or [Plaid Hosted Link](https://www.finta.com/docs/api-reference/data-model.md#connecting-a-plaid-bank) page; then check the request with [`GET /integrations/{key}/connect`](https://www.finta.com/docs/api-reference/integrations/retrieve-connect-request), or `get_integration_connection`. A Plaid link lasts 30 minutes and needs no Finta sign-in, so share it only with the user. The return page does not confirm success; use the connect request's status.

For a Plaid bank that needs reauthentication, follow the web get-started page instructions in `next_step.message`, then send `PATCH /onboarding` with `{"answers":{}}` (MCP: `update_onboarding`) to continue setup. This applies both before and during the first import. Hosted Link cannot reconnect an existing Plaid bank: the connect endpoint returns [`409 integration_already_connected`](https://www.finta.com/docs/api-reference/errors#integration_already_connected), even when the bank needs reconnection. OAuth reconnects continue through the API or MCP connect flow.

OAuth callbacks and successful Plaid Hosted Link sessions both start onboarding's first import when the last selected bank connects. If the user connected banks in the web app instead, an empty-answer onboarding update can start the import as described below.

The first import starts automatically after the last selected bank connects through the API or MCP. If all selected banks were already connected, an approving update can start it immediately and return `status: "syncing"`. If a later read finds every selected bank connected but the import has not started, `next_step.action` is `start_import`. Send `PATCH /onboarding` with `{"answers":{}}` (MCP: `update_onboarding`) to start it, then follow the returned next step; reading alone cannot start an import. API-started onboarding also finishes automatically; it does not need a dashboard button. Web-started onboarding keeps its web flow. If an agent takes over after that import finishes, `next_step.action` is `finish_in_web`: ask the user to switch to the company named in `next_step.message` in Finta first, then open `next_step.url`, choose See dashboard, and read onboarding again. A browser session can have a different company selected from the API key or MCP connection. The status stays `syncing` until that click; waiting alone cannot complete this web flow.

Finta automatically retries interrupted job handoffs for API import start and completion. Keep following the returned next step and paced checks.

The banks and cards the company selected that onboarding waits on, in this order: Mercury, Brex, Ramp, Rho, SVB, Chase. A bank or card typed in under "other" isn't listed, as in the web app. Each `status` is:

| Value | Meaning |
| - | - |
| `not_connected` | Follow `next_step.message`: OAuth and Plaid banks, including Chase and SVB, use `POST /integrations/{key}/connect` (MCP: `connect_integration`). |
| `connected` | Connected; its first sync hasn't finished. |
| `synced` | Its first sync finished. |
| `failed` | Its first sync failed, or the connection needs reauthenticating. Follow `next_step`: reconnect Plaid banks in the web app and OAuth providers through the API only when reauthentication is required; a failed import with an active connection goes to Finta support. |

`sync` is present only while `status` is `syncing`: `{ "stage": ..., "check_again_in_seconds": 30 }`. `stage` is `downloading` (downloading transactions), `categorizing`, `matching_transfers` (matching transfers and preparing the dashboard, the last step) or `failed` (a bank's sync failed; `next_step` names it and offers reconnection if reauthentication is required, otherwise `contact_support`).

Tell the user Finta is pulling in transactions and setting up the books. Wait `sync.check_again_in_seconds` before reading onboarding again, or 30 seconds if `sync` is null. Avoid a tight polling loop. Follow `next_step.message` to reconnect an expired connection or contact support about an import failure with an active connection. A new connection link cannot retry an import while that connection is still active.

If you created the user's Finta account, tell them once at `wait_for_sync` to open the environment's login URL in `next_step.message`, choose **Forgot password**, and set a password so they can use Finta in their browser later.

### Ready email

At the import wait step, offer the ready email once if it has not already been discussed. Honor a preference the user volunteers at any time. Send `{"notify_when_synced":true}` with PATCH, or the same argument to `update_onboarding`, to email the user making that request when the first sync finishes. If it already finished, Finta sends the email right away. `false` withdraws the preference. It can change after at least one question is answered and until onboarding is complete.

## Endpoints available during onboarding

Until `status: "complete"`, other endpoints return [`403 onboarding_incomplete`](https://www.finta.com/docs/api-reference/errors#onboarding_incomplete). This includes companies that require a call or are not eligible. These are the exceptions:

| Endpoint | Available when |
| - | - |
| `GET /onboarding`, `PATCH /onboarding` | Throughout onboarding; PATCH is locked once complete |
| `GET /integrations` | Every onboarding state |
| `POST /integrations/{key}/connect`, `GET /integrations/{key}/connect` | Only after approval (`connect_banks` or `syncing`) |
| `POST /team/invitations` | To invite a teammate who can approve a connection |
| `POST /feedback` | To contact Finta support |

These exceptions still require their usual permissions. Other access checks, including the existing user, company-closed and subscription checks, take precedence. An unknown route remains `404 resource_missing`.

This is a change for existing credentials belonging to an unfinished company: API and MCP access is now held until onboarding completes. The MCP connection stays connected and `tools/list` still lists all tools; gated calls return an onboarding tool error. Finish onboarding before retrying those calls.

## Errors and retries

| Code | Meaning and next action |
| - | - |
| [`invalid_request`](https://www.finta.com/docs/api-reference/errors#invalid_request) | Fix the unknown field, invalid value or type. Nothing was saved. |
| [`answers_incomplete`](https://www.finta.com/docs/api-reference/errors#answers_incomplete) | Valid answers were saved. Ask the keys in `missing`, then submit again. |
| [`onboarding_locked`](https://www.finta.com/docs/api-reference/errors#onboarding_locked) | A decided answer cannot change, or a follow-up is no longer open. Repeating the rejected edit will not help. |
| [`onboarding_complete`](https://www.finta.com/docs/api-reference/errors#onboarding_complete) | Setup is finished. Read onboarding or use other endpoints instead of updating it. |
| [`onboarding_incomplete`](https://www.finta.com/docs/api-reference/errors#onboarding_incomplete) | Read onboarding and follow `next_step`; complete setup before retrying the gated call. |

Treat unfamiliar future status, action or error values defensively. Preserve the returned message and documentation link for diagnosis rather than parsing human-readable text as a stable contract.


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