# Create a Bank Ramp Customer

> Coming soon, upon request — this feature is turned on for your tenant by the Aurea team. [Contact us](https://www.aureahub.com/contact)

Make the user a customer of the bank ramp in one environment — a person or a company — before your app sends their KYC data.

## Overview

The customer routes are available on request [Coming soon, upon request](https://docs.aureahub.com/docs/feature-availability.md) ([Bank Ramp: Setup & Customers](https://docs.aureahub.com/docs/guide-bank-customers.md)). This is their first call when your app sends the KYC data itself.

- **Your tenant's KYC must be on** in the environment, otherwise `403` `BANK_RAMP_FUNCTION_OFF`, and its KYC strategy must be one where your app sends the data: `reliance` or `identity_sharing`. With `hosted_oauth` the user becomes a customer on the bank ramp's own pages ([Start Hosted Onboarding](https://docs.aureahub.com/docs/bank-customer-authorize.md)) and this call answers `403` `BANK_RAMP_KYC_STRATEGY_OFF`.
- **`kind`** is `personal` or `corporate`, and must be one your tenant offers: otherwise `400` `BANK_RAMP_PROFILE_KIND_UNAVAILABLE` with `details.available`. A customer's kind never changes: asking again with the other kind answers `409` `BANK_RAMP_CUSTOMER_KIND_MISMATCH`, with the customer's own in `details.kind`.
- **One customer per user and environment.** `201` when the bank ramp confirmed the customer in this call; `200` with the customer the user already has — the bank ramp is not asked again.
- **Safe to repeat.** The customer's `profileId` is made by Aurea and kept before the bank ramp is asked. A call that stopped half way — the bank ramp unavailable, its answer lost — leaves the customer with `registered: false`; calling again, or [Sync the Customer](https://docs.aureahub.com/docs/bank-customer-sync.md), finishes it with the same id. Two calls at once make one customer.
- Without your tenant's bank ramp connected in that environment: `503` `BANK_RAMP_NOT_CONFIGURED`, and nothing is kept.
- The bank ramp's own refusals answer with `"error": "BankRampProviderError"` and a `BANK_RAMP_*` code in `details.code`: `403` `BANK_RAMP_PARTNER_REQUIRED` (your tenant's bank ramp application may not create customers yet: ask Aurea), `400` `BANK_RAMP_INVALID_REQUEST`, `503` while it is unavailable. The customer is kept, to be finished by a later call.
- **Retries.** Send an `Idempotency-Key` header to make a retry safe: the same key with the same body answers with the first answer, and the response header `idempotency-replayed` is `true` ([Idempotency](https://docs.aureahub.com/docs/idempotency.md)).
- Called with the user's access token. Answers `403` to a token without a tenant.

## Endpoint

### `POST /v1/ramp/bank/customer`

Authentication: bearer token required.

Makes the user a customer of the bank ramp in one environment, or answers the one there is.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | string | yes | `personal` or `corporate` |
| `isTestnet` | boolean | no | `true` for the sandbox; `false` or omitted for production |

**Responses**

`201` Created

```json
{
  "environment": "sandbox",
  "customer": {
    "customerId": "2f6c1e84-5b3a-4d97-8e21-0c4a7b9d3f15",
    "profileId": "8d4e2a17-6c3b-4f58-9a0e-1b7c5d3e2f49",
    "kind": "personal",
    "environment": "sandbox",
    "registered": true,
    "state": "created",
    "sections": { "details": null, "form": null, "verifications": [] },
    "closedReason": null,
    "errorFields": [],
    "createdAt": "2026-10-14T09:00:00.000Z",
    "updatedAt": "2026-10-14T09:00:01.000Z"
  },
  "verification": {
    "state": "created",
    "nextStep": "continue_onboarding",
    "missing": ["details", "form", "verifications"],
    "kycStrategy": "reliance"
  }
}
```

`403` KYC off

```json
{
  "statusCode": 403,
  "error": "ForbiddenError",
  "message": "The bank ramp's KYC is not switched on for this tenant in sandbox.",
  "details": { "code": "BANK_RAMP_FUNCTION_OFF", "function": "kyc", "environment": "sandbox" }
}
```

`403` Hosted pages strategy

```json
{
  "statusCode": 403,
  "error": "ForbiddenError",
  "message": "This tenant's bank ramp KYC in sandbox does not go this way.",
  "details": { "code": "BANK_RAMP_KYC_STRATEGY_OFF", "environment": "sandbox", "kycStrategy": "hosted_oauth" }
}
```

`400` Kind not offered

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "A corporate profile is not available with the bank ramp in sandbox for this tenant. Available: personal.",
  "details": {
    "code": "BANK_RAMP_PROFILE_KIND_UNAVAILABLE",
    "environment": "sandbox",
    "profileKind": "corporate",
    "available": ["personal"]
  }
}
```

`409` The other kind

```json
{
  "statusCode": 409,
  "error": "ConflictError",
  "message": "This user is a personal customer of the bank ramp in sandbox: a profile's kind does not change.",
  "details": { "code": "BANK_RAMP_CUSTOMER_KIND_MISMATCH", "environment": "sandbox", "kind": "personal" }
}
```

`503` Not connected

```json
{
  "statusCode": 503,
  "error": "BankRampProviderError",
  "message": "The bank ramp is not configured for this tenant in sandbox",
  "details": { "code": "BANK_RAMP_NOT_CONFIGURED" }
}
```

## Implementation

```javascript
// idempotencyKey: made once for this request (e.g. crypto.randomUUID()) and sent again on every retry
async function createCustomer(token, kind, isTestnet, idempotencyKey) {
  const res = await fetch('https://api.aureahub.com/v1/ramp/bank/customer', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${token}`,
      'Idempotency-Key': idempotencyKey
    },
    body: JSON.stringify({ kind, isTestnet })
  });
  const data = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${data.details?.code ?? ''}: ${data.message}`);

  // continue_onboarding: send what verification.missing names
  return data.verification;
}
```

---

Web version: https://docs.aureahub.com/#bank-customer-create
