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

# White-label quickstart

> On staging: create a customer account, verify it, issue bank details, link a bank and send a payout.

Eight steps on staging take one customer from nothing to a payout to their own bank. Each step shows the call, the part of the response that matters, and what to check before moving on.

```bash theme={null}
export APP_ID="app_..."
export APP_SECRET="..."
export API="https://staging-api-v2.stableyard.fi"
```

<Warning>
  **There is no simulator.** Staging has no endpoint that approves a verification, fakes a bank deposit or settles a payout. Steps 3 to 5 need a real person to complete hosted pages, which run against provider sandboxes where one exists. Steps 4 to 8 need products Stableyard has enabled for your app.
</Warning>

## What each step needs

| Step | Needs before you start |
| - | - |
| 1–2 | Staging credentials |
| 3–4 | The Identity & KYC product, and an email inbox the customer can read |
| 5 | Your organization's KYB approved, and the bank rail enabled for your app |
| 6 | An active `bank_onramp` capability on the account |
| 7 | Approved identity verification, and a linked-bank route enabled for your app |
| 8 | An active linked bank, an active `linked_bank` capability, and a wallet that holds the stablecoin and can sign |

## Eight steps

<Steps>
  <Step title="Read your configuration">
    The first call in every environment. It proves the credential and reports what this app can do.

    ```bash theme={null}
    curl "$API/v2/partners/config" -u "$APP_ID:$APP_SECRET"
    ```

    ```json theme={null}
    {
      "status": "active",
      "compliance": {
        "partnerKyb": { "status": "approved" },
        "requirements": { "partnerFiatAccess": "kyb", "individualUpaFiatAccess": "kyc", "businessUpaFiatAccess": "kyb" }
      },
      "modules": [{ "id": "identity_kyc", "available": true }],
      "capabilities": {
        "accounts": { "emailVerification": { "mode": "stableyard_email_otp", "partnerAssertionAllowed": false } }
      }
    }
    ```

    **Before moving on:** `compliance.partnerKyb.status` is `approved` and the `identity_kyc` module reports `available: true`. If either is not, stop and ask Stableyard; nothing after step 2 works without them.
  </Step>

  <Step title="Create a customer account">
    One account per customer, keyed by your own user id. Replace `42161` with a chain from `networks` in your configuration; staging uses test networks.

    ```bash theme={null}
    curl -X POST "$API/v2/accounts" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Idempotency-Key: account:user_123" \
      -H "Content-Type: application/json" \
      -d '{
        "externalUserId": "user_123",
        "subjectType": "individual",
        "wallets": [
          { "chainId": 42161, "address": "0x1111111111111111111111111111111111111111", "label": "Primary wallet" }
        ]
      }'
    ```

    ```json theme={null}
    {
      "account": { "id": "acct_123", "externalUserId": "user_123", "subjectType": "individual", "status": "active" },
      "connectedWallets": [{ "id": "wallet_123", "chainId": 42161, "ownershipVerificationStatus": "unverified" }],
      "settlementProfile": { "id": "settle_123", "connectedWalletId": "wallet_123", "status": "draft" }
    }
    ```

    Keep `account.id` and the wallet's `id`. `subjectType` can never be changed later. See [Universal Payment Account](/universal-payment-account).
  </Step>

  <Step title="Verify the customer's email">
    Stableyard emails a six-digit code. Your screen collects it and your backend confirms it.

    ```bash theme={null}
    curl -X POST "$API/v2/accounts/acct_123/email/verification-challenges" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Idempotency-Key: email:acct_123:v1" \
      -H "Content-Type: application/json" \
      -d '{ "email": "jane@example.com" }'
    ```

    The response carries `challenge.id`, `challenge.expiresAt` (ten minutes) and `nextAction.type: "enter_email_verification_code"`. Then:

    ```bash theme={null}
    curl -X POST "$API/v2/accounts/acct_123/email/verification-challenges/account_email_123/confirm" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Content-Type: application/json" \
      -d '{ "code": "482913" }'
    ```

    **Before moving on:** `contact.status` is `verified` and `nextAction.type` is `start_kyc_session`. If your configuration reports `emailVerification.mode: "partner_asserted"`, you assert the email instead; see [Email verification](/concepts/email-verification#assert-an-email-you-already-verified).
  </Step>

  <Step title="Start identity verification">
    No body. The account path fixes who is being verified.

    ```bash theme={null}
    curl -X POST "$API/v2/accounts/acct_123/kyc/session" -u "$APP_ID:$APP_SECRET"
    ```

    ```json theme={null}
    {
      "id": "kyc_123",
      "status": "pending",
      "verificationUrl": "https://…",
      "eligibility": { "status": "not_eligible" },
      "nextAction": { "type": "complete_verification" }
    }
    ```

    Open `verificationUrl` for the customer. It is hosted by the verification provider and takes no return URL, so your screen waits: poll `GET /v2/accounts/acct_123/kyc`, or act on the `kyc.updated` webhook.

    **Before moving on:** `current.eligibility.status` is `eligible`. States and next actions are on [Individual KYC](/concepts/individual-kyc).
  </Step>

  <Step title="Activate the bank capabilities">
    Each regulated capability is activated per account. Start with `bank_onramp`.

    ```bash theme={null}
    curl -X POST "$API/v2/accounts/acct_123/capability-activations" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Idempotency-Key: activate:acct_123:bank_onramp:v1" \
      -H "Content-Type: application/json" \
      -d '{ "capability": "bank_onramp", "country": "US", "currency": "USD", "rail": "ach" }'
    ```

    If `capability.nextAction.type` is `complete_compliance`, open `nextAction.url` for the customer before `expiresAt`. It is a one-time Stableyard-hosted form. Then wait for `capability.ready: true` from `GET /v2/accounts/acct_123/capabilities`, or the `compliance.approved` webhook.

    Repeat with `"capability": "linked_bank"` and a new `Idempotency-Key`; step 8 needs it. It can return its own `complete_compliance` action. See [Capability activation](/concepts/capability-activation).
  </Step>

  <Step title="Issue an on-ramp account and read its deposit instructions">
    Ask whether it can be issued, then issue it, then read the full bank details.

    ```bash theme={null}
    curl "$API/v2/accounts/acct_123/onramp-bank-account-requirements" -u "$APP_ID:$APP_SECRET"
    ```

    Proceed only when `available` is `true`, and take `connectedWalletId` and the asset from `destinations`.

    ```bash theme={null}
    curl -X POST "$API/v2/accounts/acct_123/onramp-bank-accounts" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Idempotency-Key: onramp:acct_123:v1" \
      -H "Content-Type: application/json" \
      -d '{
        "connectedWalletId": "wallet_123",
        "destinationAssetCode": "USDC",
        "rail": "ach",
        "recipientType": "individual",
        "recipientName": "Jane Customer",
        "recipientAddress": { "street1": "100 Example Street", "city": "Denver", "region": "CO", "postalCode": "80202", "country": "US" }
      }'
    ```

    Once `status` is `active`:

    ```bash theme={null}
    curl "$API/v2/accounts/acct_123/onramp-bank-accounts/onramp_acct_123/deposit-instructions" \
      -u "$APP_ID:$APP_SECRET"
    ```

    The response carries `beneficiary.name`, `bank.name`, `bank.abaRoutingNumber` and `bank.accountNumber`. Show them to the customer; never log or store them. See [On-ramp accounts](/concepts/on-ramp-accounts).
  </Step>

  <Step title="Link the customer's bank account">
    Read the fields the country needs, then link it.

    ```bash theme={null}
    curl "$API/v2/accounts/acct_123/bank-account-requirements" -u "$APP_ID:$APP_SECRET"
    ```

    ```bash theme={null}
    curl -X POST "$API/v2/accounts/acct_123/bank-accounts" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Idempotency-Key: bank:acct_123:v1" \
      -H "Content-Type: application/json" \
      -d '{
        "country": "US",
        "currency": "USD",
        "accountHolderName": "Jane Customer",
        "beneficiaryType": "individual",
        "bankName": "Example Bank",
        "accountNumber": "000123456789",
        "accountType": "checking",
        "routing": { "type": "aba", "routingNumber": "021000021" },
        "rail": "ach",
        "beneficiaryAddress": { "street1": "100 Example Street", "city": "Denver", "region": "CO", "postalCode": "80202", "country": "US" },
        "bankAddress": { "street1": "270 Park Avenue", "city": "New York", "region": "NY", "postalCode": "10017", "country": "US" }
      }'
    ```

    The response carries `bankAccount.id` and `bankAccount.status`. If `provisioning.nextAction.type` is `complete_provider_onboarding`, open its `url` for the customer.

    **Before moving on:** `GET /v2/accounts/acct_123/bank-accounts/{bankAccountId}` reports `status: "active"`. See [Bank accounts](/concepts/bank-accounts).
  </Step>

  <Step title="Send a payout to the linked bank">
    Name the wallet that funds it, then create the payout.

    ```bash theme={null}
    curl -X PUT "$API/v2/accounts/acct_123/payment-source" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Content-Type: application/json" \
      -d '{ "type": "connected_wallet", "connectedWalletId": "wallet_123", "assetSymbol": "USDC" }'
    ```

    ```bash theme={null}
    curl -X POST "$API/v2/payments" \
      -u "$APP_ID:$APP_SECRET" \
      -H "Idempotency-Key: payout:withdrawal_485" \
      -H "Content-Type: application/json" \
      -d '{
        "intent": "send",
        "sender": { "accountId": "acct_123" },
        "destination": { "type": "bank_account", "bankAccountId": "bank_account_123" },
        "amountMode": "collect_exact",
        "paymentAmount": { "amount": "100.00", "assetType": "crypto", "assetCode": "USDC" },
        "externalReference": "withdrawal_485"
      }'
    ```

    The response carries `funding` (the quote) and `nextAction`. When `nextAction.type` is `transaction` with `depositInstructions`, the wallet sends exactly that amount to that address before `funding.quoteExpiresAt`. Then read `GET /v2/payments/{paymentId}` until `status` is terminal.

    US payouts to a linked bank are certified on staging. Whether a route takes `collect_exact` with a crypto amount or `deliver_exact` with a USD amount depends on the route; see [Sending payments](/payments/sending-payments#create-the-payment).
  </Step>
</Steps>

## What you proved

One account, verified in order, with bank details that convert to stablecoin and a payout that reached a bank. Production repeats every step with new credentials and new accounts: nothing carries over. See [Going live](/white-label/going-live).

<Card title="Next: Branding and your frontend" icon="palette" href="/white-label/branding-and-frontend">
  What your customers see at each of these steps, and what you can change.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.