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

# Send to any phone or wallet (instant, on-chain)

> Moves USDC between two customer SCWs on Polygon PoS via an ERC-4337 UserOperation. Settlement is on-chain and final within seconds — no banks, no correspondents, no cut-off times.

**Addressing:** `sender_phone` and `recipient_phone` each accept EITHER an E.164 phone number (`+2250700000001`) OR a 0x-prefixed 40-character SCW wallet address — the format is auto-detected, so there is no separate field to learn. Use whichever identifier you already have for that party.

**Sender** must be a user affiliated with your business (provisioned under or adopted by your account).

**Recipient can be ANY phone number or existing wallet address** — system-wide, merchant-agnostic. If the recipient belongs to another business, their business is notified by webhook. If addressed by phone and that number has never been provisioned by anyone, Rach creates an unaffiliated wallet on the fly (`recipient_status: PROVISIONED_ON_THE_FLY`) and notifies the recipient by SMS with instructions to access their money via Rach's USSD service. Addressing by wallet address requires that wallet to already exist — it cannot be auto-provisioned, since there's no phone number to derive a fresh identity from.

For fiat-denominated transfers supply a valid quote_id from POST /v1/fx/quote. For direct crypto amounts use quote_id format `DIRECT_CRYPTO:USDC:{amount}` (e.g. `DIRECT_CRYPTO:USDC:10.50`). AML daily/monthly limits apply per sender.



## OpenAPI

````yaml /api-reference/caas-openapi.json post /v1/transfers/send
openapi: 3.0.0
info:
  description: >-
    Rach CaaS is the stablecoin infrastructure layer for African fintechs and
    financial institutions. It enables any business to offer their customers
    non-custodial USDC and USDT wallets, instant on-chain transfers, and fiat ↔
    stablecoin conversion in any supported currency (XOF, XAF, NGN, GHS, ...) —
    without building blockchain infrastructure.


    **Stablecoins:** USDC and USDT are supported equally everywhere. On any
    endpoint that moves value — `POST /v1/users/fund`, `POST
    /v1/transfers/send`, `POST /v1/users/withdraw` — set the token
    (`target_token` / `token`, or the ticker inside a
    `DIRECT_CRYPTO:{USDC|USDT}:{amount}` quote). `GET /v1/users/balance` returns
    both `balance_usdc` and `balance_usdt`.


    **System-wide phone addressing:** wallets are keyed by phone number and
    belong to the system, not to any one business. Money moves freely across
    businesses — and to phone numbers that have never been provisioned: Rach
    creates the wallet on the fly and notifies the recipient by SMS with USSD
    access instructions.


    **Network:** Polygon PoS (Chain ID 137). All smart contract wallets,
    transfers, and withdrawals settle on Polygon mainnet. Test environments use
    Sepolia (Chain ID 11155111).


    ## Authentication

    **B2B API endpoints** (`/v1/users/*`, `/v1/transfers/*`, `/v1/fx/*`) use API
    Key authentication. Pass your key in the `X-API-Key` header. Keys are
    prefixed `rach_sk_live_` (mainnet) or `rach_sk_test_` (sandbox — no on-chain
    execution).


    **Dashboard endpoints** (`/v1/dashboard/*`) use JWT Bearer authentication.
    Your JWT must include `business_id` and the `waas` permission scope.


    ## Sandbox Mode

    All B2B endpoints honour test-mode API keys (`rach_sk_test_*`). In sandbox
    mode every operation returns an immediate success response
    (`SANDBOX_SIMULATED`) without touching the blockchain or debiting your
    treasury balance. Use sandbox keys for integration testing and CI.


    ## Typical Integration Flow

    1. `POST /v1/users/provision` — create a non-custodial SCW for each end-user
    (offline, no gas)

    2. Collect fiat from customer → deposit to Rach bank account → `POST
    /v1/dashboard/treasury/topup`

    3. Rach confirms XOF receipt → `POST
    /v1/dashboard/treasury/topup/{id}/confirm` (credits your USDC balance)

    4. `POST /v1/users/fund` — transfer USDC from your treasury balance to the
    customer SCW on-chain

    5. `GET /v1/users/balance` — read live on-chain USDC balance via Polygon RPC

    6. `POST /v1/transfers/send` — instant peer-to-peer USDC transfer between
    customer SCWs (ERC-4337 UserOp)

    7. `POST /v1/users/withdraw` — off-ramp: sweep USDC from SCW back to Rach
    treasury; Rach pays out XOF via mobile money


    ## System-Wide Wallets & Multi-Membership

    A wallet is keyed by phone number and belongs to the **system**, not to any
    one business. Several businesses can operate the same wallet — but only with
    the customer's consent.


    **Provisioning outcomes** (`POST /v1/users/provision`):

    - `PROVISIONED` — new wallet created; you are its first member.

    - `ADOPTED` — the phone had an unaffiliated wallet (created on the fly by an
    inbound transfer); you are now its primary member.

    - `ALREADY_EXISTS` — the wallet exists and you already operate it.

    - `LINK_REQUIRED` — the wallet belongs to another business. You get the same
    address back but **no operating rights**. Start the consent flow to gain
    them.


    **Consent-based linking** (to operate a wallet another business created):

    1. `POST /v1/users/link/request` — a one-time code is SMSed to the customer.

    2. `POST /v1/users/link/confirm` — submit the code the customer gives you;
    you become a member. Existing members receive a `user.linked` webhook.


    **Money-in is open, money-out is gated.** Any business can fund a wallet or
    send money to it (paid from the sender's own ledger). Only a **member** may
    originate transfers, withdraw, or change the phone number for a wallet.


    **Consent-gated phone change** (`POST /v1/users/phone-change/request` →
    `/confirm`): a one-time code is SMSed to the NEW number (proving SIM
    control). The SCW address, balance and history are preserved; every member
    receives a `user.phone_changed` webhook.


    ## Webhook Events

    Signed with HMAC-SHA256 over the body in the `X-Rach-Signature: sha256=...`
    header (key = your webhook secret).

    - `transfer.received` — a wallet you operate received an inbound transfer.

    - `user.linked` — a new business was granted operating rights on a wallet
    you operate.

    - `user.phone_changed` — the phone number of a wallet you operate was
    re-keyed (address unchanged).


    ## Multi-Currency

    Rach moves any fiat; USDC is the settlement rail. Rates are per-currency
    (XOF, XAF, NGN, GHS, ...). See `GET /v1/dashboard/rates`. Topup quotes and
    withdrawals carry a `currency_code` / `payout_currency`.


    ## Status Lifecycles

    **Deposit:** `QUEUED` → `SETTLED`

    **Transfer:** `QUEUED` → `SUBMITTED` → `SETTLED`

    **Withdrawal:** `PENDING` → `SUBMITTED` → `CRYPTO_RECEIVED` → `COMPLETED`
  title: Rach CaaS — Crypto-as-a-Service API
  contact:
    name: Rach Finance Support
    url: https://rach.finance
  version: 1.0.1
servers:
  - url: https://api.rach.finance/caas/api
security: []
paths:
  /v1/transfers/send:
    post:
      tags:
        - B2B - Transfers
      summary: Send to any phone or wallet (instant, on-chain)
      description: >-
        Moves USDC between two customer SCWs on Polygon PoS via an ERC-4337
        UserOperation. Settlement is on-chain and final within seconds — no
        banks, no correspondents, no cut-off times.


        **Addressing:** `sender_phone` and `recipient_phone` each accept EITHER
        an E.164 phone number (`+2250700000001`) OR a 0x-prefixed 40-character
        SCW wallet address — the format is auto-detected, so there is no
        separate field to learn. Use whichever identifier you already have for
        that party.


        **Sender** must be a user affiliated with your business (provisioned
        under or adopted by your account).


        **Recipient can be ANY phone number or existing wallet address** —
        system-wide, merchant-agnostic. If the recipient belongs to another
        business, their business is notified by webhook. If addressed by phone
        and that number has never been provisioned by anyone, Rach creates an
        unaffiliated wallet on the fly (`recipient_status:
        PROVISIONED_ON_THE_FLY`) and notifies the recipient by SMS with
        instructions to access their money via Rach's USSD service. Addressing
        by wallet address requires that wallet to already exist — it cannot be
        auto-provisioned, since there's no phone number to derive a fresh
        identity from.


        For fiat-denominated transfers supply a valid quote_id from POST
        /v1/fx/quote. For direct crypto amounts use quote_id format
        `DIRECT_CRYPTO:USDC:{amount}` (e.g. `DIRECT_CRYPTO:USDC:10.50`). AML
        daily/monthly limits apply per sender.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cmd_api.TransferRequest'
        description: Transfer payload
        required: true
      responses:
        '202':
          description: Accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cmd_api.TransferResponse'
        '400':
          description: Invalid payload or expired FX quote
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '404':
          description: Sender or recipient wallet not provisioned
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '409':
          description: Duplicate idempotency key
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '410':
          description: FX quote expired
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '429':
          description: 'AML block: daily or monthly limit exceeded'
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
      security:
        - ApiKeyAuth: []
components:
  schemas:
    cmd_api.TransferRequest:
      type: object
      required:
        - idempotency_key
        - local_fiat_amount
        - quote_id
        - recipient_phone
        - sender_phone
        - target_token
      properties:
        idempotency_key:
          type: string
          example: txn_ref_abc123
        local_fiat_amount:
          type: string
          example: '5500.00'
        quote_id:
          type: string
          example: DIRECT_CRYPTO:USDC:0.50
        recipient_phone:
          description: >-
            RecipientPhone identifies the recipient — same dual format.
            Addressing by

            phone allows on-the-fly provisioning for a never-seen number;
            addressing

            by wallet address requires that wallet to already exist.
          type: string
          example: '+2250700000002'
        sender_phone:
          description: >-
            SenderPhone identifies the sender — accepts EITHER an E.164 phone
            number

            (+2250700000001) OR a 0x-prefixed wallet address. No separate field

            needed: the format is auto-detected.
          type: string
          example: '+2250700000001'
        target_token:
          type: string
          enum:
            - USDT
            - USDC
          example: USDC
    cmd_api.TransferResponse:
      type: object
      properties:
        created_at:
          type: string
        message:
          type: string
        recipient_status:
          description: >-
            RecipientStatus is "EXISTING" when the recipient already had a
            wallet, or

            "PROVISIONED_ON_THE_FLY" when Rach auto-created an unaffiliated
            wallet for

            their phone number (recipient gets an SMS with USSD access
            instructions).
          type: string
          example: EXISTING
        status:
          type: string
        transfer_id:
          type: string
  securitySchemes:
    ApiKeyAuth:
      description: >-
        Your Rach B2B API key. Use `rach_sk_live_*` for production (on-chain) or
        `rach_sk_test_*` for sandbox (no-chain simulation).
      type: apiKey
      name: X-API-Key
      in: header

````