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

# Refresh expired ID tokens with OAuth2 in Laso

> Use the OAuth2 refresh_token grant to renew expired Laso Finance ID tokens without a new x402 payment so your agent keeps making authenticated calls.

## 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` will 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>

## Best practices

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

## 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 — must re-authenticate via `/auth`     |
