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

# Reuse Merchant Sessions

> Save a merchant session after sign-in and reuse it on later checkouts

Some purchases require a merchant account for saved addresses, member prices, order history, or a cart the user already filled. A browser profile lets Agent Checkouts save the merchant session after the user signs in and load it on later runs.

Complete sign-in through [buyer input requests](/agents/checkouts/buyer-inputs). Password collection is separate from the saved session.

<video className="w-full rounded-xl" autoPlay muted loop playsInline src="https://mintcdn.com/crossmint/-Rn7L8WaqdsO5e7L/images/agents/checkouts/authenticated-stores.mp4?fit=max&auto=format&n=-Rn7L8WaqdsO5e7L&q=85&s=2d7a61a83be24e4ba9f282ab97217248" data-path="images/agents/checkouts/authenticated-stores.mp4" />

## Prerequisites

* **Agent Checkout integration**: Complete the [Agent Checkouts quickstart](/agents/agent-checkouts-quickstart) so your application can start a run, observe it, and send messages.
* **Server-side API key**: Use a production key with `agent-checkouts.create`, `agent-checkouts.read`, `agent-checkouts.update`, and `agent-checkouts.browser-profiles.create` scopes.

Browser profiles are scoped to a user. With a server-side key, identify that user with `x-crossmint-user-id` on every request.

<Warning>Always send `x-crossmint-user-id` with a server-side key. Without it, requests use your project's shared service subject, so different users could share the same browser profile.</Warning>

```typescript theme={null}
const BASE_URL = "https://www.crossmint.com/api/unstable/agent-checkouts";

const headers = {
    "Content-Type": "application/json",
    "X-API-KEY": process.env.CROSSMINT_API_KEY!,
    "x-crossmint-user-id": "YOUR_USER_ID",
};
```

## Sign In Once and Reuse the Session

<Steps>
  <Step title="Create a browser profile for the user">
    Create one profile and store its `id` with the user in your system. The optional `label` is only for your own bookkeeping.

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
          --url https://www.crossmint.com/api/unstable/agent-checkouts/browser-profiles \
          --header 'Content-Type: application/json' \
          --header 'X-API-KEY: YOUR_SERVER_API_KEY' \
          --header 'x-crossmint-user-id: YOUR_USER_ID' \
          --data '{ "label": "Primary shopping profile" }'
      ```

      ```typescript Node.js theme={null}
      // Uses BASE_URL and headers from the prerequisites.
      const res = await fetch(`${BASE_URL}/browser-profiles`, {
          method: "POST",
          headers,
          body: JSON.stringify({ label: "Primary shopping profile" }),
      });

      if (!res.ok) {
          throw new Error(`Could not create browser profile: ${res.status}`);
      }
      const { id: profileId } = await res.json();
      ```
    </CodeGroup>

    The response contains metadata only. It does not return cookies, tokens, or other browser state.

    ```json theme={null}
    {
        "id": "7c3b1f2a-9d54-4e80-b1a6-2f0c8e5d4a31",
        "label": "Primary shopping profile",
        "createdAt": "2026-08-10T11:00:00.000Z",
        "updatedAt": "2026-08-10T11:00:00.000Z"
    }
    ```

    A user can have one browser profile. Reuse that profile rather than creating one for each merchant.
  </Step>

  <Step title="Attach the profile to a checkout run">
    Pass the profile as `browser.profileId` when you start a run. The first run starts without a saved merchant session, so tell the agent to sign in before it buys.

    ```typescript theme={null}
    // Uses BASE_URL, headers, and profileId from the earlier steps.
    const res = await fetch(BASE_URL, {
        method: "POST",
        headers,
        body: JSON.stringify({
            request: {
                startUrl: "https://shop.example.com/products/classic-tee",
                task: "Sign in, then buy the medium in black.",
            },
            browser: { profileId },
            constraints: {
                maxCost: { amount: "100.00", currency: "USD" },
            },
        }),
    });

    if (!res.ok) {
        throw new Error(`Could not start checkout: ${res.status}`);
    }
    const { runId } = await res.json();
    ```

    You can also include `browser.location` in the same object when the checkout should run from a particular country. See [Configure a Checkout](/agents/checkouts/configure#configure-the-run).

    [Follow the run](/agents/checkouts/follow-run) and answer sign-in requests through [Handle Buyer Inputs](/agents/checkouts/buyer-inputs). An accepted form response does not itself confirm successful sign-in.
  </Step>

  <Step title="Complete the first signed-in checkout">
    Answer each request by its `requestId`, then continue observing the run. After the checkout finishes cleanly, the merchant's session is stored in the browser profile.

    The profile itself does not report what happened during a checkout. Read the run and its messages to confirm whether the agent signed in, encountered another verification step, or completed the purchase.
  </Step>

  <Step title="Reuse the profile on later runs">
    Pass the same `browser.profileId` on the next checkout for this user.

    ```typescript theme={null}
    // Uses BASE_URL, headers, and profileId from the earlier steps.
    const nextResponse = await fetch(BASE_URL, {
        method: "POST",
        headers,
        body: JSON.stringify({
            request: {
                startUrl: "https://shop.example.com/products/wool-scarf",
                task: "Buy one in grey using my saved account.",
            },
            browser: { profileId },
            constraints: {
                maxCost: { amount: "100.00", currency: "USD" },
            },
        }),
    });
    if (!nextResponse.ok) {
        throw new Error(`Could not start checkout: ${nextResponse.status}`);
    }
    ```

    The run loads the saved session before it navigates. If the merchant has expired the session or requires the user to sign in again, the agent sends new input requests. Handle them the same way as the first run.
  </Step>
</Steps>

## How Browser Profiles Stay Protected

* **The profile API returns metadata only.** Cookies, tokens, and other saved browser state are not returned.
* **Saved browser state is not added to a model prompt.** It is loaded only into the browser for that user's checkout.
* **Each run has its own browser.** The browser is released when the run finishes.
* **Payment authorization is separate.** Follow [Authorize Payments](/agents/checkouts/payments) when the run asks for a card credential.

## Manage a Browser Profile

Browser profile routes live under `https://www.crossmint.com/api/unstable/agent-checkouts/browser-profiles` and are documented with the Agent Checkouts API reference.

* A user can have one profile. Creating another returns `409`.
* Requesting a profile owned by another user returns `404`, so its existence cannot be probed.
* `label` is the only editable field.
* Deleting a profile irreversibly erases its saved browser state. Runs already using it continue, and erasure finishes after they end.

## Next Steps

<CardGroup cols={2}>
  <Card title="Authorize Payments" icon="credit-card" href="/agents/checkouts/payments">
    Pay with an Agent Card, a merchant-saved card, or a local method
  </Card>

  <Card title="Configure a Checkout" icon="browser" href="/agents/checkouts/configure">
    Set purchase context before starting the run
  </Card>
</CardGroup>
