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

# Create Card Customer

> Register a new card customer under your issuer program and trigger email OTP verification

<Note>
  **Issuer program required.** This endpoint is only available to users with an approved [Card Issuer Program](/api-reference/issuer/apply). Each customer gets a dedicated `yativo_card_id` used for all subsequent onboarding steps.
</Note>

<ParamField header="Authorization" type="string" required>
  Bearer token: `Bearer YOUR_ACCESS_TOKEN`
</ParamField>

<ParamField body="email" type="string" required>
  The customer's email address. Yativo sends a one-time verification code to this address. Must be unique per active customer card under your account — you cannot reuse your own issuer account email.
</ParamField>

<ParamField body="external_customer_id" type="string">
  Your own reference ID for this customer (e.g., a user ID from your platform). Stored as-is and returned on all customer records so you can correlate them on your side. No two active customer sessions may share the same `external_customer_id` under your account.
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST 'https://crypto-api.yativo.com/api/v1/yativo-card/customers/onboard' \
    -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
    -H 'Content-Type: application/json' \
    -d '{
      "email": "customer@example.com",
      "external_customer_id": "usr_8821"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    'https://crypto-api.yativo.com/api/v1/yativo-card/customers/onboard',
    {
      method: 'POST',
      headers: {
        Authorization: 'Bearer YOUR_ACCESS_TOKEN',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email: 'customer@example.com',
        external_customer_id: 'usr_8821',
      }),
    }
  );
  const data = await response.json();
  // data.data.yativo_card_id — save this for all subsequent calls
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "success": true,
    "message": "Customer card onboarding initiated. Verification code sent to customer email.",
    "data": {
      "yativo_card_id": "yativo_card_customer_8f9a2b3c4d5e6f7a8b9c0d1e_1769031332068",
      "customer_id": "6627f3a2c5d4e100123abcde",
      "external_customer_id": "usr_8821",
      "email_masked": "cu***@example.com",
      "next_step": "verify_otp",
      "otp_expires_in_seconds": 600
    }
  }
  ```

  ```json 400 Invalid email theme={null}
  {
    "success": false,
    "error_code": "INVALID_EMAIL",
    "message": "Valid customer email is required"
  }
  ```

  ```json 400 Issuer's own email used theme={null}
  {
    "success": false,
    "error_code": "ISSUER_EMAIL_NOT_ALLOWED",
    "message": "You cannot use your own account email as a customer email. Use a different email for this customer."
  }
  ```

  ```json 400 Email already in use by personal card theme={null}
  {
    "success": false,
    "error_code": "EMAIL_ALREADY_IN_USE",
    "message": "This email is already associated with your personal card. Each card must use a unique email address.",
    "data": {
      "account_type": "user",
      "flow_status": "card_created"
    }
  }
  ```

  ```json 409 Customer already exists for this email theme={null}
  {
    "success": false,
    "error_code": "CUSTOMER_CARD_ALREADY_ACTIVE",
    "message": "A customer card with this email already exists. Use the returned card ID to continue onboarding.",
    "data": {
      "yativo_card_id": "yativo_card_customer_8f9a2b3c4d5e6f7a8b9c0d1e_1769031332068",
      "flow_status": "otp_verified",
      "next_step": "kyc_link",
      "external_customer_id": "usr_8821"
    }
  }
  ```

  ```json 400 Duplicate external_customer_id theme={null}
  {
    "success": false,
    "error_code": "CUSTOMER_CARD_ALREADY_ACTIVE",
    "message": "Customer card onboarding already in progress",
    "data": {
      "yativo_card_id": "yativo_card_customer_8f9a2b3c4d5e6f7a8b9c0d1e_1769031332068",
      "flow_status": "otp_requested",
      "next_step": "verify_otp"
    }
  }
  ```

  ```json 409 Customer fully onboarded theme={null}
  {
    "success": false,
    "error_code": "CUSTOMER_ALREADY_ONBOARDED",
    "message": "This customer has already completed card onboarding. No further action is needed.",
    "data": {
      "yativo_card_id": "yativo_card_customer_8f9a2b3c4d5e6f7a8b9c0d1e_1769031332068",
      "flow_status": "active",
      "external_customer_id": "usr_8821",
      "next_step": "none"
    }
  }
  ```
</ResponseExample>

## Response Fields

| Field                    | Type           | Description                                                                                     |
| ------------------------ | -------------- | ----------------------------------------------------------------------------------------------- |
| `yativo_card_id`         | string         | Unique customer card identifier. **Save this** — required for every subsequent onboarding call. |
| `customer_id`            | string         | Internal Yativo database ID for this customer record.                                           |
| `external_customer_id`   | string \| null | Your reference ID, echoed back unchanged.                                                       |
| `email_masked`           | string         | Partially masked version of the customer's email for display purposes.                          |
| `next_step`              | string         | Always `"verify_otp"` on creation.                                                              |
| `otp_expires_in_seconds` | number         | How long the emailed code is valid (600 = 10 minutes).                                          |

## Idempotency

This endpoint is safe to retry. If you call it again with the same `email` or `external_customer_id` while a session is already active, the API returns the existing card rather than creating a duplicate.

### Handling `CUSTOMER_CARD_ALREADY_ACTIVE`

When you receive this error code, treat it as a resume signal — not a failure. The customer's card was already created and their OTP was already sent. Read `next_step` from `data` and continue the onboarding flow from there:

| `next_step`  | What to do next                                                      |
| ------------ | -------------------------------------------------------------------- |
| `verify_otp` | OTP has been sent — call `POST /customers/{yativoCardId}/verify-otp` |
| `kyc_link`   | OTP verified — call `GET /customers/{yativoCardId}/kyc-link`         |
| `continue`   | Resume from the current `flow_status`                                |
| `none`       | Customer is fully onboarded — no action needed                       |

If the customer has already completed onboarding (`flow_status` is `active`, `safe_deployed`, or `kyc_approved`), the API returns `409 CUSTOMER_ALREADY_ONBOARDED` with `next_step: "none"` — no further action is needed for this customer.

## Next Steps

After a `201` response, direct your customer to check their email and proceed:

1. **Verify OTP** — `POST /v1/yativo-card/customers/{yativoCardId}/verify-otp`
2. **Get KYC link** — `GET /v1/yativo-card/customers/{yativoCardId}/kyc-link`
3. **Poll KYC status** — `GET /v1/yativo-card/customers/{yativoCardId}/kyc-status`
4. **Submit source of funds** — `POST /v1/yativo-card/customers/{yativoCardId}/source-of-funds`
5. **Create virtual card** — `POST /v1/yativo-card/customers/{yativoCardId}/cards/virtual`
