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

# Authentication and bearer tokens in Laso Finance

> Learn how the Laso Finance API authenticates requests using SIGN-IN-WITH-X wallet signatures, x402 payment headers, and OAuth2 bearer tokens.

## Overview

The Laso Finance API uses three authentication mechanisms:

1. **`SIGN-IN-WITH-X` header** for `GET /auth`. A base64-encoded CAIP-122 signed message that proves wallet ownership. Free.
2. **`x-payment` header** on the paywalled endpoints (`/get-card`, `/order-gift-card`, `/get-push-to-card`, `/order-intl-card`, `/send-payment`, `/send-bank-payment`). The signed x402 payment payload. The server verifies it, settles the payment, and extracts the payer's wallet address. An MPP client sends its credential as `Authorization: Payment ...` instead; the payer's wallet is extracted the same way.
3. **Bearer tokens**. For authenticated endpoints like `/get-card-data`, `/get-account-balance`, etc. Pass an `id_token` as a `Bearer` token in the `Authorization` header.

## Getting tokens

Call `GET /auth` (free) or `GET /get-card` (which costs the card amount in USDC) to receive auth credentials:

```json theme={null}
{
  "auth": {
    "id_token": "eyJhbGciOiJSUzI1NiIs...",
    "refresh_token": "AMf-vBx4N2...",
    "expires_in": "3600"
  },
  "user_id": "0xabc123..."
}
```

| Field           | Description                                                                                   |
| --------------- | --------------------------------------------------------------------------------------------- |
| `id_token`      | Firebase ID token. Use as `Bearer` token for authenticated endpoints. Expires after \~1 hour. |
| `refresh_token` | Long-lived token for getting new `id_token`s.                                                 |
| `expires_in`    | Token lifetime in seconds (typically 3600 = 1 hour).                                          |
| `user_id`       | Your user ID, derived from your wallet address (lowercased).                                  |

### Signing in with SIGN-IN-WITH-X

Build a CAIP-122 message and sign it with your wallet, then send it base64-encoded in the `SIGN-IN-WITH-X` header. The `@x402/extensions/sign-in-with-x` package handles the construction and the `wrapFetchWithSIWx` helper handles the full request flow automatically:

```typescript theme={null}
import { wrapFetchWithSIWx } from "@x402/extensions/sign-in-with-x";
import { privateKeyToAccount } from "viem/accounts";

const signer = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY);
const fetchWithSiwx = wrapFetchWithSIWx(fetch, signer);

const res = await fetchWithSiwx("https://laso.finance/auth");
const { auth, user_id } = await res.json();
```

The server-side validation enforces:

* Domain binding: the signed message's `domain` must match `laso.finance`.
* Nonce uniqueness: each signature can only be used once.
* 5-minute expiry: signatures older than 5 minutes are rejected.

A rejected signature returns **402 Payment Required** (not 401), the same response as a request with no credentials at all. This follows the x402 protocol: the 402 carries a fresh challenge (new nonce, payment options, SIWX info) base64-encoded in the `PAYMENT-REQUIRED` response header, so the correct recovery is to sign the new challenge and retry. If you receive a 402 after sending a `SIGN-IN-WITH-X` header, treat it as a verification failure. Do not resend the same payload.

Supported chains:

| `chainId`                                           | Type           |
| --------------------------------------------------- | -------------- |
| `eip155:8453` (Base)                                | `eip191` (EOA) |
| `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` (mainnet) | `ed25519`      |

## Using tokens

Pass the `id_token` as a Bearer token in the `Authorization` header (the examples write it as `$LASO_ID_TOKEN`, exported from your saved credentials):

```bash theme={null}
curl https://laso.finance/get-card-data?card_id=O-01ABC123 \
  -H "Authorization: Bearer $LASO_ID_TOKEN"
```

## Refreshing tokens

### Why tokens expire

The `id_token` returned by `/auth` and `/get-card` expires after approximately **1 hour** (3600 seconds). After expiry, authenticated endpoints like `/get-card-data` return `401 Unauthorized`.

The `refresh_token` is long-lived and can be used to get a new `id_token` without paying again.

### Refreshing a token

Call `POST /auth` with `grant_type: "refresh_token"` and your `refresh_token`. This follows the standard OAuth2 pattern and is free: no x402 payment required.

```bash theme={null}
curl -X POST https://laso.finance/auth \
  -H "Content-Type: application/json" \
  -d '{"grant_type": "refresh_token", "refresh_token": "AMf-vBx4N2..."}'
```

Response:

```json theme={null}
{
  "id_token": "eyJhbGciOiJSUzI1NiIs...",
  "refresh_token": "AMf-vBx4N2...",
  "expires_in": "3600",
  "user_id": "0xabc..."
}
```

<Note>
  The response returns a new `refresh_token` as well. Always store the latest
  `refresh_token` from each response.
</Note>

### Proactive refresh

Don't wait for a `401` error. Refresh the token before it expires:

```typescript theme={null}
const TOKEN_LIFETIME_MS = 3600 * 1000; // 1 hour
const REFRESH_BUFFER_MS = 5 * 60 * 1000; // 5 minutes before expiry

let tokenIssuedAt = Date.now();

function shouldRefresh() {
  return Date.now() - tokenIssuedAt > TOKEN_LIFETIME_MS - REFRESH_BUFFER_MS;
}

async function getValidToken() {
  if (shouldRefresh()) {
    const { data } = await axios.post("https://laso.finance/auth", {
      grant_type: "refresh_token",
      refresh_token: currentRefreshToken,
    });
    currentIdToken = data.id_token;
    currentRefreshToken = data.refresh_token;
    tokenIssuedAt = Date.now();
  }
  return currentIdToken;
}
```

### Reactive refresh

Alternatively, catch `401` errors and refresh on demand:

```typescript theme={null}
async function callWithRefresh(requestFn) {
  try {
    return await requestFn(currentIdToken);
  } catch (error) {
    if (error.response?.status === 401) {
      const { data } = await axios.post("https://laso.finance/auth", {
        grant_type: "refresh_token",
        refresh_token: currentRefreshToken,
      });
      currentIdToken = data.id_token;
      currentRefreshToken = data.refresh_token;
      return await requestFn(currentIdToken);
    }
    throw error;
  }
}
```

### Store both tokens

Always persist both the `id_token` and `refresh_token`. If you lose the `refresh_token`, you'll need to call `GET /auth` again, which is free but requires signing a fresh `SIGN-IN-WITH-X` message.

<Tip>
  You only need to hit `GET /auth` once per session. Use the `id_token` for requests, and when it expires, call `POST /auth` with the `refresh_token` to get a new pair.
</Tip>

### Error responses

| Status | Meaning                                                              |
| ------ | -------------------------------------------------------------------- |
| `200`  | Success, new tokens returned                                         |
| `400`  | Invalid or missing `grant_type` or `refresh_token` in request body   |
| `401`  | Token is invalid or revoked, so you must re-authenticate via `/auth` |

## Token lifecycle

```
┌─────────────────────────────────────────────────┐
│  GET /auth  (free, SIGN-IN-WITH-X header)       │
│  or GET /get-card (\$5-\$1000)                  │
│         │                                       │
│         ▼                                       │
│  ┌─────────────┐                                │
│  │  id_token   │──── expires after ~1 hour ───┐ │
│  │refresh_token│                              │ │
│  └─────────────┘                              │ │
│         │                                     │ │
│         ▼                                     ▼ │
│  Use id_token as                     POST /auth │
│  Bearer token for         (free, returns new pair)│
│  authenticated endpoints                        │
└─────────────────────────────────────────────────┘
```

## Getting a login link for humans

If a human wants to see what their agent has been doing (cards, transactions, balances), use `GET /get-auth-link` to generate a one-time login URL:

```bash theme={null}
curl https://laso.finance/get-auth-link \
  -H "Authorization: Bearer $LASO_ID_TOKEN"
```

Response:

```json theme={null}
{
  "auth_url": "https://laso.finance/agent/dashboard/auth?code=K7MPQ-W3XZ9",
  "user_id": "0xabc...",
  "expires_in": 900
}
```

Give `auth_url` to the human exactly as received; opening it in a browser logs them in to the Laso Finance dashboard as that user. The URL carries a short single-use login code rather than a long token, so it passes through tool-output credential filters intact. Agents should not fetch or open it themselves, since redeeming the code consumes the human's login. The code expires after 15 minutes, so generate a new link if needed.

## How wallet identity works

Your identity in the Laso Finance system is your wallet address:

1. You call `GET /auth` with a `SIGN-IN-WITH-X` header.
2. The server cryptographically verifies the signature and extracts your wallet address.
3. If it's your first time, an account is created automatically.
4. Your `user_id` is your wallet address (lowercased).
5. All tokens and cards are tied to this wallet address.

This means there's no separate signup or account creation. Your wallet *is* your account.
