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

# Link from the Bilt app

> Build the endpoints Bilt calls to link, or create and link, an account when a member starts in the Bilt app

In this flow, the member links from the Bilt app. Bilt calls endpoints you host with the member's verified contact, and each link completes in one request. There is no redirect, callback, or polling.

## Before you begin

* Agree base URLs, credentials, and contacts with Bilt. See [Environments and credentials](/partners/environments-and-credentials.md).
* Agree contact normalization: E.164 for phones, and a policy for email case and plus-addressing.

## How it works

```mermaid theme={null}
sequenceDiagram
    participant Member
    participant Bilt
    participant You as Your backend
    Member->>Bilt: Link my account
    Bilt->>You: POST /bilt/lookup-contact
    You-->>Bilt: exists, partnerMemberId
    Bilt->>You: POST /bilt/link-account
    You-->>Bilt: 200 linked
    Bilt-->>Member: Partner account linked
    You-->>Member: Notify: your account was linked to Bilt
```

After a link succeeds, tell your customer through your own channel that their account was linked.

Implement these endpoints on your server. Bilt calls them in this order.

| Endpoint you host | What Bilt uses it for | Reference |
| - | - | - |
| `POST /bilt/lookup-contact` | Check whether you hold an account for the member's verified contact | [Look up a contact](/api-reference/endpoints-you-host/look-up-a-contact.md) |
| `POST /bilt/link-account` | Link the account you found to the Bilt member | [Link an account](/api-reference/endpoints-you-host/link-an-account.md) |
| `POST /bilt/create-and-link-account` | Open and link a new account, if enabled for you | [Create and link an account](/api-reference/endpoints-you-host/create-and-link-an-account.md) |
| `POST /bilt/unlink-account` | End the link when the member disconnects in the Bilt app | [Unlink an account](/api-reference/endpoints-you-host/unlink-an-account.md) |

## Steps

**1. Look up the contact**

Bilt calls [Look up a contact](/api-reference/endpoints-you-host/look-up-a-contact.md) first. Return the `partnerMemberId` you find, or `200` with `exists: false`. Keep it read-only.

**2. Link the account**

Bilt calls [Link an account](/api-reference/endpoints-you-host/link-an-account.md). Run these checks in order:

1. Validate the bearer token, and return `401` before you read the body.
2. Return `403` unless `contact.isVerified` is `true`. A missing value is not `true`.
3. Return `400` for an inconsistent body, such as a malformed contact or UUID.
4. Resolve the account by exact match on the verified contact. Use `partnerMemberId` only to choose between matches. Return `404` when nothing matches.

Never create an account from this endpoint, and never use fuzzy matching.

**3. Create and link, if you support it**

[Create and link an account](/api-reference/endpoints-you-host/create-and-link-an-account.md) is optional, enabled per partner. Bilt calls it only when a member with no account asks to open one. Run the link account checks.

If you already hold an account for the contact, return `409 account_exists` and write nothing. Bilt then links that account through link account with the same `idempotencyKey`. Use `account_exists` only for that case.

Lookup sends a flat contact and link account a nested one, so do not share a serializer.

### Make links safe to retry

Bilt retries a timeout or `5xx` with the same `idempotencyKey`. A retry must never leave a second link or account. Use one of these approaches:

* Store the key, and tell Bilt how long you keep it.
* Enforce one active link per account and per member, and answer a repeat with success.

You do not need to replay the original response byte for byte.

A retried create and link arrives as link account after a fresh lookup. Answer it with `200 linked`, and do not refuse the key as spent.

> [!WARNING]
>
> **Neither approach stops a delayed retry after the link ends.** A late retry carries the same verified contact as a fresh request, and a structural check silently relinks the account. Treat each key as single-use, and refuse it once its link has ended. Bilt rejects the mirror case on its side with a `409`.

## What can go wrong

* The bearer token is missing or wrong. Answer [`401`](/api-reference/endpoints-you-host/link-an-account) before you read the body.
* The contact is not verified. Answer [`403`](/api-reference/endpoints-you-host/link-an-account), and never link it.
* You already hold an account on create and link. Answer [`409 account_exists`](/api-reference/endpoints-you-host/create-and-link-an-account) and write nothing.
* A retry arrives after the link ended. Refuse the spent `idempotencyKey`, or the account is silently relinked.

## Next steps

* [Unlink an account](/partners/account-linking/unlink-an-account.md) covers the unlink endpoint you host.
* [Test and go live](/partners/test-and-go-live.md) has the full checklist.


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