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

# Build custom Plaid account linking

> Connect customers' bank accounts from your own interface while Layer manages the Plaid connection.

You can launch Plaid Link from your own interface without embedding a Layer React component. Your application controls the button, loading states, and account-selection experience, while Layer creates the Plaid Link token, securely stores the resulting access token, and imports account activity.

This guide uses Plaid's React SDK in its examples. You can follow the same API flow with another [Plaid Link SDK](https://plaid.com/docs/link/).

## Prerequisites

Before starting, you will need:

* Plaid enabled for your Layer account
* The ID of the Layer business connecting an account
* A [business-scoped access token](/api-details/authentication#business-scoped-access-tokens)

Install Plaid's React SDK:

```bash theme={null}
npm install react-plaid-link
```

## Link an account

<Steps>
  <Step title="Generate business scoped access token">
    Follow [Authentication](/api-details/authentication#business-scoped-access-tokens) to generate an access token for the Layer business.
  </Step>

  <Step title="Request a Plaid Link token">
    When the customer chooses to connect an account, request a short-lived Link token from Layer.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
        --url https://sandbox.layerfi.com/v1/businesses/{businessId}/plaid/link \
        --header "Authorization: Bearer <business_access_token>" \
        --header "Content-Type: application/json" \
        --data '{}'
      ```

      ```typescript TypeScript theme={null}
      const response = await fetch(
        `https://sandbox.layerfi.com/v1/businesses/${businessId}/plaid/link`,
        {
          method: "POST",
          headers: {
            Authorization: `Bearer ${businessAccessToken}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({}),
        },
      );

      if (!response.ok) {
        throw new Error("Unable to start account linking");
      }

      const { data } = await response.json();
      const linkToken = data.link_token;
      ```
    </CodeGroup>

    Layer returns the token that your frontend will pass to Plaid:

    ```json theme={null}
    {
      "data": {
        "type": "Link_Token",
        "link_token": "link-sandbox-...",
        "hosted_link": null
      }
    }
    ```

    Request a new Link token each time the customer starts a new linking session. Do not persist or reuse it.

    For an implementation example, see the [`layer-react` Link-token request](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/api/businesses/%5Bbusiness-id%5D/plaid/link/index.ts#L11-L26).
  </Step>

  <Step title="Open Plaid Link">
    Initialize Plaid Link with the token returned by Layer. Open the modal only after the SDK reports that it is ready:

    ```tsx theme={null}
    import { useEffect } from "react";
    import { usePlaidLink } from "react-plaid-link";

    const { open, ready } = usePlaidLink({
      token: linkToken,
      onSuccess: (publicToken, metadata) => {
        void exchangePublicToken(publicToken, metadata.institution);
      },
      onExit: () => {
        setLinkToken(null);
      },
    });

    useEffect(() => {
      if (ready) {
        open();
      }
    }, [open, ready]);
    ```

    Plaid owns the credential-entry modal. Your application owns the interface that launches it and the experience shown before and after it.

    For an implementation example, see how [`layer-react` initializes and opens Plaid Link](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/features/linkedAccounts/usePlaidLinkModal.ts#L82-L113).
  </Step>

  <Step title="Exchange the public token">
    Plaid calls `onSuccess` with a short-lived public token and institution metadata. Send both to Layer:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
        --url https://sandbox.layerfi.com/v1/businesses/{businessId}/plaid/link/exchange \
        --header "Authorization: Bearer <business_access_token>" \
        --header "Content-Type: application/json" \
        --data '{
          "public_token": "public-sandbox-...",
          "institution": {
            "institution_id": "ins_123",
            "name": "Example Bank"
          }
        }'
      ```

      ```typescript TypeScript theme={null}
      type PlaidInstitution = {
        institution_id: string;
        name: string;
      };

      async function exchangePublicToken(
        publicToken: string,
        institution: PlaidInstitution | null,
      ) {
        const response = await fetch(
          `https://sandbox.layerfi.com/v1/businesses/${businessId}/plaid/link/exchange`,
          {
            method: "POST",
            headers: {
              Authorization: `Bearer ${businessAccessToken}`,
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              public_token: publicToken,
              institution,
            }),
          },
        );

        if (!response.ok) {
          throw new Error("Unable to connect the account");
        }
      }
      ```
    </CodeGroup>

    The `institution` value may be `null`. After a successful exchange, Layer securely stores the Plaid access token and begins importing account data.

    <Warning>
      Do not exchange the public token directly with Plaid. Layer must perform the exchange so it can manage the Plaid Item and synchronize its accounts.
    </Warning>

    For implementation examples, see the [`layer-react` exchange request](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/api/businesses/%5Bbusiness-id%5D/plaid/link/exchange.ts#L5-L16) and [success handler](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/features/linkedAccounts/usePlaidLinkModal.ts#L52-L79).
  </Step>

  <Step title="Load the connected accounts">
    After the exchange succeeds, call [List bank accounts](/api-reference/v1/list-bank-accounts):

    ```bash cURL theme={null}
    curl https://sandbox.layerfi.com/v1/businesses/{businessId}/bank-accounts \
      --header "Authorization: Bearer <business_access_token>"
    ```

    A newly connected account may still be synchronizing. Poll the endpoint while any entry in `external_accounts` has `is_syncing` set to `true`. As a reference, `layer-react` polls every five seconds and stops after 15 minutes.

    For implementation examples, see the [`layer-react` bank-account request](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/api/businesses/%5Bbusiness-id%5D/bank-accounts/useListBankAccounts.ts#L9-L24) and [polling configuration](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/contexts/BankAccountsContext/BankAccountsContext.tsx#L23-L62).
  </Step>

  <Step title="Confirm which accounts belong to the business">
    Layer pauses data synchronization for accounts that require confirmation. An external account requires relevance confirmation when its `notifications` array contains:

    ```json theme={null}
    {
      "type": "CONFIRM_RELEVANT"
    }
    ```

    If the customer uses the account for their business, call [Confirm external account](/api-reference/v1/confirm-external-account):

    ```bash cURL theme={null}
    curl --request POST \
      --url https://sandbox.layerfi.com/v1/businesses/{businessId}/external-accounts/{externalAccountId}/confirm \
      --header "Authorization: Bearer <business_access_token>" \
      --header "Content-Type: application/json" \
      --data '{"is_relevant": true}'
    ```

    If the account is not used for the business, call [Exclude external account](/api-reference/v1/exclude-external-account):

    ```bash cURL theme={null}
    curl --request POST \
      --url https://sandbox.layerfi.com/v1/businesses/{businessId}/external-accounts/{externalAccountId}/exclude \
      --header "Authorization: Bearer <business_access_token>" \
      --header "Content-Type: application/json" \
      --data '{"is_irrelevant": true}'
    ```

    Use the external account's `id`, not the containing bank account's `id`. Refetch the bank accounts after processing every selection. Confirming a relevant account allows Layer to begin synchronizing it.

    For implementation examples, see how [`layer-react` identifies accounts requiring confirmation](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/utils/bankAccount.ts#L4-L10) and [submits confirmations and exclusions](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/features/bankAccounts/useConfirmAndExcludeMultiple.ts#L27-L41).
  </Step>
</Steps>

## Repair a Plaid connection

When an external account has a non-null `connection_needs_repair_as_of` value, request an update-mode Link token. Pass that external account's `connection_external_id` as `plaid_item_id`:

```bash cURL theme={null}
curl --request POST \
  --url https://sandbox.layerfi.com/v1/businesses/{businessId}/plaid/update-mode-link \
  --header "Authorization: Bearer <business_access_token>" \
  --header "Content-Type: application/json" \
  --data '{
    "plaid_item_id": "<plaid_item_id>"
  }'
```

Open Plaid Link with the returned `link_token` using the same process described above. After Plaid calls `onSuccess`, do not exchange the public token. Instead, ask Layer to refresh the connection status:

```bash cURL theme={null}
curl --request POST \
  --url https://sandbox.layerfi.com/v1/businesses/{businessId}/external-accounts/update-connection-status \
  --header "Authorization: Bearer <business_access_token>"
```

Then refetch the business's bank accounts.

For implementation examples, see the [`layer-react` update-mode token request](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/api/businesses/%5Bbusiness-id%5D/plaid/update-mode-link.ts#L6-L23) and [update-mode success handling](https://github.com/Layer-Fi/layer-react/blob/v0.1.141/src/hooks/features/linkedAccounts/usePlaidLinkModal.ts#L85-L104).

## Handle failures

Your interface should let the customer retry each network operation independently:

* If Link-token creation fails, keep the customer on your connection screen and request a new token when they retry.
* If the customer exits Plaid Link, discard the Link token.
* If the public-token exchange fails, show an error and let the customer start a new linking session.
* If account synchronization takes longer than expected, stop polling and provide a manual refresh action.
