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

# Authenticating a user

> Send the user a one-time code, verify it, and get the connection token that acts as them.

Connecting a user is the first step of Issuing. You send them a one-time code, they read it back, and you receive a **connection token** that acts as them. That token is what your agent creates and uses cards with.

Every call on this page is made by your server with your org token as the bearer (see [step 1 of the Quickstart](/issuing/quickstart#1-get-your-credentials)).

## Send the user a code

By email or phone. `external_user_id` is optional: your own id for the user, returned on webhooks so you can match them up.

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/connect/start \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "external_user_id": "your-internal-id"}'
```

```json theme={null}
{ "object": "connect_attempt", "connect_id": "ca_9k1m4x7d", "channel": "email" }
```

## Verify the code

In sandbox the code is always `111111`.

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/connect/verify \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"connect_id": "ca_9k1m4x7d", "code": "111111"}'
```

```json theme={null}
{
  "object": "connection",
  "access_token": "act_1a2b3c…",
  "refresh_token": "rct_4d5e6f…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "user": { "id": "user_7g8h9i", "email": "user@example.com", "phone": null }
}
```

Store all three. `user.id` is how your server names the user on `/api/v2` calls. `access_token` is the connection token your agent uses. The `connection.created` webhook fires here.

## Record consent

Once per user, before any card or balance action:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/connect/consent \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "user_7g8h9i"}'
```

A `user_info_required` error later means this step was skipped.

## Refresh the connection

Connection tokens expire after one hour. Rotate them with the refresh token, using your org token as the bearer:

```bash theme={null}
curl -X POST https://api.agentcard.sh/api/v2/connect/refresh \
  -H "Authorization: Bearer $ORG_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "rct_4d5e6f…"}'
```

Each refresh returns a new pair and invalidates the old one. Store what comes back. An `invalid_refresh_token` means it was already used or expired: reconnect the user.

## Users you know by phone only

If your agent lives in iMessage or WhatsApp, you may not want to relay a code. Create an **onboarding attempt** instead: it returns a wallet link to text, the code fires inside our page at the user's first money action, and you exchange the attempt for the connection afterwards. See [Connections](/api-reference/connections/overview) in the API reference.

## Sandbox

Sandbox sends email but not SMS, and the code is always `111111`. Sandbox users are isolated: connecting `anyone@example.com` in sandbox can never touch a real account.
