> ## 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.

# Buy from Authenticated Stores

> Check out inside the user's account at a merchant, and reuse that login on later purchases instead of signing in on every run.

Some purchases only work when the user is signed in at the merchant: saved addresses, saved payment methods, member pricing, a cart the user already filled.

Every checkout starts in a fresh browser, signed out. So the agent logs in on every run, and each login pulls the user back to read a one-time code.

A **browser profile** ends that. The user signs in once, inside a checkout, and Crossmint keeps the browser state that login produced. Later checkouts load it and start signed in. One profile covers every merchant the user signs in to.

## Why this is safe

* **A saved login is real account access, so Crossmint treats it as sensitive.**
* **Crossmint stores metadata only.** A profile record is an id, your label, and timestamps. The saved browser state itself is held by our browser infrastructure as an opaque blob that Crossmint never reads.
* **Its contents never reach a model.** The state is loaded into the browser for that user's checkout and nowhere else; it is never included in a prompt.
* **The API returns metadata only.** No cookies or tokens ever come back.
* **Each run gets its own browser,** released when the run finishes.
* **Deleting erases the stored browser state,** not just our record of it.

Card details never enter a profile.

## Quickstart

This walks the full path: create a profile, run a checkout where the user logs in, then run a second checkout that reuses that login. It reuses the `BASE_URL` and `headers` from the [Agent Checkouts quickstart](/agents/agent-checkouts-quickstart#setup).

Browser profiles are scoped to one user. Identify that user with `x-crossmint-user-id` on a server-side key, or with the user's JWT on a client-side key.

<Warning>**Always send `x-crossmint-user-id` with a server-side key.** Omit it and the request runs under your project's shared service subject, so every end user's saved logins pile into one profile that any of your checkouts can load.</Warning>

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

const headers = {
    "Content-Type": "application/json",
    "X-API-KEY": CROSSMINT_SERVER_API_KEY, // sk_production_... server-side key
    "x-crossmint-user-id": "YOUR_USER_ID", // required — the user this profile belongs to
};
```

<Steps>
  <Step title="Create the profile">
    Create the profile once for the user. The optional `label` is for your own bookkeeping.

    <CodeGroup>
      ```typescript Node.js theme={null}
      const res = await fetch(`${BASE_URL}/browser-profiles`, {
          method: "POST",
          headers,
          body: JSON.stringify({ label: "Shopify Stores" }),
      });

      const { id: browserProfileId } = await res.json(); // 201 — store this
      ```

      ```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": "Shopify Stores" }'
      ```
    </CodeGroup>

    The profile comes back as metadata only. Crossmint stores the browser state itself, so no cookie or token is ever returned.

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

    Store `id` against the user in your own system. A user has one profile, so you create it once and reuse the id from then on.
  </Step>

  <Step title="Run a checkout where the user logs in">
    Pass `browserProfileId` when you create the checkout. Everything else works as it does in the [Agent Checkouts quickstart](/agents/agent-checkouts-quickstart).

    ```typescript theme={null}
    const res = await fetch(BASE_URL, {
        method: "POST",
        headers,
        body: JSON.stringify({
            target: {
                kind: "direct_url",
                url: "https://shop.example.com/products/classic-tee",
                request: "buy the medium in black",
            },
            browserProfileId,
            constraints: {
                maxCost: { amount: "100.00", currency: "USD" },
            },
        }),
    });

    const { id } = await res.json();
    ```

    The profile is empty on this first run, so the merchant asks for a login and the user signs in inside the run.

    When the checkout finishes cleanly, the login is saved into the profile.
  </Step>

  <Step title="Run a second checkout that reuses the login">
    Create the next checkout the same way, passing the same `browserProfileId`.

    ```typescript theme={null}
    await fetch(BASE_URL, {
        method: "POST",
        headers,
        body: JSON.stringify({
            target: {
                kind: "direct_url",
                url: "https://shop.example.com/products/wool-scarf",
                request: "buy one in grey",
            },
            browserProfileId,
            constraints: {
                maxCost: { amount: "100.00", currency: "USD" },
            },
        }),
    });
    ```

    This run loads the saved state, so it starts signed in and goes straight to the purchase with no login prompt.

    The profile itself reports no login activity: what a checkout did with it is reported on that checkout, so read the checkout to see whether the run still needed a login.
  </Step>
</Steps>

## Manage a profile

Every browser profile route lives under `https://www.crossmint.com/api/unstable/agent-checkouts/browser-profiles` and is documented in the Agent Checkouts API reference, alongside the checkout routes. A few behaviors are worth knowing before you call them:

* **A user holds at most one profile**, so creating a second one returns `409` and listing needs no pagination.
* **A profile owned by another user returns `404`**, not `403`, so an id cannot be probed for existence.
* **The label is the only editable field.** Everything else about a profile is set by Crossmint.
* **Deleting is irreversible** and erases the stored browser state, not just the record. It does not cancel checkouts already running with the profile: those runs continue to completion, and erasure finishes once they end.
