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

# Onboarding customers

> Create a user, verify their identity, and read their capabilities. Or connect an existing Spritz user.

Every Spritz user belongs to your integration. Onboarding has three beats: create the
user, verify their identity, and read their capabilities to see what they can do.

If the person already has a Spritz account, with you or with another integrator, you
don't create a new user. You [connect to them](#connect-an-existing-spritz-user)
instead, with their consent.

## 1. Create a user

Create a user with their email address. Spritz returns a user API key (prefixed `ak_`)
that you use to authenticate requests on that user's behalf.

```bash theme={null}
curl -X POST https://platform.spritz.finance/v1/integrator/users \
  -H "Content-Type: application/json" \
  # plus your integrator signing headers (see Authentication)
  -d '{ "email": "user@example.com" }'
```

```json theme={null}
{
  "userId": "63d12d3b577fab6c6382136e",
  "email": "user@example.com",
  "apiKey": "ak_..."
}
```

Store the `apiKey` securely. You send it as the `Authorization` bearer for every request
you make on this user's behalf (see [Authentication](/guides/authentication)). You can
also pass an optional `timezone`.

## 2. Verify identity

Before a user can move money, they complete identity verification (KYC). Start a
verification session for the user:

```bash theme={null}
curl -X POST https://platform.spritz.finance/v1/users/me/verification-sessions/ \
  -H "Content-Type: application/json" \
  # plus your integrator signing headers and the user's Authorization (see Authentication)
  -d '{}'
```

```json theme={null}
{
  "sessionId": "inq_2Q3x7k9m1n",
  "provider": "persona",
  "sessionToken": "...",
  "verificationUrl": "https://verify.spritz.finance/...",
  "verificationUrlExpiresAt": "2026-06-30T12:34:56.000Z"
}
```

Spritz uses [Persona](https://withpersona.com) for identity verification. The session
response gives you what Persona's SDKs need: `sessionId` is the Persona inquiry ID and
`sessionToken` is the session token.

**Recommended: embed Persona natively.** For the best experience, run verification
inside your own app with the Persona SDK that matches your platform, passing the inquiry
ID and session token. Persona provides:

* **Web**: the [embedded flow](https://docs.withpersona.com/quickstart-embedded-flow)
* **React Native**: Persona's React Native SDK
* **iOS and Android**: Persona's native mobile SDKs

See [Persona's docs](https://docs.withpersona.com) for each SDK. Passing the session
token resumes the same inquiry, so the result stays in sync with the user's
`verification.status`.

**Fallback: hosted flow.** If you'd rather not embed, open `verificationUrl` in a
browser tab, iframe, or mobile web view. It's single-use and short-lived; if it expires
before the user finishes, create another session.

The user's verification state is reported on their profile as `verification.status`:
`not_started`, `verified`, `failed`, `retry`, or `disabled`. A `retry` status means
they can try again; create a new session to do so.

## 3. Read capabilities

`GET /v1/users/me` returns the user's verification status and their **capabilities**.
Capabilities are the source of truth for what a user can do right now, and what's
blocking anything they can't.

```json theme={null}
{
  "id": "63d12d3b577fab6c6382136e",
  "email": "user@example.com",
  "verification": { "status": "verified", "country": "US" },
  "capabilities": [
    {
      "product": "crypto_to_fiat",
      "method": "ach_credit",
      "name": "ACH Bank Transfer",
      "description": "Pay out to a US bank account via ACH",
      "status": "active",
      "requirements": []
    },
    {
      "product": "fiat_to_crypto",
      "method": "ach_debit",
      "name": "ACH Debit",
      "status": "requirements_needed",
      "nextRequirement": "terms_acceptance",
      "requirements": [
        {
          "type": "terms_acceptance",
          "description": "Accept the deposit terms to enable ACH debit",
          "actionUrl": "https://...",
          "retryable": false,
          "status": "not_started"
        }
      ]
    }
  ]
}
```

Each capability is a **product**, and for ramps a **method**:

* **Products**: `fiat_to_crypto`, `crypto_to_fiat`, `bill_pay`, `crypto_card`
* **Methods**: `ach_credit`, `ach_debit`, `wire`, `sepa_credit_transfer`, `rtp`,
  `push_to_card`, `canadian_eft`

Its `status` tells you where it stands:

| Status                | Meaning                                               |
| --------------------- | ----------------------------------------------------- |
| `active`              | Ready to use now                                      |
| `requirements_needed` | The user has something to complete first              |
| `pending`             | In progress, for example verification under review    |
| `not_available`       | Not offered for this user (for example, their region) |

When a capability is `requirements_needed`, its `requirements[]` lists what to do, and
`nextRequirement` points to the one to tackle first. Each requirement has a `type`, a
human-readable `description`, an `actionUrl` where the user completes it, a `retryable`
flag, and its own `status` (`not_started`, `pending`, `completed`, `failed`).

| Requirement type          | What it means                                                                      |
| ------------------------- | ---------------------------------------------------------------------------------- |
| `identity_verification`   | Complete KYC (see step 2)                                                          |
| `terms_acceptance`        | Accept the relevant terms                                                          |
| `additional_verification` | An extra verification step is needed                                               |
| `document_submission`     | Submit supporting documents                                                        |
| `region_restriction`      | Not available in the user's region. This one is terminal; no user action clears it |

### Drive your UI from capabilities

Render your onboarding and product UI off the capabilities list rather than tracking
state yourself:

* Show a capability as available when its `status` is `active`.
* When it's `requirements_needed`, surface the requirement `description` and send the
  user to its `actionUrl`.
* After the user completes a requirement, re-fetch `GET /v1/users/me`, or react to the
  `capabilities.updated` [webhook](/guides/webhooks) so you update without polling.

## Connect an existing Spritz user

If the person already has a Spritz account, with you or with another integrator, use
**Integrator Connect** to gain access with their consent instead of creating a new
user. It's an OAuth-style authorization flow.

<Steps>
  <Step title="Create a connect session">
    From your backend, create a session with a pre-registered HTTPS redirect URI and an
    optional `state` value you can use to tie the result back to a request.

    ```bash theme={null}
    curl -X POST https://platform.spritz.finance/v1/integrator/connect/sessions \
      -H "Content-Type: application/json" \
      # plus your integrator signing headers (see Authentication)
      -d '{
        "redirectUri": "https://app.example.com/spritz/callback",
        "state": "opaque-value"
      }'
    ```

    ```json theme={null}
    {
      "sessionId": "...",
      "authorizationUrl": "https://app.spritz.finance/connect?session_id=...",
      "expiresAt": "2026-06-30T12:40:00.000Z"
    }
    ```
  </Step>

  <Step title="Send the user to approve">
    Redirect the user to `authorizationUrl`. They sign in to Spritz, review your
    integration, and approve. The session is valid for about 10 minutes.
  </Step>

  <Step title="Receive the authorization code">
    On approval, Spritz redirects the user back to your `redirectUri` with a `code` and
    the `state` you sent.
  </Step>

  <Step title="Exchange the code for a user key">
    From your backend, exchange the code for that user's API key. The code is
    single-use and expires after a few minutes.

    ```bash theme={null}
    curl -X POST https://platform.spritz.finance/v1/integrator/connect/token \
      -H "Content-Type: application/json" \
      # plus your integrator signing headers
      -d '{ "code": "..." }'
    ```

    ```json theme={null}
    {
      "apiKey": "ak_...",
      "userId": "63d12d3b577fab6c6382136e",
      "email": "user@example.com",
      "grantId": "..."
    }
    ```
  </Step>
</Steps>

You now hold a user API key for that user and can act on their behalf, exactly like a
user you created.

## Next

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/guides/authentication">
    Use the user API key to sign requests.
  </Card>

  <Card title="What we support" icon="globe" href="/guides/supported">
    Rails, tokens, and regions behind each capability.
  </Card>

  <Card title="Off-ramp" icon="arrow-right-from-bracket" href="/guides/use-cases/off-ramp">
    Put an active capability to work.
  </Card>

  <Card title="Webhooks" icon="bell" href="/guides/webhooks">
    React to capabilities.updated and verification changes.
  </Card>
</CardGroup>
