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

# Overview

> Create a Fluz account for one of your users over the API, handle the already-exists case correctly, and hand off to verification.

`registerUser` provisions a Fluz account for someone using profile data you already hold — no redirect, no hosted form, no asking the user to re-type their name and date of birth.

<Warning>
  **Restricted access.** This mutation requires explicit permission from Fluz. Contact your account manager to enable user registration for your application. Calls from an application without it fail with `AUTH-0022`.
</Warning>

***

## Where this fits

Registration is one step of an onboarding arc, and it's optional — you can hand the whole thing to a widget instead.

| Approach     | Registration   | KYC                     | Consent            | Build cost                    |
| :----------- | :------------- | :---------------------- | :----------------- | :---------------------------- |
| **Widget**   | Hosted by Fluz | Hosted by Fluz          | Hosted by Fluz     | Hours                         |
| **Hybrid**   | `registerUser` | `verifyUserInformation` | Widget or redirect | Days                          |
| **API only** | `registerUser` | `verifyUserInformation` | OAuth redirect     | Days, plus you handle the PII |

Register users yourself when you **already hold clean profile data** and don't want the user typing it twice. If you'd be collecting name, date of birth, and contact details purely to pass them to Fluz, use an [embedded widget](/developers/widgets) instead — it keeps that collection inside Fluz's compliance scope.

The full sequence for the API path:

<Steps>
  <Step title="Register">
    `registerUser` with name, phone, email, and date of birth.
  </Step>

  <Step title="Verify">
    Run KYC with `verifyUserInformation`, or hand the user a document verification link. → [User KYC Verification](/user-kyc-verification)
  </Step>

  <Step title="Get authorized">
    The user grants your application scopes. → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow)
  </Step>

  <Step title="Operate">
    Mint a user access token and use the API on their behalf. → [Authentication](/concepts/authentication)
  </Step>
</Steps>

***

## The mutation

```graphql theme={null}
mutation RegisterUser(
  $firstName: String!
  $lastName: String!
  $phoneNumber: String!
  $regionCode: String!
  $emailAddress: String!
  $dateOfBirth: String!
  $billingAddress: VirtualCardBillingAddressInput!
  $acceptCardholderAgreement: Boolean!
) {
  registerUser(
    firstName: $firstName
    lastName: $lastName
    phoneNumber: $phoneNumber
    regionCode: $regionCode
    emailAddress: $emailAddress
    dateOfBirth: $dateOfBirth
    billingAddress: $billingAddress
    acceptCardholderAgreement: $acceptCardholderAgreement
  ) {
    success
    userId
    accountId
    billingAddressId
    error {
      code
      message
    }
  }
}
```

<Note>
  `error` is an object (`RegisterUserError`), not a string — always select `code` and `message` as subfields. Requesting bare `error` won't compile.
</Note>

### Parameters

| Parameter                   | Type                           | Required | Description                                                                                                                                                                    |
| :-------------------------- | :----------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `firstName`                 | String                         | Yes      | The user's first name                                                                                                                                                          |
| `lastName`                  | String                         | Yes      | The user's last name                                                                                                                                                           |
| `phoneNumber`               | String                         | Yes      | Digits and `-` accepted — `"5551234567"` or `"555-123-4567"`                                                                                                                   |
| `regionCode`                | String                         | Yes      | ISO 3166-1 alpha-2 country code for the phone number, e.g. `"US"`                                                                                                              |
| `emailAddress`              | String                         | Yes      | Must not already exist on a Fluz account                                                                                                                                       |
| `dateOfBirth`               | String                         | Yes      | `YYYY-MM-DD`                                                                                                                                                                   |
| `billingAddress`            | VirtualCardBillingAddressInput | Yes      | The user's billing address (`streetAddressLine1`, `city`, `state`, `postalCode`, `country`). Validated, saved for the user's virtual cards, and returned as `billingAddressId` |
| `acceptCardholderAgreement` | Boolean                        | Yes      | Must be `true` — registration is rejected otherwise                                                                                                                            |
| `deferSeatAssignment`       | Boolean                        | No       | When `true`, creates the user without a rewards-network seat; one is assigned later at card redemption                                                                         |

Names should match the identity documents the user will verify with — a mismatch surfaces later as a KYC failure that's much harder to diagnose than a registration error.

### What gets created

A complete Fluz account: the user record, their wallet and balances, and their rewards eligibility. There's nothing further to provision before the account can be verified and used.

***

## Handling the response

<Warning>
  **Failures come back in `data`, not in `errors`.** A failed registration is an HTTP 200 with `success: false`. Code that only checks the GraphQL `errors` array will read every failure as a success.
</Warning>

```json Success theme={null}
{ "data": { "registerUser": { "success": true, "error": null } } }
```

```json Failure theme={null}
{
  "data": {
    "registerUser": {
      "success": false,
      "error": {
        "code": "AUTH-0026",
        "message": "The phone number you chose is already in use."
      }
    }
  }
}
```

A handler that gets all three layers right:

```typescript theme={null}
const res = await fetch(FLUZ.graphqlUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${accessToken}`,
  },
  body: JSON.stringify({ query: REGISTER_USER, variables: profile }),
});

const body = await res.json();

// 1. Transport and GraphQL-level failures
if (body.errors?.length) {
  throw new Error(`GraphQL error: ${body.errors[0].message}`);
}

const result = body.data.registerUser;

// 2. Business-level failure — still an HTTP 200
if (!result.success) {
  switch (result.error.code) {
    case 'AUTH-0026':
    case 'AUTH-0027':
      // Not a failure. This person already has a Fluz account.
      return routeToAuthorization(profile);

    case 'AUTH-0004':
      return promptForValidPhoneNumber();

    case 'AUTH-0022':
    case 'AUTH-0030':
      // Configuration problem on your side — don't retry, alert.
      throw new ConfigurationError(result.error.code);

    default:
      throw new RetryableError(result.error.message);
  }
}

// 3. Success
return proceedToVerification(profile);
```

***

## "Already in use" is a routing signal, not an error

`AUTH-0026` (phone) and `AUTH-0027` (email) mean the person **already has a Fluz account**. That's a normal, expected outcome — Fluz accounts are not scoped to your application, so anyone who has used Fluz before, through any app or the consumer product, already exists.

Treating this as a failure is the most common integration mistake here. The right response is to stop trying to create an account and start asking for access to the existing one: send the user through the [OAuth grant flow](/client-facing-o-auth-grant-flow), or open a [widget](/developers/widgets). They log in to the account they already have and authorize you.

Design your onboarding so the register-then-fall-back path is the normal case rather than an exception branch, and it stays clean at scale.

***

## Retries and duplicates

<Warning>
  `registerUser` takes **no idempotency key**. A retry is a genuinely new attempt, and a timeout is an unknown outcome.
</Warning>

If a call times out or the connection drops, the registration may well have succeeded. Retrying the identical request then returns `AUTH-0026` — which is indistinguishable from the user having had an account all along.

That ambiguity is harmless as long as you treat both the same way: **on timeout, retry once, and route `AUTH-0026` / `AUTH-0027` to authorization rather than to an error state.** Either the account you just made or the account that already existed ends up authorized, which is the outcome you wanted. What you must not do is surface "phone number already in use" to a user who just gave you their number for the first time.

***

## After registration

A registered account is not yet a verified one. Before the user can move money you need:

1. **Identity verification.** Pass the SSN and address you hold to `verifyUserInformation`, or issue a document verification link for the user to complete. → [User KYC Verification](/user-kyc-verification)
2. **An authorization grant.** Registering someone doesn't give you permission to act for them — that's a separate, explicit step. → [Client-facing OAuth grant flow](/client-facing-o-auth-grant-flow)
3. **Your own identifier attached.** Pass `external_id` on the authorization so you can address this person by your own user ID from then on. → [Managing External Reference IDs](/managing-external-reference-ids)

***

## Handling the data

You're transmitting full name, date of birth, email, and phone number — a set that identifies a real person. Send it over TLS from your server, keep it out of logs and error-tracking payloads, and don't echo it back in client-visible responses.

Note that date of birth in particular is regulated identity data in most jurisdictions, and it stays sensitive on your side after the call succeeds. If you'd rather not hold any of it, that's the argument for the [widget](/developers/widgets) — Fluz collects it inside its own compliance scope and you never touch it.

***

## Error codes

| Code        | Meaning                                                        | What to do                                                     |
| :---------- | :------------------------------------------------------------- | :------------------------------------------------------------- |
| `AUTH-0004` | Phone number invalid or unparseable for the given `regionCode` | Fix the input. Check `regionCode` matches the number's country |
| `AUTH-0022` | Your application isn't permitted to register users             | Contact your account manager. Don't retry                      |
| `AUTH-0025` | General registration failure                                   | Retryable. Escalate if it persists                             |
| `AUTH-0026` | Phone number already on an account                             | Route to authorization — see above                             |
| `AUTH-0027` | Email already on an account                                    | Route to authorization — see above                             |
| `AUTH-0030` | Your application is not active                                 | Check the app's status in the dashboard. Don't retry           |

***

## Environments

Point at the GraphQL host for your environment — `https://transactional-graph.staging.fluzapp.com/api/v1/graphql` for staging, `https://transactional-graph.fluzapp.com/api/v1/graphql` for production. Registration permission is granted per application, so a production app needs it enabled separately. → [Deploying to Production](/deploying-to-production)

Never register real people in staging. → [Staging vs. Live](/concepts/environments)

***

## Next steps

<CardGroup cols={2}>
  <Card title="User KYC verification" icon="user-check" href="/user-kyc-verification">
    Verify the account you just created.
  </Card>

  <Card title="Grant flow" icon="shield-check" href="/client-facing-o-auth-grant-flow">
    Get permission to act on their behalf.
  </Card>

  <Card title="External reference IDs" icon="id-card" href="/managing-external-reference-ids">
    Address them by your own user ID.
  </Card>

  <Card title="Embedded widgets" icon="layout-template" href="/developers/widgets">
    Hand the whole onboarding to Fluz instead.
  </Card>
</CardGroup>
