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

# Partner Integration Guide

> End-to-end technical reference for embedding QASH into your platform — user provisioning, KYC, login, financial operations, and card issuance.

This guide covers the complete integration lifecycle for the QASH Partner API: registering end-users, running identity verification, logging the user in, executing financial operations, and issuing a card — all from your own backend.

<Note>
  **Current scope**: partner integrations currently support users in Colombia (`countryCode: "CO"`), one asset (`USDC`), with amounts expressed in USD-equivalent terms. Other countries and assets are not yet available through the partner API.
</Note>

## Two credential types

The integration uses two distinct credentials, for two distinct phases:

```
End User → Your App → Your Backend → QASH Partner API
                             ↑
              Phase 1: X-Api-Key + X-Api-Secret
              Phase 2: Authorization: Bearer <user-access-token>
```

| Phase                            | Credential                                  | Used for                                                                                           |
| -------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Onboarding** (pre-login)       | `X-Api-Key` + `X-Api-Secret`                | Register users, manage profiles, run KYC — before the user has ever logged in                      |
| **Everything else** (post-login) | `Authorization: Bearer <user-access-token>` | Financial operations, card issuance, and card management — same token model as the QASH app itself |

The access token is obtained through a two-step, backend-only email OTP login (Steps 5–6 below). There is no client-side SDK to integrate — your backend calls two endpoints and receives a standard QASH JWT.

***

## 1. Prerequisites

Before you start you need:

* **Partner API credentials** — `X-Api-Key` + `X-Api-Secret` issued from the QASH Dashboard under **Settings → API Keys**. The secret is shown **once** at creation; store it securely in a secrets manager.
* **Server-side integration only** — credentials must never be exposed to client-side code. Every call must originate from your backend.

### Credential format

```http theme={null}
X-Api-Key:    qash_key_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Api-Secret: qash_secret_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

| Header         | Notes                                            |
| -------------- | ------------------------------------------------ |
| `X-Api-Key`    | Identifies your partner account                  |
| `X-Api-Secret` | Authenticates your request — never log or expose |

### Base URL

| Environment | URL                       |
| ----------- | ------------------------- |
| Production  | `https://api.qash.ai`     |
| Staging     | `https://staging.qash.ai` |

***

## 2. User lifecycle

Every end-user goes through the following states before they can log in and transact:

```
register → pending → kyc_required → [KYC approved] → active
```

| Status         | Meaning                                   | Can log in / transact? |
| -------------- | ----------------------------------------- | ---------------------- |
| `pending`      | Registered, KYC not yet initiated         | No                     |
| `kyc_required` | KYC initiated, awaiting user verification | No                     |
| `active`       | KYC approved — fully operational          | Yes                    |
| `suspended`    | Temporarily disabled                      | No                     |
| `banned`       | Permanently blocked                       | No                     |

Login is gated on `active` status — this is intentional. It's what forces registration, profile, and KYC to run under partner API-key credentials rather than a user session: there is no user session yet at that point.

### Onboarding and login sequence

```mermaid theme={null}
sequenceDiagram
    participant PB as Partner Backend
    participant Q as QASH
    participant EU as End User

    PB->>Q: §3 POST /api/v1/partner/users
    Q-->>PB: { id: "uuid", status: "pending" }

    PB->>Q: §4 POST /api/v1/user/profile (userId in body)
    Q-->>PB: { profile }

    PB->>Q: §5 GET /api/v1/user/profile/check-completion?userId=...
    Q-->>PB: { isComplete: true, missingFields: [] }

    PB->>Q: §6 POST /api/v1/partner/kyc (userId in body)
    Q-->>PB: { verificationUrl, inquiryId }

    PB->>EU: Redirect user to verificationUrl

    EU->>EU: Completes identity verification via Persona

    Note over Q,EU: Persona notifies QASH — status moves to active

    PB->>Q: §7 GET /api/v1/partner/users/:userId (poll until active)
    Q-->>PB: { status: "active" }

    PB->>Q: §8 POST /api/v1/partner/auth/send { email }
    Q-->>EU: 6-digit code by email

    PB->>Q: §8 POST /api/v1/partner/auth/verify { email, code }
    Q-->>PB: { accessToken, refreshToken }

    Note over PB,Q: From here on, every call uses Authorization: Bearer <accessToken>

    PB->>Q: §9.1 GET /api/v1/user/balance
    Q-->>PB: { items: [{ id: 649, asset: "USDC", credits: "0.00000000" }] }

    PB->>Q: §9.2 POST /api/v1/transactions/deposit
    Q-->>PB: { transactionId, status: "PENDING", paymentLink }

    PB->>Q: §10.1 POST /api/v1/auth/cards/setup
    Q-->>PB: { applicationId, status: "pending" }
```

Section numbers (`§N`) in the diagram match the numbered sections below.

***

## 3. Step 1 — Register a user

```http theme={null}
POST /api/v1/partner/users
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
```

```json theme={null}
{
  "email": "juan@example.com",
  "phone": "+573001234567",
  "countryCode": "CO"
}
```

### Request fields

| Field         | Type   | Required | Description                                           |
| ------------- | ------ | -------- | ----------------------------------------------------- |
| `email`       | string | Yes      | User email — must be unique within your partner scope |
| `phone`       | string | No       | E.164 format — e.g. `"+573001234567"`                 |
| `countryCode` | string | Yes      | Currently only `"CO"` is supported                    |

### Response — 201 Created

```json theme={null}
{
  "success": true,
  "data": {
    "user": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "email": "juan@example.com",
      "status": "pending",
      "createdAt": "2026-07-01T00:00:00.000Z"
    }
  },
  "message": "User created successfully"
}
```

**Store the `id` immediately.** This UUID is your permanent reference for the user across all subsequent calls until you have an access token.

### Errors

| Status | Error                                   | Cause                                          |
| ------ | --------------------------------------- | ---------------------------------------------- |
| `400`  | Validation message                      | Missing field or invalid format                |
| `409`  | `A user with this email already exists` | Email already registered in your partner scope |
| `401`  | `Invalid partner credentials`           | Wrong `X-Api-Key` or `X-Api-Secret`            |

***

## 4. Step 2 — Create the user profile

Before starting KYC, the user must have a personal profile. Persona uses this data to pre-fill the identity verification form — without it the KYC call will fail.

```http theme={null}
POST /api/v1/user/profile
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
```

```json theme={null}
{
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "firstName": "Juan",
  "lastName": "García",
  "dateOfBirth": "1990-05-15",
  "nationality": "CO",
  "gender": "male",
  "governmentIdType": "national_id",
  "governmentIdNumber": "1234567890",
  "governmentIdCountry": "CO",
  "addressLine1": "Calle 100 # 15-20",
  "city": "Bogotá",
  "stateProvince": "Bogotá D.C.",
  "postalCode": "110111",
  "addressCountry": "CO",
  "occupation": "engineer",
  "annualSalary": "50000-100000",
  "accountPurpose": "personal_savings",
  "expectedMonthlyVolume": "5000-10000",
  "isPep": false,
  "usCitizen": false,
  "acceptedTerms": true
}
```

See [Create user profile](/api-reference/users/create-profile) for the full field reference.

***

## 5. Step 3 — Verify profile completion

```http theme={null}
GET /api/v1/user/profile/check-completion?userId=a1b2c3d4-e5f6-7890-abcd-ef1234567890
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
```

```json theme={null}
{
  "success": true,
  "data": {
    "isComplete": true,
    "missingFields": []
  }
}
```

If `isComplete` is `false`, the `missingFields` array lists what's still needed. Use `PATCH /api/v1/user/profile` to fill in the gaps before proceeding to KYC.

```http theme={null}
PATCH /api/v1/user/profile
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
```

```json theme={null}
{
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "occupation": "engineer",
  "annualSalary": "50000-100000",
  "accountPurpose": "personal_savings",
  "expectedMonthlyVolume": "5000-10000"
}
```

***

## 6. Step 4 — Initiate KYC

Once the profile is complete, initiate identity verification.

```http theme={null}
POST /api/v1/partner/kyc
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
```

```json theme={null}
{
  "userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "firstName": "Juan",
  "lastName": "García",
  "email": "juan@example.com",
  "birthDate": "1990-05-15"
}
```

### Response — 200 OK

```json theme={null}
{
  "success": true,
  "data": {
    "inquiryId": "inq_xxxxxxxxxxxxxxxxxxxxxxxx",
    "verificationUrl": "https://inquiry.withpersona.com/verify?inquiry-id=inq_xxxxxxxxxxxxxxxxxxxxxxxx",
    "isExisting": false,
    "message": "KYC verification started. Please complete the verification process."
  }
}
```

Redirect your user to `verificationUrl`. The form is pre-filled with the profile data you provided. Once the user finishes, **QASH handles activation automatically** — no webhook configuration required on your side.

### Errors

| Status | Error                       | Cause                                                                 |
| ------ | --------------------------- | --------------------------------------------------------------------- |
| `404`  | `PersonalProfile not found` | Profile was not created (Step 2 missing)                              |
| `409`  | `KYC already in progress`   | Check status first with [KYC status](/api-reference/users/kyc-status) |

***

## 7. Step 5 — Poll for activation

Poll until `status` is `"active"`, using either endpoint:

```http theme={null}
GET /api/v1/partner/users/:userId
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
```

or check KYC status directly:

```http theme={null}
GET /api/v1/user/kyc/status?userId=a1b2c3d4-e5f6-7890-abcd-ef1234567890
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
```

<Tip>
  Poll with exponential backoff. Start at 10 seconds, back off to 60 seconds max. Identity verification typically completes within 1–5 minutes in production.
</Tip>

***

## 8. Step 6 — Log the user in

This is the last pre-login call, and the bridge into everything else. See [Send login code](/api-reference/users/send-otp) and [Verify login code](/api-reference/users/verify-otp) for full details.

```http theme={null}
POST /api/v1/partner/auth/send
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
```

```json theme={null}
{ "email": "juan@example.com" }
```

The user receives a 6-digit code by email. Once they provide it back to your app:

```http theme={null}
POST /api/v1/partner/auth/verify
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
```

```json theme={null}
{ "email": "juan@example.com", "code": "482913" }
```

```json theme={null}
{
  "success": true,
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "tokenType": "Bearer",
  "user": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "email": "juan@example.com", "status": "active" }
}
```

<Warning>
  Store `accessToken` and `refreshToken` in your backend on behalf of the user. From this point forward, **every** financial and card endpoint uses `Authorization: Bearer <accessToken>` — no `X-Api-Key`, `X-Api-Secret`, or `userId` needed. The token already identifies the user and your partner account.
</Warning>

***

## 9. Financial operations

All financial endpoints below use `Authorization: Bearer <accessToken>` only.

```http theme={null}
Authorization: Bearer <accessToken>
Content-Type: application/json
```

### Amount format

Ledger amounts (accounts, balance, deposit, transfer, transactions) are **decimal USDC values**, not smallest-unit integers — e.g. `100` means USDC 100.00. Never use floating point math when handling these values in your own backend; treat them as fixed-precision decimals.

`payout` and `exchange/calculate` deal with external currencies (e.g. COP) and follow the unit conventions documented on their own pages — see [Payout](/api-reference/financials/payout) and [Calculate exchange](/api-reference/financials/exchange-calculate).

***

### 9.1 Get balance

```http theme={null}
GET /api/v1/user/balance
```

```json theme={null}
{
  "items": [
    { "id": 649, "asset": "USDC", "type": "LIABILITY", "credits": "0.00000000", "debits": "0.00000000" }
  ],
  "meta": { "totalItems": 1, "totalPages": 1, "currentPage": 1, "itemsPerPage": 20 }
}
```

The user's USDC account is provisioned automatically — there is no separate "create account" call. Use the `id` from this response as `accountId` in deposits, transfers, and transaction queries. See [Get balance](/api-reference/financials/balance).

***

### 9.2 Deposit funds

```http theme={null}
POST /api/v1/transactions/deposit
```

```json theme={null}
{
  "toAccountId": 649,
  "amount": 100,
  "currency": "USDC",
  "provider": "mock",
  "description": "USDC deposit"
}
```

See [Deposit](/api-reference/account-management/deposit) for the full field reference and response shape.

***

### 9.3 Transfer funds

```http theme={null}
POST /api/v1/transactions/transfer
```

```json theme={null}
{
  "fromAccountId": 649,
  "toAccountId": 650,
  "amount": 50,
  "description": "Service payment"
}
```

See [Transfer](/api-reference/financials/transfer).

***

### 9.4 List / get transactions

```http theme={null}
GET /api/v1/transactions?accountId=649&limit=20
GET /api/v1/transactions/:id
```

See [List transactions](/api-reference/financials/transactions) and [Get transaction](/api-reference/financials/transaction-detail).

***

### 9.5 Payout to bank account

```http theme={null}
POST /api/v1/user/payout
```

See [Payout](/api-reference/financials/payout) for the full request body — destination bank fields, idempotency via `transactionId`, and response shape.

***

### 9.6 Exchange rate quote

```http theme={null}
POST /api/v1/user/exchange/calculate
```

See [Calculate exchange](/api-reference/financials/exchange-calculate) for request/response fields.

***

## 10. Card issuance and management

Once the user is logged in, issuing and managing their card uses the same access token — no separate credential.

### 10.1 Issue a card

```http theme={null}
POST /api/v1/auth/cards/setup
```

```json theme={null}
{
  "cardType": "virtual",
  "occupation": "engineer",
  "annualSalary": "50000-100000",
  "accountPurpose": "personal_savings",
  "expectedMonthlyVolume": "5000-10000"
}
```

This requires the user's KYC to be approved and their profile complete — both are already satisfied at this point in the flow. See [Issue a card](/api-reference/cards/setup) for the full prerequisites and error cases.

### 10.2 Card management

| Method  | Endpoint                     | Description              |
| ------- | ---------------------------- | ------------------------ |
| `GET`   | `/api/v1/cards/status`       | Current card status      |
| `GET`   | `/api/v1/cards/details`      | Masked card details      |
| `GET`   | `/api/v1/cards/balance`      | Card balance             |
| `GET`   | `/api/v1/cards/transactions` | Card transaction history |
| `POST`  | `/api/v1/cards/lock`         | Lock the card            |
| `POST`  | `/api/v1/cards/unlock`       | Unlock the card          |
| `PATCH` | `/api/v1/cards/limit`        | Update spending limit    |

See [Cards overview](/api-reference/cards/introduction) for details on each.

***

## 11. User management

### List your users

```http theme={null}
GET /api/v1/partner/users
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
```

| Query param | Type   | Description                                        |
| ----------- | ------ | -------------------------------------------------- |
| `status`    | string | `"pending"`, `"active"`, `"suspended"`, `"banned"` |
| `limit`     | number | 1–200, default 100                                 |
| `offset`    | number | Default 0                                          |

***

## 12. API key management

<Note>
  This is a separate credential from the end-user access token above — it authenticates **you as the partner business**, not an end user. Use it only to provision or revoke `X-Api-Key`/`X-Api-Secret` pairs from your own backend admin tooling.
</Note>

### Create a key pair

```http theme={null}
POST /api/v1/partner/api-keys
Authorization: Bearer <business-user-jwt>
Content-Type: application/json
```

```json theme={null}
{ "keyName": "Production key - July 2026" }
```

```json theme={null}
{
  "success": true,
  "apiKey": "qash_key_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "secretKey": "qash_secret_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
  "keyName": "Production key - July 2026"
}
```

<Warning>
  `secretKey` is returned **once only**. Store it immediately in a secrets manager.
</Warning>

### Revoke a key

```http theme={null}
DELETE /api/v1/partner/api-keys/:keyId
Authorization: Bearer <business-user-jwt>
```

***

## 13. Security checklist

* [ ] `X-Api-Secret` stored in a secrets manager — not in source code or environment files committed to git.
* [ ] All API calls originate from your backend — no direct browser-to-QASH calls.
* [ ] `accessToken`/`refreshToken` stored securely per user, not exposed to client-side code unless your integration is designed to hand sessions directly to a client app.
* [ ] `transactionId` included on every payout request, unique per payout.
* [ ] Your backend validates `user.status === "active"` before attempting login.
* [ ] API key rotation plan in place — revoke and reissue on any suspected compromise.
* [ ] Logs do not contain `X-Api-Secret`, `accessToken`, `refreshToken`, or raw API responses with sensitive fields.

***

## 14. Complete endpoint reference

### Endpoint groups by auth model

| Group                                                               | Auth                                        | Notes                                        |
| ------------------------------------------------------------------- | ------------------------------------------- | -------------------------------------------- |
| User management (`/partner/users`)                                  | `X-Api-Key` + `X-Api-Secret`                | Pre-login                                    |
| Profile (`/user/profile*`)                                          | `X-Api-Key` + `X-Api-Secret`                | `userId` in body (POST/PATCH) or query (GET) |
| KYC start (`/partner/kyc`)                                          | `X-Api-Key` + `X-Api-Secret`                | `userId` in body                             |
| KYC status (`/user/kyc/status`)                                     | `X-Api-Key` + `X-Api-Secret`                | `userId` in query — pre-login variant        |
| Login (`/partner/auth/send`, `/partner/auth/verify`)                | `X-Api-Key` + `X-Api-Secret`                | Issues the user access token                 |
| KYC document/history (`/auth/kyc/document/me`, `/auth/kyc/history`) | `Authorization: Bearer <accessToken>`       | Post-login variant, no `userId` needed       |
| Financial operations                                                | `Authorization: Bearer <accessToken>`       | Post-login                                   |
| Card issuance and management                                        | `Authorization: Bearer <accessToken>`       | Post-login                                   |
| API key management (`/partner/api-keys`)                            | `Authorization: Bearer <business-user-jwt>` | Your own business account, not an end user   |

### Full endpoint map

**User management** — `X-Api-Key` + `X-Api-Secret`

| Method | Path                            | Description                    |
| ------ | ------------------------------- | ------------------------------ |
| `POST` | `/api/v1/partner/users`         | Register a new user            |
| `GET`  | `/api/v1/partner/users`         | List your users                |
| `GET`  | `/api/v1/partner/users/:userId` | Get a specific user            |
| `POST` | `/api/v1/partner/kyc`           | Initiate identity verification |

**User profile and KYC status** — `X-Api-Key` + `X-Api-Secret` + `userId` in body/query

| Method  | Path                                    | Description                               |
| ------- | --------------------------------------- | ----------------------------------------- |
| `POST`  | `/api/v1/user/profile`                  | Create user profile — required before KYC |
| `GET`   | `/api/v1/user/profile`                  | Get user profile                          |
| `PATCH` | `/api/v1/user/profile`                  | Update profile fields                     |
| `GET`   | `/api/v1/user/profile/check-completion` | Check profile readiness for KYC           |
| `GET`   | `/api/v1/user/kyc/status`               | KYC verification status                   |

**Login** — `X-Api-Key` + `X-Api-Secret`

| Method | Path                          | Description                                   |
| ------ | ----------------------------- | --------------------------------------------- |
| `POST` | `/api/v1/partner/auth/send`   | Send a 6-digit login code by email            |
| `POST` | `/api/v1/partner/auth/verify` | Exchange the code for an access/refresh token |

**KYC document/history** — `Authorization: Bearer <accessToken>`

| Method | Path                           | Description                    |
| ------ | ------------------------------ | ------------------------------ |
| `GET`  | `/api/v1/auth/kyc/document/me` | Extracted KYC document details |
| `GET`  | `/api/v1/auth/kyc/history`     | Full KYC verification history  |

**Financial operations** — `Authorization: Bearer <accessToken>`

| Method | Path                              | Description                              |
| ------ | --------------------------------- | ---------------------------------------- |
| `GET`  | `/api/v1/user/balance`            | Get account balances                     |
| `GET`  | `/api/v1/transactions`            | List transactions (requires `accountId`) |
| `GET`  | `/api/v1/transactions/:id`        | Get a single transaction                 |
| `POST` | `/api/v1/transactions/deposit`    | Create a deposit payment link            |
| `POST` | `/api/v1/transactions/transfer`   | Transfer between accounts                |
| `POST` | `/api/v1/user/payout`             | Send funds to a bank account             |
| `POST` | `/api/v1/user/exchange/calculate` | Get an exchange rate quote               |

**Cards** — `Authorization: Bearer <accessToken>`

| Method  | Path                         | Description       |
| ------- | ---------------------------- | ----------------- |
| `POST`  | `/api/v1/auth/cards/setup`   | Issue a card      |
| `GET`   | `/api/v1/cards/status`       | Card status       |
| `GET`   | `/api/v1/cards/details`      | Card details      |
| `GET`   | `/api/v1/cards/balance`      | Card balance      |
| `GET`   | `/api/v1/cards/transactions` | Card transactions |
| `POST`  | `/api/v1/cards/lock`         | Lock card         |
| `POST`  | `/api/v1/cards/unlock`       | Unlock card       |
| `PATCH` | `/api/v1/cards/limit`        | Update card limit |

**API key management** — `Authorization: Bearer <business-user-jwt>`

| Method   | Path                           | Description         |
| -------- | ------------------------------ | ------------------- |
| `POST`   | `/api/v1/partner/api-keys`     | Create API key pair |
| `DELETE` | `/api/v1/partner/api-keys/:id` | Revoke API key      |
