This guide covers the complete integration lifecycle for the Qash Partner API: registering end-users, running identity verification, issuing user JWTs, and executing financial operations — all from your own backend using your API credentials.
End User → Your App → Your Backend → Qash Partner API
↑
X-Api-Key + X-Api-Secret
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.
X-Api-Key: qash_key_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Api-Secret: qash_secret_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
| 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 |
| Sandbox | https://sandbox.api.qash.ai |
2. User lifecycle
Every end-user goes through the following states before they can transact:
register → pending → [KYC initiated] → [KYC approved] → active
| Status | Meaning | Can transact? |
|---|
pending | Registered but KYC not yet approved | No |
active | KYC approved — fully operational | Yes |
suspended | Temporarily disabled | No |
banned | Permanently blocked | No |
Onboarding sequence
3. Step 1 — Register a user
POST /api/v1/partner/users
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
{
"userType": "personal",
"email": "juan@example.com",
"phone": "+5215512345678",
"countryCode": "MX",
"preferredLanguage": "es"
}
Request fields
| Field | Type | Required | Description |
|---|
userType | string | Yes | "personal" or "business" |
email | string | Yes | User email — must be unique within your partner scope |
phone | string | No | E.164 format — e.g. "+5215512345678" |
countryCode | string | Yes | ISO 3166-1 alpha-2 — e.g. "MX", "CO", "US" |
preferredLanguage | string | No | "es" (default) or "en" |
Response — 201 Created
{
"success": true,
"data": {
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "juan@example.com",
"status": "pending",
"createdAt": "2026-06-12T00:00:00.000Z"
}
},
"message": "User created successfully"
}
Store the id immediately. This UUID is your permanent reference for the user across all subsequent calls.
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 — Initiate KYC
Once the user is registered, initiate identity verification. The user must complete this before they can transact.
POST /api/v1/partner/kyc
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
{
"userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"firstName": "Juan",
"lastName": "García",
"email": "juan@example.com",
"phone": "+5215512345678",
"birthDate": "1990-05-15"
}
Request fields
| Field | Type | Required | Description |
|---|
userId | string (UUID) | Yes | Qash user ID from Step 1 |
firstName | string | Yes | User’s legal first name |
lastName | string | Yes | User’s legal last name |
email | string | Yes | User’s email — used to pre-fill the verification form |
phone | string | No | Phone number — used to pre-fill the verification form |
birthDate | string | No | YYYY-MM-DD format |
Response — 200 OK
{
"success": true,
"inquiryId": "inq_xxxxxxxxxxxxxxxxxxxxxxxx",
"verificationUrl": "https://inquiry.withpersona.com/verify?inquiry-id=inq_xxxxxxxxxxxxxxxxxxxxxxxx"
}
| Field | Type | Description |
|---|
inquiryId | string | Verification session ID — store for reference |
verificationUrl | string | URL to redirect your user to for identity verification |
How to use verificationUrl
Redirect your user to verificationUrl in a browser or embed it in a WebView. The user will complete their identity check — the form is pre-filled with the data you provided.
Once the user finishes, Qash handles the rest automatically. No webhook configuration is required on your side.
If approved, the user’s status transitions to "active". If declined or failed, the user remains "pending" and a new KYC flow must be initiated.
Errors
| Status | Error | Cause |
|---|
400 | userId, firstName, lastName and email are required | Missing required field |
400 | birthDate must be in YYYY-MM-DD format | Invalid date format |
409 | KYC already in progress | Check status first with GET /users/:userId |
401 | Invalid partner credentials | Wrong API credentials |
5. Step 3 — Poll for activation
Poll until status is "active":
GET /api/v1/partner/users/:userId
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Response — 200 OK
{
"success": true,
"data": {
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "juan@example.com",
"status": "active",
"countryCode": "MX",
"phone": "+5215512345678",
"preferredLanguage": "es",
"createdAt": "2026-06-12T00:00:00.000Z"
}
}
}
Poll with exponential backoff. Start at 10 seconds, back off to 60 seconds max. Identity verification typically completes within 1–5 minutes.
Errors
| Status | Error | Cause |
|---|
404 | User not found | User doesn’t exist or belongs to another partner |
6. Step 4 — Exchange a user token
Once the user is active, exchange their userId for a short-lived JWT from your backend. This JWT is required for all financial operations on behalf of that user.
POST /api/v1/partner/auth/token
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Content-Type: application/json
{
"userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Response — 200 OK
{
"success": true,
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "juan@example.com",
"status": "active",
"userType": "personal",
"roles": []
}
}
| Field | Type | Description |
|---|
accessToken | string (JWT) | Bearer token for financial endpoints — expires in 24 hours |
refreshToken | string (JWT) | Use to renew accessToken — valid for 30 days |
user.status | string | Must be "active" — token exchange fails for any other status |
Token management
Call POST /auth/token from your backend each time you need to act on behalf of a user. Keep the token server-side — never expose it to client code.
User initiates action in your app
→ Your backend calls POST /auth/token
→ Your backend uses accessToken to call the financial endpoint
→ Returns result to your app
Errors
| Status | Error | Cause |
|---|
401 | Invalid partner credentials | Wrong X-Api-Key or X-Api-Secret |
403 | User account is pending | User hasn’t completed KYC |
403 | User account is suspended | User is suspended |
404 | User not found | User doesn’t exist or belongs to another partner |
7. Financial operations
All financial endpoints require the following headers:
X-Api-Key: <your-api-key>
X-Api-Secret: <your-api-secret>
Authorization: Bearer <accessToken>
Content-Type: application/json
All monetary amounts are integers in the smallest unit of the currency.
| Currency | Smallest unit | Example |
|---|
| MXN | centavos | 150000 = MXN 1,500.00 |
| USD | cents | 100000 = USD 1,000.00 |
| COP | centavos | 500000000 = COP 5,000,000.00 |
Never use floating point values for amounts.
7.1 Create a ledger account
POST /api/v1/user/accounts
{
"accountName": "Main MXN Account",
"provider": "qash",
"accountType": "fiat_wallet",
"currencyCode": "MXN",
"status": "active",
"isPrimary": true
}
| Field | Type | Required | Description |
|---|
accountName | string | Yes | Descriptive name for the account |
provider | string | Yes | "qash" for standard accounts |
accountType | string | Yes | "fiat_wallet" |
currencyCode | string | Yes | "MXN", "USD", "COP" |
status | string | Yes | "active" |
isPrimary | boolean | No | Whether this is the user’s primary account |
Response — 201 Created: Returns the account object including id — save it to reference in deposit, transfer, and transaction calls.
7.2 List accounts
GET /api/v1/user/accounts
{
"items": [
{
"id": 619,
"description": "Main MXN Account",
"asset": "MXN",
"type": "LIABILITY",
"number": "ACC-xxxx",
"credits": 150000,
"debits": 0
}
],
"meta": {
"totalItems": 1,
"totalPages": 1,
"currentPage": 1,
"itemsPerPage": 20
}
}
Current balance = credits - debits. Both are integers in the account’s smallest currency unit.
7.3 Get balance
Returns all accounts with their current balance.
7.4 Deposit funds
Credit a user’s account after your platform confirms an external payment has settled.
POST /api/v1/user/deposit
{
"toAccountId": 619,
"amount": 100000,
"currency": "MXN",
"provider": "irisbank",
"description": "Top-up via IrisBank",
"metadata": {
"payer": {
"documentType": "RFC",
"documentNumber": "XAXX010101000"
}
}
}
| Field | Type | Required | Description |
|---|
toAccountId | number | Yes | Account ID to credit |
amount | number | Yes | Amount in smallest currency unit |
currency | string | Yes | Currency code matching the account |
provider | string | Yes | Payment source — "irisbank", "cobre", "stripe", "manual" |
description | string | Yes | Human-readable reason for the deposit |
metadata.payer.documentType | string | No | "CC", "RFC", "PASSPORT" |
metadata.payer.documentNumber | string | No | Payer document number |
Response — 200 OK
{
"id": 4501,
"status": "COMPLETED",
"amount": 100000,
"description": "Top-up via IrisBank"
}
status: "COMPLETED" means the balance has been updated immediately.
7.5 Transfer funds
POST /api/v1/user/transfer
{
"fromAccountId": 619,
"toAccountId": 620,
"amount": 50000,
"description": "Service fee"
}
| Field | Type | Required | Description |
|---|
fromAccountId | number | No | Source account — defaults to user’s primary account |
toAccountId | number | Yes | Destination account ID |
amount | number | Yes | Amount in smallest currency unit |
description | string | Yes | Transfer description |
7.6 Payout to bank account
{
"amount": 100000,
"currency": "MXN",
"provider": "cobre",
"fromAccountId": 619,
"beneficiary": {
"name": "Juan García",
"bankCode": "40012",
"accountNumber": "012180001234567890",
"accountType": "CLABE"
},
"description": "Withdrawal to Bancomer",
"idempotencyKey": "payout-juan-20260612-001"
}
| Field | Type | Required | Description |
|---|
amount | number | Yes | Amount in smallest unit |
currency | string | Yes | Currency code |
provider | string | Yes | Payment rail — "cobre", "irisbank", "mercury" |
fromAccountId | number | Yes | Source account to debit |
beneficiary.name | string | Yes | Recipient full name |
beneficiary.bankCode | string | Yes | Bank identifier |
beneficiary.accountNumber | string | Yes | CLABE / account number |
beneficiary.accountType | string | Yes | "CLABE", "IBAN", "SWIFT" |
description | string | Yes | Payout description |
idempotencyKey | string | Yes | Unique key per attempt — safe to retry with the same key |
Always include idempotencyKey. Retrying without one may result in duplicate payouts.
7.7 List transactions
GET /api/v1/user/transactions?limit=20&page=1
| Query param | Type | Description |
|---|
page | number | Page number (default 1) |
limit | number | Results per page (default 20, max 100) |
from | string | ISO date — filter start date |
to | string | ISO date — filter end date |
type | string | "DEPOSIT", "TRANSFER", "PAYOUT" |
7.8 Get a single transaction
GET /api/v1/user/transactions/:id
7.9 Exchange rate quote
POST /api/v1/user/exchange/calculate
{
"fromCurrency": "MXN",
"toCurrency": "USD",
"fromAmount": 1500
}
{
"fromCurrency": "MXN",
"toCurrency": "USD",
"fromAmount": 1500,
"toAmount": 78.54,
"rate": 0.052360,
"expiresAt": "2026-06-12T00:01:00.000Z"
}
Quotes expire — check expiresAt before using the rate. Do not cache quotes across requests.
7.10 KYC status
GET /api/v1/user/kyc/status
{
"status": "approved",
"verifiedAt": "2026-06-12T00:00:00.000Z"
}
| Status | Description |
|---|
created | Verification initiated |
pending | User started but hasn’t submitted |
completed | Submitted — under review |
approved | Approved — user is active |
rejected | Declined — initiate a new KYC flow |
expired | Session expired — initiate a new KYC flow |
8. Card operations
Card endpoints use the same dual-auth headers as financial endpoints.
| Method | Endpoint | Description |
|---|
GET | /api/v1/user/card/me | Get the user’s primary card |
GET | /api/v1/user/card/status | Current card status |
GET | /api/v1/user/card/details | Full card details |
GET | /api/v1/user/card/balance | Card balance |
GET | /api/v1/user/card/transactions | Card transactions |
POST | /api/v1/user/card/lock | Lock the card |
POST | /api/v1/user/card/unlock | Unlock the card |
PATCH | /api/v1/user/card/limit | Update spending limit |
9. User management
List your users
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 |
{
"success": true,
"data": {
"users": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "juan@example.com",
"status": "active",
"countryCode": "MX",
"createdAt": "2026-06-12T00:00:00.000Z"
}
],
"pagination": {
"total": 1,
"limit": 100,
"offset": 0,
"hasMore": false
}
}
}
10. API key management
Create a key pair
POST /api/v1/partner/api-keys
Authorization: Bearer <business-user-jwt>
Content-Type: application/json
{ "keyName": "Production key - June 2026" }
{
"success": true,
"apiKey": "qash_key_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"secretKey": "qash_secret_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...",
"keyName": "Production key - June 2026"
}
secretKey is returned once only. Store it immediately in a secrets manager. If lost, revoke and generate a new pair.
Revoke a key
DELETE /api/v1/partner/api-keys/:keyId
Authorization: Bearer <business-user-jwt>
11. Security checklist
12. Complete endpoint reference
| Endpoint group | X-Api-Key | X-Api-Secret | Authorization: Bearer |
|---|
User management (/partner/users, /partner/kyc) | Required | Required | — |
Token exchange (/partner/auth/token) | Required | Required | — |
Financial operations (/user/*) | Required | Required | Required (user JWT) |
API key management (/partner/api-keys) | — | — | Required (business JWT) |
Full endpoint map
| 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 |
POST | /api/v1/partner/auth/token | Exchange userId for user JWT |
GET | /api/v1/user/kyc/status | KYC verification status |
POST | /api/v1/user/accounts | Create a ledger account |
GET | /api/v1/user/accounts | List all accounts |
GET | /api/v1/user/balance | Get account balances |
POST | /api/v1/user/deposit | Credit a user account |
GET | /api/v1/user/transactions | List transactions |
GET | /api/v1/user/transactions/:id | Get a single transaction |
POST | /api/v1/user/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 |
GET | /api/v1/user/card/me | Get user’s card |
GET | /api/v1/user/card/status | Card status |
GET | /api/v1/user/card/details | Card details |
GET | /api/v1/user/card/balance | Card balance |
GET | /api/v1/user/card/transactions | Card transactions |
POST | /api/v1/user/card/lock | Lock card |
POST | /api/v1/user/card/unlock | Unlock card |
PATCH | /api/v1/user/card/limit | Update card limit |
POST | /api/v1/partner/api-keys | Create API key pair |
DELETE | /api/v1/partner/api-keys/:id | Revoke API key |