---
title: "Verify a user"
description: "Verify a user's email with two API calls, then store the access token Flume returns. You send the token on every later call made for that user."
url: https://docs.tryflume.ai/guides/verify-a-user
updated: 2026-11-11
---

# Verify a user

> Verify a user's email with two API calls, then store the access token Flume returns. You send the token on every later call made for that user.

Verify a user in two calls: send a code to their email, then confirm the code. The second call returns the **user access token**.

**Diagram: Steps 1 and 2: verify a user through the API.** You build the code screen. Two calls, with the partner key only. The second returns the user access token.

```mermaid
sequenceDiagram
  participant user as User
  participant page as Your page
  participant backend as Your backend
  participant flume as Flume servers
  page->>backend: email
  backend->>flume: POST /users/trigger-verification
  flume->>user: email, 6 digits
  user->>page: types the code
  page->>backend: code
  backend->>flume: POST /users/confirm-verification
  flume->>backend: access_token
  Note over backend: store the token
```

1. Your screen sends the user's email to your backend.
2. Your backend calls POST /users/trigger-verification with external_ref and email.
3. Flume emails the user a 6-digit code, valid for 10 minutes.
4. The user types the code on your screen.
5. Your screen sends the code to your backend.
6. Your backend calls POST /users/confirm-verification with the email and the code.
7. Flume returns the user access token, once.
8. Your backend stores the token against that user and sends it on every call made for them.

## Before you begin

- A partner key. See [Authentication and keys](https://docs.tryflume.ai/concepts/authentication.md).
- A stable id for the user in your system. You send it as `external_ref`.

## 1. Send the code

**curl**

```sh
curl -X POST "https://sandbox-api.tryflume.ai/partner/v1/users/trigger-verification" \
  -H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "external_ref": "u_8812",
  "email": "sarah.chen@example.com",
  "first_name": "Sarah",
  "last_name": "Chen",
  "org": {
    "name": "NY Design Co"
  }
}'
```

**Python**

```python
import os

import requests

headers = {
    "X-Flume-Partner-Key": os.environ["FLUME_PARTNER_KEY"],
}

response = requests.post(
    "https://sandbox-api.tryflume.ai/partner/v1/users/trigger-verification",
    headers=headers,
    json={
        "external_ref": "u_8812",
        "email": "sarah.chen@example.com",
        "first_name": "Sarah",
        "last_name": "Chen",
        "org": {
            "name": "NY Design Co",
        },
    },
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

**Node.js**

```js
const response = await fetch("https://sandbox-api.tryflume.ai/partner/v1/users/trigger-verification", {
  method: "POST",
  headers: {
    "X-Flume-Partner-Key": process.env.FLUME_PARTNER_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "external_ref": "u_8812",
    "email": "sarah.chen@example.com",
    "first_name": "Sarah",
    "last_name": "Chen",
    "org": {
      "name": "NY Design Co"
    }
  }),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
console.log(await response.json());
```

**Response (202)**

```json
{
  "external_ref": "u_8812",
  "masked_email": "s···@example.com",
  "expires_in_s": 600,
  "resend_after_s": 30
}
```

- Only `external_ref` and `email` are required.
- Send the name, phone and firm if you have them. Flume puts them on the user's new Flume account.
- `phone` must be a US number.
- `X-Flume-Client-IP` is optional. Send the user's IP address in it and Flume also limits codes per IP address. See [Users and verification](https://docs.tryflume.ai/concepts/users.md#what-are-the-rules-for-a-code).

Show the user a code box. Use `masked_email` to say where the code went.

## 2. Confirm the code

**curl**

```sh
curl -X POST "https://sandbox-api.tryflume.ai/partner/v1/users/confirm-verification" \
  -H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "sarah.chen@example.com",
  "code": "482913"
}'
```

**Python**

```python
import os

import requests

headers = {
    "X-Flume-Partner-Key": os.environ["FLUME_PARTNER_KEY"],
}

response = requests.post(
    "https://sandbox-api.tryflume.ai/partner/v1/users/confirm-verification",
    headers=headers,
    json={
        "email": "sarah.chen@example.com",
        "code": "482913",
    },
    timeout=30,
)
response.raise_for_status()
print(response.json())
```

**Node.js**

```js
const response = await fetch("https://sandbox-api.tryflume.ai/partner/v1/users/confirm-verification", {
  method: "POST",
  headers: {
    "X-Flume-Partner-Key": process.env.FLUME_PARTNER_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "email": "sarah.chen@example.com",
    "code": "482913"
  }),
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
console.log(await response.json());
```

**Response (200)**

```json
{
  "external_ref": "u_8812",
  "email": "sarah.chen@example.com",
  "access_token": "ut_dev_acme_example_token",
  "token_type": "user_token",
  "verified_at": "2026-10-16T09:02:11Z",
  "account": "CREATED"
}
```

- `access_token` is returned once. Store it encrypted, against the user.
- A user has one working token at a time. This one replaces any earlier token.
- `account` is `CREATED` for a new Flume account, or `LINKED` for an existing one.
- Send `external_ref` in the body only if two of your users share one email.

## 3. Use the token

Send it as `X-Flume-User-Token`, with your partner key, on every call made for the user.

```sh
curl "https://sandbox-api.tryflume.ai/partner/v1/products/7f3c2a91-4b0e-4c8d-9a21-1f6b2d5e7c30" \
  -H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY" \
  -H "X-Flume-User-Token: $FLUME_USER_TOKEN"
```

## Handle the common errors

| You get | Do this |
|---|---|
| `422` `VERIFICATION_CODE_INVALID` | Let the user try again. The message says how many tries are left |
| `422` `VERIFICATION_TRIES_EXHAUSTED` | Send a new code |
| `422` `VERIFICATION_CODE_EXPIRED` | Send a new code |
| `429` `VERIFICATION_RATE_LIMITED` | Wait `Retry-After` seconds, then resend |
| `502` `VERIFICATION_EMAIL_FAILED` | Retry the send |
| `404` `NOT_FOUND` on confirm | Call trigger-verification first |

## Resend a code

Call `POST /users/trigger-verification` again after `resend_after_s` seconds. The new code replaces the old one.

## Revoke a token

Revoke when a user leaves or a token may have leaked.

**curl**

```sh
curl -X DELETE "https://sandbox-api.tryflume.ai/partner/v1/users/u_8812/token" \
  -H "X-Flume-Partner-Key: $FLUME_PARTNER_KEY"
```

**Python**

```python
import os

import requests

headers = {
    "X-Flume-Partner-Key": os.environ["FLUME_PARTNER_KEY"],
}

response = requests.delete(
    "https://sandbox-api.tryflume.ai/partner/v1/users/u_8812/token",
    headers=headers,
    timeout=30,
)
response.raise_for_status()
print(response.status_code)
```

**Node.js**

```js
const response = await fetch("https://sandbox-api.tryflume.ai/partner/v1/users/u_8812/token", {
  method: "DELETE",
  headers: {
    "X-Flume-Partner-Key": process.env.FLUME_PARTNER_KEY,
  },
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
console.log(response.status);
```

**Response (204)**: no body.

To give the user a new token, verify them again.

## Test it

The sandbox has no test code. Use an inbox you can read, and confirm with the code from the real email.

## See also

- [Users and verification](https://docs.tryflume.ai/concepts/users.md)
- [What happens when a user verifies in the iframe?](https://docs.tryflume.ai/concepts/users.md#what-happens-when-a-user-verifies-in-the-iframe)
- [Send a verification code](https://docs.tryflume.ai/api/trigger-verification.md)
- [Confirm a verification code](https://docs.tryflume.ai/api/confirm-verification.md)
