> ## 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.

# CaaS — Money to any phone

> Move real money to anyone in the world with a phone number — or run pure USDC/USDT wallets.

Rach **CaaS** is a global money-movement rail. Give your customers non-custodial **USDC /
USDT** wallets, then **fund** them from local currency, **send** to anyone in the world by
phone number, and **cash out** back to local fiat — all over WhatsApp, SMS, USSD or email,
so it works on a smartphone or a basic feature phone.

It's flexible: use the full **fiat → stablecoin → fiat** money-movement flow, or treat it
as pure **stablecoin infrastructure** (non-custodial USDC/USDT wallets + on-chain transfers).
Wallets are ERC-4337 smart-contract wallets on **Polygon** (Sepolia in test), keyed by
**phone number**.

<Warning>
  CaaS is a **separate product with its own base URL and its own API key**. Base URL
  `https://api.rach.finance/caas/api/v1/`; B2B keys are prefixed `rach_sk_test_` /
  `rach_sk_live_` and sent in `X-API-Key`. A Payments `live_sk_`/`test_sk_` key will **not**
  work here. See [Get your API keys](/guides/api-keys).
</Warning>

## The mental model

The single most important idea: **a wallet belongs to the person, not the business that
created it.** A wallet is keyed by phone number and holds real, non-custodial USDC on
Polygon. What each business keeps privately is its own **USDC ledger** — the balance it
pre-funds with Rach and draws down when acting for its customers.

<CardGroup cols={3}>
  <Card title="Money-in is open" icon="lock-open">
    Anyone can fund a wallet or send to a phone number — even one that has never been
    registered. The cost is always paid from the sender's own ledger.
  </Card>

  <Card title="Money-out is gated" icon="lock">
    Only a **member** of a wallet may originate a transfer, withdraw to cash, or change its
    phone number. Membership starts at provisioning and grows only with the customer's consent.
  </Card>

  <Card title="Identity is portable" icon="fingerprint">
    The address is derived from a permanent identity salt, not the phone number. Change the
    SIM, keep the wallet, balance and history intact.
  </Card>
</CardGroup>

## Reach

Rach meets customers on whatever channel they have — **WhatsApp, SMS, USSD** (where
supported) and **email**. Every notification and self-service flow works the same whether
the customer has a smartphone or a basic feature phone, so no one is left unable to claim or
move their money.

## The flow, end to end

<Steps>
  <Step title="Pre-fund your ledger (once)">
    Top up your USDC ledger with Rach from your **dashboard** — request a locked rate, wire
    the local currency, Rach confirms receipt and credits your spendable balance.
  </Step>

  <Step title="Provision the customer">Create their wallet by phone number (offline, gasless).</Step>
  <Step title="Fund the wallet">Move USDC/USDT from your ledger to the customer wallet on-chain.</Step>
  <Step title="Send or cash out">Send to any phone worldwide, or off-ramp back to local fiat.</Step>
</Steps>

<Note>
  **Why the ledger and the wallet are separate:** your ledger is keyed to your business and
  the wallet is keyed to the person. Funding a customer who also uses another Rach partner
  simply debits **your** balance and tops up the shared wallet — the other business is
  unaffected.
</Note>

## Provision a user

Provisioning derives the smart-contract wallet address offline (no gas, no on-chain
transaction) and records you as the wallet's first member.

```bash theme={null}
curl https://api.rach.finance/caas/api/v1/users/provision \
  -H "X-API-Key: $CAAS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number": "+2348010000001" }'
```

```json Response theme={null}
{
  "wallet_address": "0xSCW...",
  "blind_index": "bi_9f8e...",
  "status": "PROVISIONED",
  "created_at": "2026-08-14T12:00:00Z"
}
```

The `status` tells you what you can do next:

| Status           | Meaning                                                      | Can you operate?                                          |
| ---------------- | ------------------------------------------------------------ | --------------------------------------------------------- |
| `PROVISIONED`    | New wallet created for this phone                            | Yes — first member                                        |
| `ADOPTED`        | Took over a wallet created on the fly by an inbound transfer | Yes — primary member                                      |
| `ALREADY_EXISTS` | Wallet exists and you already operate it                     | Yes                                                       |
| `LINK_REQUIRED`  | Wallet belongs to another business                           | Not yet — run the [consent flow](#multi-business-wallets) |

## Read balances

```bash theme={null}
curl "https://api.rach.finance/caas/api/v1/users/balance?phone_number=+2348010000001" \
  -H "X-API-Key: $CAAS_KEY"
```

```json Response theme={null}
{ "wallet_address": "0xSCW...", "balance_usdc": "40.00", "balance_usdt": "0.00" }
```

## Fund a customer (on-ramp)

Move stablecoins from your ledger to a customer wallet. Rach atomically debits your ledger
**before** touching the chain — insufficient balance is refused cleanly with no on-chain
effect — then settles as a gasless UserOperation.

```bash theme={null}
curl https://api.rach.finance/caas/api/v1/users/fund \
  -H "X-API-Key: $CAAS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+2348010000001",
    "target_token": "USDC",
    "stablecoin_amount": "40.00",
    "local_fiat_amount": "60000",
    "deposit_id": "dep_1001"
  }'
```

## Send to any phone (or wallet)

Send USDC/USDT to **any phone number in the world** — instant and final on-chain within
seconds. The recipient does **not** need to be your customer, or provisioned at all: if the
phone has never been seen, Rach creates a wallet on the fly and notifies the recipient on
their channel (WhatsApp / SMS / USSD / email) with how to claim and cash out — no app required.

`sender_phone` and `recipient_phone` each accept **either** an E.164 phone number
(`+2250700000001`) **or** a `0x` SCW wallet address — the format is auto-detected. Convert
fiat first with an [FX quote](#fx-conversion) if needed, and always pass an `idempotency_key`.

```bash theme={null}
curl https://api.rach.finance/caas/api/v1/transfers/send \
  -H "X-API-Key: $CAAS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotency_key": "xfer_7c1e",
    "sender_phone": "+2348010000001",
    "recipient_phone": "+2348010000002",
    "quote_id": "qt_...",
    "local_fiat_amount": "5000",
    "target_token": "USDC"
  }'
```

<Info>
  **Money-in is open, money-out is gated.** The **sender** must be a member of a wallet you
  operate — knowing a phone number is never enough to move someone's money (it's rejected with
  a clear permission error). The **recipient can be anyone**. If the recipient belongs to
  another business, that business receives a `transfer.received` [webhook](/guides/webhooks);
  if they're unregistered, Rach notifies them directly to claim.
</Info>

## FX conversion

Quote fiat → stablecoin (locked for a short window) before you fund or send.

```bash theme={null}
curl https://api.rach.finance/caas/api/v1/fx/quote \
  -H "X-API-Key: $CAAS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fiat_amount": 5000, "local_currency": "XOF", "target_token": "USDC" }'
```

```json Response theme={null}
{
  "quote_id": "qt_a1b2",
  "currency_pair": "XOF/USDC",
  "rate": "620.5",
  "fiat_amount": "5000",
  "expected_out": "8.06",
  "target_token": "USDC",
  "expires_at": "2026-08-14T12:10:00Z"
}
```

Rates are per-currency (XOF, XAF, NGN, GHS, …) — USDC is the settlement rail underneath.

## Cash out (off-ramp)

Two routes get money back to a local bank or mobile-money account.

**Business-initiated** — you off-ramp your own customer:

```bash theme={null}
curl https://api.rach.finance/caas/api/v1/users/withdraw \
  -H "X-API-Key: $CAAS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+2348010000001",
    "amount": "10.00",
    "token": "USDC",
    "payout_mobile": "+2348010000001",
    "payout_network": "MTN",
    "payout_currency": "NGN",
    "idempotency_key": "wd_5521"
  }'
```

Rach sweeps the USDC on-chain and disburses local currency to the mobile-money or bank
destination.

**Self-service** — someone who received money without belonging to any business cashes out
through Rach directly over their channel, after a one-time identity check. No API call on
your side.

<Warning>
  **Compliance gate.** Receiving money never requires identity — anyone can be paid — but
  **no payout leaves the system until Rach has screened the recipient** against AML and
  sanctions checks.
</Warning>

## Swap USDC ⇄ USDT

```bash theme={null}
curl https://api.rach.finance/caas/api/v1/swaps \
  -H "X-API-Key: $CAAS_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "phone_number": "+2348010000001", "from_token": "USDC", "to_token": "USDT", "amount": "10.00" }'
```

Poll `GET /v1/swaps/{id}` for status.

## Multi-business wallets (consent)

A person can be a customer of several Rach partners at once — one wallet, one address
everywhere. A second business gaining the right to **act** on that wallet requires the
customer's explicit consent. Provisioning a phone that already belongs to another business
returns the address with `LINK_REQUIRED` and no operating rights; run the consent flow:

<Steps>
  <Step title="Request to link">
    `POST /v1/users/link/request` — Rach sends a one-time code to the customer by WhatsApp/SMS.
  </Step>

  <Step title="Customer approves">
    The customer shares the code with you — possession of the phone is the consent.
  </Step>

  <Step title="Confirm the link">
    `POST /v1/users/link/confirm` with the code — you become a member (existing members get a
    `user.linked` webhook).
  </Step>
</Steps>

After linking, both businesses operate the same wallet independently — each still pays from
its own ledger. The original business is never displaced; **membership only grows.**

## Changing a phone number

Because the phone number is the shared lookup key, changing it is consent-gated: a code is
sent to the **new** number (proving SIM control), and the address, balance and history are
preserved.

<Steps>
  <Step title="Request the change">`POST /v1/users/phone-change/request` (old + new number).</Step>
  <Step title="Confirm with the code">`POST /v1/users/phone-change/confirm` — the lookup key is re-pointed.</Step>
</Steps>

Every member receives a `user.phone_changed` webhook so no one is left pointing at a stale key.

## Status lifecycles

| Flow                  | Lifecycle                                                 |
| --------------------- | --------------------------------------------------------- |
| Funding / deposit     | `QUEUED` → `SETTLED`                                      |
| Transfer              | `QUEUED` → `SUBMITTED` → `SETTLED`                        |
| Withdrawal / off-ramp | `PENDING` → `SUBMITTED` → `CRYPTO_RECEIVED` → `COMPLETED` |

## What you can build

The same primitives compose into very different products:

<CardGroup cols={2}>
  <Card title="Neobank / wallet app" icon="building-columns">
    Provision each customer, pre-fund your ledger, fund wallets on demand. Customers send to
    each other and to anyone else instantly, in-app.
  </Card>

  <Card title="Cross-border remittance" icon="globe">
    Pay funds to any recipient by phone number; registered users are notified in-app,
    unregistered ones cash out via USSD after an identity check.
  </Card>

  <Card title="Mobile-money operator" icon="mobile">
    Bring wallets to feature phones — balance, send and cash-out over USSD / WhatsApp / SMS,
    gaslessly.
  </Card>

  <Card title="Marketplace / gig payouts" icon="store">
    Disburse to workers and sellers by phone number even before they've signed up — wallets
    are created on the fly and the recipient is guided to their money.
  </Card>
</CardGroup>

## Trust model

* **Non-custodial wallets** — funds sit in the customer's own smart-contract wallet on
  Polygon, with per-transaction limits and timelocked recovery.
* **Phone numbers are never stored in the clear** — lookups use a keyed blind index; the
  address is derived from a permanent identity salt.
* **Consent is cryptographic possession** — gaining rights or re-keying a phone always
  requires a one-time code delivered to that phone.
* **Money-out is member-only** and **compliance runs before any payout**.
* **Signed webhooks** — every event is HMAC-signed (`X-Rach-Signature`) so you can trust its
  origin.

## Sandbox vs live

The **key prefix decides the environment**: a `rach_sk_test_` key runs in sandbox — every
B2B call returns an immediate `SANDBOX_SIMULATED` success without touching the blockchain,
moving your ledger, or changing the shared registry. A `rach_sk_live_` key executes for real
on Polygon mainnet.

<Tip>
  Build and test with `rach_sk_test_` (Sepolia, simulated), then switch to `rach_sk_live_` —
  same code, different key.
</Tip>

## Keys & webhooks

Generate your CaaS API keys and configure webhook endpoints from your **CaaS dashboard**
(see [Get your API keys](/guides/api-keys) and [Webhooks](/guides/webhooks)). Every B2B
endpoint is in the **CaaS API** reference tab.
