Skip to main content
The Customers API is available to select partners during onboarding, and currently supports KYC of US individuals only. If you’re interested in using this product, get in touch and our team will follow up to discuss fit.
Not yet onboarded for Live? Try the Customers API in Sandbox. Live access is limited to onboarded partners; Sandbox is available to everyone.
This guide walks the full customer lifecycle in Sandbox using the CDP CLI. You will:
  1. Create an individual customer and request custody and transfer capabilities.
  2. Create an entity-owned account and a customer-owned account.
  3. Fund the entity account with USDC and transfer some to the customer.
  4. Try to convert the customer’s USDC to USD — and watch it fail because the customer is missing a capability.
  5. Request the missing capability and retry the conversion successfully.
Base URL (Sandbox): https://sandbox.cdp.coinbase.com/platform/v2 (for cdp api and cdp env)

Prerequisites

  • A CDP login with access to the CDP Portal
  • Node.js 22 or later
  • A Sandbox Secret API Key JSON from the Portal (Sandbox environment)
Never commit API keys to source control. Store them in environment variables or a secrets manager. Do not use real personal data in Sandbox.

Install and configure the CDP CLI

Requires Node.js 22 or later.
npm install -g @coinbase/cdp-cli@latest --force
cdp --version
Configure a Sandbox environment from your downloaded key file. Point it at the Sandbox platform base so cdp api paths resolve correctly:
cdp env sandbox --key-file ./cdp-api-key.json --url https://sandbox.cdp.coinbase.com/platform/v2
Every command below passes -e sandbox to target that environment. For CLI basics — field syntax, --jq, --dry-run — see CDP for Agents.

1. Create a KYC’d customer

Create an individual customer, submit their identity information, record Terms of Service and tax attestations, and request the custody and transfer capabilities they need. Note that tradeStablecoin is not requested yet — you add it later in step 7. In Sandbox, the fullSsn value 000-00-0000 is a magic value that forces a deterministic approval, so the requested capabilities verify and become active without real PII.
CUSTOMER_ID=$(cdp api -X POST /platform/v2/customers -e sandbox --jq=.customerId - <<'JSON'
{
  "type": "individual",
  "individual": {
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phoneNumber": "+16175551212",
    "fullSsn": "000-00-0000",
    "dateOfBirth": { "day": "1", "month": "1", "year": "1987" },
    "address": {
      "line1": "500 Main St",
      "city": "Boston",
      "state": "MA",
      "postCode": "02108",
      "countryCode": "US"
    },
    "purposeOfAccount": "investing",
    "sourceOfFunds": "salary"
  },
  "tosAcceptances": [
    { "versionId": "us_individual_2026-05-29", "language": "en", "acceptedAt": "2026-04-17T20:00:00Z" }
  ],
  "taxAttestations": [
    { "form": "us_w9", "isExemptBackupWithholding": true, "edeliveryConsent": true, "acceptedAt": "2026-04-17T20:00:00Z" }
  ],
  "compliance": { "requesterIpAddress": "203.0.113.10" },
  "capabilities": {
    "custodyCrypto": { "requested": true },
    "custodyFiat": { "requested": true },
    "custodyStablecoin": { "requested": true },
    "transferCrypto": { "requested": true },
    "transferFiat": { "requested": true },
    "transferStablecoin": { "requested": true }
  }
}
JSON
)
echo "CUSTOMER_ID=$CUSTOMER_ID"
The request body carries the full baseline that every capability requires: identity fields, Terms of Service acceptance, a W-9 tax attestation, and the end-user’s IP address on compliance.requesterIpAddress. See Requirements for the field-to-capability mapping.
Verification is asynchronous. With the approval magic value, the requested capabilities settle to active quickly, but in production you should subscribe to the customers.capability.changed webhook rather than poll.

2. Confirm the customer’s capabilities are active

cdp api /platform/v2/customers/$CUSTOMER_ID -e sandbox
{
  "customerId": "customer_af2937b0-9846-4fe7-bfe9-ccc22d935114",
  "type": "individual",
  "individual": {
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com"
  },
  "capabilities": {
    "custodyCrypto": { "requested": true, "status": "active" },
    "custodyFiat": { "requested": true, "status": "active" },
    "custodyStablecoin": { "requested": true, "status": "active" },
    "tradeCrypto": { "requested": false, "status": "unrequested" },
    "tradeStablecoin": { "requested": false, "status": "unrequested" },
    "transferCrypto": { "requested": true, "status": "active" },
    "transferFiat": { "requested": true, "status": "active" },
    "transferStablecoin": { "requested": true, "status": "active" }
  },
  "createdAt": "2026-04-17T20:00:00Z",
  "updatedAt": "2026-04-17T20:00:05Z"
}
All six requested capabilities are active. tradeStablecoin is still unrequested — that’s intentional.

3. Create an entity account

Create an account owned by your entity (your own treasury). Omitting owner makes the account entity-owned.
ENTITY_ACCOUNT_ID=$(cdp api -X POST /platform/v2/accounts -e sandbox \
  name="Treasury account" --jq=.accountId)
echo "ENTITY_ACCOUNT_ID=$ENTITY_ACCOUNT_ID"
{
  "accountId": "account_af2937b0-9846-4fe7-bfe9-ccc22d935114",
  "type": "cdp",
  "owner": "entity_af2937b0-9846-4fe7-bfe9-ccc22d935114",
  "name": "Treasury account",
  "createdAt": "2026-04-17T20:01:00Z",
  "updatedAt": "2026-04-17T20:01:00Z"
}

4. Create the customer’s account

Pass the customer ID as owner to create a customer-owned account. This requires the customer to hold the custodyCrypto capability, which Jane does. Customer-facing operations also require the end-user’s IP on compliance.requesterIpAddress.
CUSTOMER_ACCOUNT_ID=$(cdp api -X POST /platform/v2/accounts -e sandbox \
  owner=$CUSTOMER_ID name="Jane Doe account" \
  compliance.requesterIpAddress=8.8.8.8 \
  --jq=.accountId)
echo "CUSTOMER_ACCOUNT_ID=$CUSTOMER_ACCOUNT_ID"
{
  "accountId": "account_b1748c92-12ef-4a0b-9d3e-77a1e5f0c233",
  "type": "cdp",
  "owner": "customer_af2937b0-9846-4fe7-bfe9-ccc22d935114",
  "name": "Jane Doe account",
  "createdAt": "2026-04-17T20:02:00Z",
  "updatedAt": "2026-04-17T20:02:00Z"
}
If the customer is missing a required capability (for example custodyCrypto), this call returns 403 with errorType: customer_not_authorized. Resolve the customer’s requirements before retrying.

5. Fund the entity account

Sandbox does not move real funds. Seed a USDC balance on the entity account with the Sandbox-only funding helper. This endpoint lives at the host root (/fake/balances), not under /platform/v2.
cdp api -X POST /fake/balances -e sandbox - <<JSON
{
  "account_id": "$ENTITY_ACCOUNT_ID",
  "balances": [
    {
      "asset": { "symbol": "usdc", "type": "crypto", "name": "USD Coin", "decimals": 6 },
      "availableAmount": "10.00",
      "totalAmount": "10.00"
    }
  ]
}
JSON
/fake/balances is a Sandbox-only helper for seeding test balances. It does not exist in production, where accounts are funded by real deposits.
Confirm the balance landed:
cdp api /platform/v2/accounts/$ENTITY_ACCOUNT_ID/balances -e sandbox
{
  "balances": [
    {
      "asset": { "symbol": "usdc", "type": "crypto", "name": "USD Coin", "decimals": 6 },
      "amount": {
        "usdc": { "available": "10.00", "total": "10.00" }
      }
    }
  ]
}

6. Transfer USDC to the customer

Move USDC from the entity account to the customer’s account. Because USDC is a stablecoin, the receiving customer must hold the custodyStablecoin capability — Jane does.
cdp api -X POST /platform/v2/transfers -e sandbox \
  source.accountId=$ENTITY_ACCOUNT_ID source.asset=usdc \
  target.accountId=$CUSTOMER_ACCOUNT_ID target.asset=usdc \
  amount=5.00 asset=usdc \
  execute:=true \
  compliance.requesterIpAddress=203.0.113.10
{
  "transferId": "transfer_8a2c0e7f-194d-4e9a-b5f6-d8a2c0e7f194",
  "status": "processing",
  "source": { "accountId": "account_af2937b0-9846-4fe7-bfe9-ccc22d935114", "asset": "usdc" },
  "target": { "accountId": "account_b1748c92-12ef-4a0b-9d3e-77a1e5f0c233", "asset": "usdc" },
  "sourceAmount": "5.00",
  "sourceAsset": "usdc",
  "targetAmount": "5.00",
  "targetAsset": "usdc",
  "createdAt": "2026-04-17T20:03:00Z",
  "updatedAt": "2026-04-17T20:03:00Z"
}
Confirm the customer account now holds USDC:
cdp api /platform/v2/accounts/$CUSTOMER_ACCOUNT_ID/balances -e sandbox

7. Convert USDC to USD (this fails)

Now convert the customer’s USDC to USD. A conversion is a transfer where source and target are the same account but different assets. Set amountType to target so the customer receives an exact USD amount.
cdp api -X POST /platform/v2/transfers -e sandbox \
  source.accountId=$CUSTOMER_ACCOUNT_ID source.asset=usdc \
  target.accountId=$CUSTOMER_ACCOUNT_ID target.asset=usd \
  amount=2.00 asset=usd amountType=target \
  execute:=true \
  compliance.requesterIpAddress=203.0.113.10
This returns 403 Forbidden:
error: customer_not_authorized — Customer is not authorized for one or more capabilities required by this action. (HTTP 403) [unauthorized capabilities: tradeStablecoin]

Capabilities required to convert USDC to USD

A conversion changes an asset’s denomination within a single account, so it gates on the customer holding both of these:
CapabilityWhy it’s required
tradeStablecoinPerforms the trade between a stablecoin (USDC) and another asset
custodyFiatHolds the resulting USD
A conversion needs no transfer* capability — the funds never leave the account. (The reverse direction, USD to USDC, would instead require tradeStablecoin + custodyStablecoin.) Jane already holds custodyFiat, so the only missing capability is tradeStablecoin — exactly what unauthorizedCapabilities reports.
Always read unauthorizedCapabilities to learn precisely which capabilities the customer is missing for an operation, then request them. The error message itself is intentionally generic — never surface compliance-specific reasons to your end-users.

8. Request the missing capability

Add tradeStablecoin with an update. The baseline requirements were already satisfied in step 1, so in Sandbox the capability verifies and becomes active.
cdp api -X PUT /platform/v2/customers/$CUSTOMER_ID -e sandbox - <<'JSON'
{
  "capabilities": { "tradeStablecoin": { "requested": true } },
  "compliance": { "requesterIpAddress": "203.0.113.10" }
}
JSON
Confirm tradeStablecoin is now active:
cdp api /platform/v2/customers/$CUSTOMER_ID -e sandbox --jq=.capabilities.tradeStablecoin
{ "requested": true, "status": "active" }
If tradeStablecoin comes back pending instead of active, inspect the customer’s requirements map for outstanding items and resolve them. See Requirements.

9. Retry the conversion

With tradeStablecoin active, run the same conversion again — it now succeeds.
cdp api -X POST /platform/v2/transfers -e sandbox \
  source.accountId=$CUSTOMER_ACCOUNT_ID source.asset=usdc \
  target.accountId=$CUSTOMER_ACCOUNT_ID target.asset=usd \
  amount=2.00 asset=usd amountType=target \
  execute:=true \
  compliance.requesterIpAddress=203.0.113.10
{
  "transferId": "transfer_5d747be1-60e2-4805-04c0-99d984bcfe01",
  "status": "processing",
  "source": { "accountId": "account_b1748c92-12ef-4a0b-9d3e-77a1e5f0c233", "asset": "usdc" },
  "target": { "accountId": "account_b1748c92-12ef-4a0b-9d3e-77a1e5f0c233", "asset": "usd" },
  "sourceAmount": "2.00",
  "sourceAsset": "usdc",
  "targetAmount": "2.00",
  "targetAsset": "usd",
  "exchangeRate": { "sourceAsset": "usdc", "targetAsset": "usd", "rate": "1" },
  "createdAt": "2026-04-17T20:05:00Z",
  "updatedAt": "2026-04-17T20:05:00Z"
}
You’ve onboarded a customer, provisioned accounts, moved funds, and learned the core gating pattern: check the capability, resolve what’s missing, then retry.

Move to production

To run this flow for real, switch the Sandbox base URL to the production base URL and use a production API key. In production there are no magic values and no /fake/balances helper — customers complete real KYC, and accounts are funded by real deposits.

Capabilities

The full capability set, statuses, and how to gate operations

Requirements

Resolve outstanding requirements, Terms of Service, and tax attestations

Customers

The customer record, identity fields, and the verification lifecycle

Customers API reference

REST reference for customer, account, and transfer endpoints