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

# Register User, Create Default Wallets, And Return User And Wallets With Balances

> Registers a new user and best-effort provisions default Ethereum, TRON, and Solana custodial wallets with zero balances.
**Duplicate contact guard:** Before any user row or default wallet is created, **email** and **phone_number** must be unique among active users and within the request (bulk create). On conflict the API returns **409** and makes **no** database changes.

During registration, the user can also set an optional 6-digit numeric PIN (`pin`) which
can later be used to log in via the PIN-based login endpoint (`POST /api/v1/auth/login-pin`) or 
as transaction PIN to authorize a transaction. 
If `pin` is omitted during log in, the user can still log in with email + password but cannot use PIN login
until a PIN is set.




## OpenAPI

````yaml /openapi.yaml post /api/v1/users/register
openapi: 3.1.0
info:
  title: EmaalCoin API
  description: >
    EmaalCoin API reference. These APIs power cryptocurrency exchange between
    USDT and Fiat trading.


    - **On-Ramp**: Buy USDT with fiat (KES) via mobile money (STK Push for
    Kenya)

    - **Off-Ramp**: Sell USDT for KES with mobile money payout (Kenya)

    - **Ethereum, TRON, and Solana**: Multi-chain wallet support

    - **Merchant Offers**: Create and manage trading offers

    - **OTC Flows**: Bank and mobile money deposits/withdrawals for merchants

    - **Pricing engine**: FX quotes and fee profiles for quote-backed orders and
    transfers

    - **P2P/B2B transfers**: Internal and external stablecoin sends (USDT/USDC)
    with optional pricing snapshots

    - **Stablecoin swaps**: Treasury-backed two-leg USDT ↔ USDC conversion with
    BitGo confirmation, inventory reservation, and automatic refunds

    - **Multisig**: Collaborative and platform-managed multisig flows for
    high-value B2B
  version: 1.1.0
servers:
  - url: https://crypto.westminister.tech
    description: Production API (requires DNS for this hostname).
  - url: http://13.42.110.185:8080
    description: >-
      UAT on EC2 (direct). Use this base when building proof PDF URLs if the
      production hostname does not resolve.
security: []
tags:
  - name: Pricing
    description: >-
      Commercial pricing profiles and FX rate quotes (admin). Used with orders
      and transfers for auditable snapshots.
  - name: Transfers
    description: >-
      P2P and B2B stablecoin transfers (USDT/USDC). Optional fiat-equivalent
      pricing fields. B2B multisig behavior depends on wallet registration
      (synchronous completion vs proposal flow).
  - name: Stablecoin Swaps
    description: >-
      Treasury-backed USDT ↔ USDC conversions for BitGo provider-managed wallets
      on `tron`, `ethereum`, or `solana`. Collection confirms before treasury
      payout starts. Completion requires both legs and dual-asset balance
      synchronization.
  - name: Multisig
    description: >-
      Multisig wallet registration and TRON proposal/confirm flows
      (vendor/admin).
  - name: Escrow Wallets
    description: Escrow pool wallet metadata for off-ramp and operations.
  - name: Users
    description: >-
      User-related operations (registration, lookup, updates, counts, status
      changes)
  - name: Wallets-Users
    description: User wallet operations (create, manage, query balances)
  - name: Offers
    description: Offer operations (create, read, list, counts)
  - name: Orders
    description: Order operations (create, update, list, counts, stats)
  - name: Payment
    description: Payment initiation operations
  - name: Merchants
    description: >
      Merchant (vendor) onboarding and hierarchy operations.

      USE **GET /API/V1/AUTH/MERCHANTS** WITH **PAGE_ID** AND **PAGE_SIZE** TO
      PAGE MASTER VENDORS.

      USE **GET /API/V1/AUTH/MERCHANTS/{MERCHANT_ID}** FOR ONE MASTER VENDOR,
      **MERCHANT_WALLETS**, AND ALL **SUBVENDOR** ROWS WITH **WALLETS** (FULL
      TREE).
  - name: OTC Deposits
    description: >
      OTC fiat-to-crypto deposits (bank or mobile money). Rows live in
      **`otc_deposits`**. Merchants create drafts or one-shot deposits.
      **admin**, **otc_officer**, or **treasury** list all deposits.

      B2B bank flow uses draft, quote, submit, proof, assign, claim, settlement,
      and audit. **`credited_to_address`** is the merchant on-chain wallet
      treasury credits (resolved from the default wallet for
      **`requested_network`** on the master merchant ledger user or any
      **sub-vendor** under that merchant).

      **`GET /api/v1/auth/otc/deposits`** backfills missing
      **`credited_to_address`**/**`to_wallet_id`** when a wallet can be
      resolved. One-shot **`POST /api/v1/auth/otc/deposits`** persists the
      payout address when **`requested_network`** is sent.

      Bank proof PDFs upload via `POST .../proof` and are viewed at **`GET
      /otc-deposits-proof/{deposit_id}/{file}.pdf`** (relative
      **`proof_url`**/**`file_url`** plus API base URL).

      Merchants may edit tab-1 draft fields with **`PATCH
      /api/v1/auth/otc/deposits/{id}/draft`** while **`status`** is **`draft`**.
  - name: OTC Withdrawals
    description: >-
      OTC crypto-to-fiat withdrawals (bank or mobile money). Merchants create.
      Admin approves and executes.
  - name: OTC Config
    description: OTC display config (bank details, mobile money paybill) for merchant UI
  - name: Fiat Currencies
    description: >
      Shared platform **fiat_currencies** catalog. **GET
      /api/v1/auth/fiat-currencies** lists active rows for UI pickers. **GET
      /api/v1/auth/fiat-currencies/admin** lists all rows including inactive
      (admin). Admin manages rows with **GET
      /api/v1/auth/fiat-currencies/{id}**, **POST
      /api/v1/auth/fiat-currencies**, **PATCH
      /api/v1/auth/fiat-currencies/{id}**, and **DELETE
      /api/v1/auth/fiat-currencies/{id}**. Use for OTC, pricing, orders,
      remittance, and backoffice — not under **`/otc/...`**. Prefer **PATCH**
      with **`is_active: false`** over delete when a currency is referenced.
  - name: Countries
    description: >-
      Shared platform **countries** catalog. Public **GET /api/v1/countries**
      lists active rows for pickers. Auth **GET /api/v1/auth/countries** returns
      the same active catalog. Admin manages rows with **GET
      /api/v1/auth/countries/admin**, **GET /api/v1/auth/countries/{id}**,
      **POST /api/v1/auth/countries**, **PATCH /api/v1/auth/countries/{id}**,
      and **DELETE /api/v1/auth/countries/{id}**.
  - name: Files
    description: Authenticated file uploads for OTC receipts and proof artifacts.
paths:
  /api/v1/users/register:
    post:
      tags:
        - Users
      summary: >-
        Register User, Create Default Wallets, And Return User And Wallets With
        Balances
      description: >
        Registers a new user and best-effort provisions default Ethereum, TRON,
        and Solana custodial wallets with zero balances.

        **Duplicate contact guard:** Before any user row or default wallet is
        created, **email** and **phone_number** must be unique among active
        users and within the request (bulk create). On conflict the API returns
        **409** and makes **no** database changes.


        During registration, the user can also set an optional 6-digit numeric
        PIN (`pin`) which

        can later be used to log in via the PIN-based login endpoint (`POST
        /api/v1/auth/login-pin`) or 

        as transaction PIN to authorize a transaction. 

        If `pin` is omitted during log in, the user can still log in with email
        + password but cannot use PIN login

        until a PIN is set.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterUserRequest'
      responses:
        '201':
          description: User registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterUserResponse'
        '409':
          description: >-
            Email or phone_number already registered, or duplicated within the
            request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
              examples:
                email_taken:
                  summary: Email already registered
                  value:
                    error: email is already registered
                phone_taken:
                  summary: Phone already registered
                  value:
                    error: phone_number is already registered
components:
  schemas:
    RegisterUserRequest:
      type: object
      required:
        - email
        - phone_number
        - first_name
        - last_name
        - password
        - role
        - kyc_status
      properties:
        email:
          type: string
          format: email
          description: User's login email. Must be unique.
        phone_number:
          type: string
          description: E.164 format phone (e.g., +2547xxxxxxx).
        first_name:
          type: string
          description: Given name.
        last_name:
          type: string
          description: Family name.
        password:
          type: string
          format: password
          description: User password (min 6 chars).
        role:
          type: string
          enum:
            - customer
            - vendor
            - user
          description: Role to assign.
        kyc_status:
          type: string
          enum:
            - pending
            - approved
            - rejected
          description: Initial KYC state. Usually pending.
        pin:
          type: string
          pattern: ^[0-9]{6}$
          description: >
            Optional 6-digit numeric PIN used for PIN-based login (POST
            /api/v1/users/login-pin). If omitted, the user can still log in with
            email + password but cannot use PIN login until a PIN is set.
      example:
        email: tigger.germain@gmail.com
        phone_number: '+254723453286'
        first_name: Tigger
        last_name: Germain
        password: secret
        role: vendor
        kyc_status: pending
        pin: '123456'
    RegisterUserResponse:
      type: object
      properties:
        message:
          type: string
          description: Success message.
        user:
          $ref: '#/components/schemas/User'
        wallets:
          type: array
          items:
            $ref: '#/components/schemas/WalletWithBalances'
        warning:
          type: string
          description: Present when one or more default wallets failed.
        wallet_errors:
          type: array
          items:
            $ref: '#/components/schemas/DefaultWalletFailure'
      example:
        message: User registered successfully with default wallets
        user:
          id: 383f20af-5960-4374-a5c3-b7566ea8f771
          email: tigger.germain@gmail.com
          phone_number: 254723453286
          first_name: Tigger
          last_name: Germain
          role: vendor
          kyc_status: pending
        wallets:
          - wallet:
              id: ea963c72-35d4-4a4e-8e32-70971c27f188
              user_id: 383f20af-5960-4374-a5c3-b7566ea8f771
              name: Ethereum Wallet
              address: 1.0526201056375617e+48
              wallet_type: hot
              network: ethereum
              is_default: true
              is_active: true
            balances:
              - id: dd9bbc6b-a3b5-4b5e-97db-e00aa8389ee1
                user_id: 383f20af-5960-4374-a5c3-b7566ea8f771
                wallet_id: ea963c72-35d4-4a4e-8e32-70971c27f188
                currency: ETH
                token_type: ETH
                token_symbol: ETH
                amount: '0.00000000'
                usd_value: '0.00'
                locked_amount: '0.00000000'
                last_price_usd: '0.00'
              - id: 309e195d-6b6b-4a41-b793-5452f5c1ed02
                user_id: 383f20af-5960-4374-a5c3-b7566ea8f771
                wallet_id: ea963c72-35d4-4a4e-8e32-70971c27f188
                currency: USDT
                token_type: USDT
                token_symbol: USDT
                amount: '0.00000000'
                usd_value: '0.00'
                locked_amount: '0.00000000'
                last_price_usd: '0.00'
          - wallet:
              id: 05e36fd9-88e9-4dfb-8d8b-07493cef8e84
              user_id: 383f20af-5960-4374-a5c3-b7566ea8f771
              name: TRON Wallet
              address: TFviB3ioZTnbPJVqqWjqZJbc6zu7hkeyEc
              wallet_type: hot
              network: tron
              is_active: true
            balances:
              - id: 57bcec93-ea33-4fe3-bbd1-d9e10bad45ca
                user_id: 383f20af-5960-4374-a5c3-b7566ea8f771
                wallet_id: 05e36fd9-88e9-4dfb-8d8b-07493cef8e84
                currency: TRX
                token_type: TRX
                token_symbol: TRX
                amount: '0.00000000'
                usd_value: '0.00'
                locked_amount: '0.00000000'
                last_price_usd: '0.00'
              - id: 75ca706e-9461-489b-ad2d-03bb82043d01
                user_id: 383f20af-5960-4374-a5c3-b7566ea8f771
                wallet_id: 05e36fd9-88e9-4dfb-8d8b-07493cef8e84
                currency: USDT
                token_type: USDT
                token_symbol: USDT
                amount: '0.00000000'
                usd_value: '0.00'
                locked_amount: '0.00000000'
                last_price_usd: '0.00'
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: User ID.
        email:
          type: string
          format: email
          description: User email.
        phone_number:
          type: string
          description: Phone in E.164 format.
        first_name:
          type: string
          description: Given name.
        last_name:
          type: string
          description: Family name.
        role:
          type: string
          enum:
            - customer
            - vendor
            - subvendor
            - user
            - admin
          description: >-
            Assigned role. Admin can only be assigned via PUT
            /api/v1/auth/user/id/{id}/role by an existing admin.
        kyc_status:
          type: string
          description: Current KYC status.
    WalletWithBalances:
      type: object
      properties:
        wallet:
          $ref: '#/components/schemas/Wallet'
        balances:
          type: array
          items:
            $ref: '#/components/schemas/Balance'
    DefaultWalletFailure:
      type: object
      description: Per-network error when default wallet provisioning fails for one chain.
      properties:
        network:
          type: string
          enum:
            - ethereum
            - tron
            - solana
        error:
          type: string
          description: Error message from the failed wallet create attempt.
    Wallet:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Wallet ID.
        user_id:
          type: string
          format: uuid
          description: Owner user ID.
        name:
          type: string
          description: Wallet label (e.g.
          Ethereum Wallet).: null
        address:
          type: string
          description: Chain address.
        alias:
          type: integer
          format: int64
          description: Globally unique numeric wallet alias.
        wallet_type:
          type: string
          description: hot/cold/etc.
        network:
          type: string
          description: Network name (ethereum
          tron: null
          solana).: null
        is_default:
          type: boolean
          description: Whether this is the default wallet for the network.
        is_active:
          type: boolean
          description: Whether wallet is active.
        created_at:
          type: string
          format: date-time
          description: Wallet creation timestamp (RFC 3339).
    Balance:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Balance record ID.
        user_id:
          type: string
          format: uuid
          description: Owning user ID.
        wallet_id:
          type: string
          format: uuid
          description: Wallet ID.
        currency:
          type: string
          description: Currency code (e.g., ETH, USDT, TRX).
        token_type:
          type: string
          description: Token standard/type.
        token_symbol:
          type: string
          description: Display symbol.
        amount:
          type: string
          description: Free amount.
        usd_value:
          type: string
          description: USD value of amount.
        locked_amount:
          type: string
          description: >-
            Portion reserved/unavailable. Available = amount - locked_amount.
            Examples: open orders, pending transfers, settlement holds.
        last_price_usd:
          type: string
          description: >-
            Latest USD price for this token, used to compute usd_value (fiat
            equivalent).

````