Skip to main content
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 credentialsX-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

X-Api-Key:    qash_key_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Api-Secret: qash_secret_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HeaderNotes
X-Api-KeyIdentifies your partner account
X-Api-SecretAuthenticates your request — never log or expose

Base URL

EnvironmentURL
Productionhttps://api.qash.ai
Sandboxhttps://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
StatusMeaningCan transact?
pendingRegistered but KYC not yet approvedNo
activeKYC approved — fully operationalYes
suspendedTemporarily disabledNo
bannedPermanently blockedNo

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

FieldTypeRequiredDescription
userTypestringYes"personal" or "business"
emailstringYesUser email — must be unique within your partner scope
phonestringNoE.164 format — e.g. "+5215512345678"
countryCodestringYesISO 3166-1 alpha-2 — e.g. "MX", "CO", "US"
preferredLanguagestringNo"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

StatusErrorCause
400Validation messageMissing field or invalid format
409A user with this email already existsEmail already registered in your partner scope
401Invalid partner credentialsWrong 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

FieldTypeRequiredDescription
userIdstring (UUID)YesQash user ID from Step 1
firstNamestringYesUser’s legal first name
lastNamestringYesUser’s legal last name
emailstringYesUser’s email — used to pre-fill the verification form
phonestringNoPhone number — used to pre-fill the verification form
birthDatestringNoYYYY-MM-DD format

Response — 200 OK

{
  "success": true,
  "inquiryId": "inq_xxxxxxxxxxxxxxxxxxxxxxxx",
  "verificationUrl": "https://inquiry.withpersona.com/verify?inquiry-id=inq_xxxxxxxxxxxxxxxxxxxxxxxx"
}
FieldTypeDescription
inquiryIdstringVerification session ID — store for reference
verificationUrlstringURL 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

StatusErrorCause
400userId, firstName, lastName and email are requiredMissing required field
400birthDate must be in YYYY-MM-DD formatInvalid date format
409KYC already in progressCheck status first with GET /users/:userId
401Invalid partner credentialsWrong 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

StatusErrorCause
404User not foundUser 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": []
  }
}
FieldTypeDescription
accessTokenstring (JWT)Bearer token for financial endpoints — expires in 24 hours
refreshTokenstring (JWT)Use to renew accessToken — valid for 30 days
user.statusstringMust 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

StatusErrorCause
401Invalid partner credentialsWrong X-Api-Key or X-Api-Secret
403User account is pendingUser hasn’t completed KYC
403User account is suspendedUser is suspended
404User not foundUser 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

Amount format

All monetary amounts are integers in the smallest unit of the currency.
CurrencySmallest unitExample
MXNcentavos150000 = MXN 1,500.00
USDcents100000 = USD 1,000.00
COPcentavos500000000 = 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
}
FieldTypeRequiredDescription
accountNamestringYesDescriptive name for the account
providerstringYes"qash" for standard accounts
accountTypestringYes"fiat_wallet"
currencyCodestringYes"MXN", "USD", "COP"
statusstringYes"active"
isPrimarybooleanNoWhether 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

GET /api/v1/user/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"
    }
  }
}
FieldTypeRequiredDescription
toAccountIdnumberYesAccount ID to credit
amountnumberYesAmount in smallest currency unit
currencystringYesCurrency code matching the account
providerstringYesPayment source — "irisbank", "cobre", "stripe", "manual"
descriptionstringYesHuman-readable reason for the deposit
metadata.payer.documentTypestringNo"CC", "RFC", "PASSPORT"
metadata.payer.documentNumberstringNoPayer 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"
}
FieldTypeRequiredDescription
fromAccountIdnumberNoSource account — defaults to user’s primary account
toAccountIdnumberYesDestination account ID
amountnumberYesAmount in smallest currency unit
descriptionstringYesTransfer description

7.6 Payout to bank account

POST /api/v1/user/payout
{
  "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"
}
FieldTypeRequiredDescription
amountnumberYesAmount in smallest unit
currencystringYesCurrency code
providerstringYesPayment rail — "cobre", "irisbank", "mercury"
fromAccountIdnumberYesSource account to debit
beneficiary.namestringYesRecipient full name
beneficiary.bankCodestringYesBank identifier
beneficiary.accountNumberstringYesCLABE / account number
beneficiary.accountTypestringYes"CLABE", "IBAN", "SWIFT"
descriptionstringYesPayout description
idempotencyKeystringYesUnique 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 paramTypeDescription
pagenumberPage number (default 1)
limitnumberResults per page (default 20, max 100)
fromstringISO date — filter start date
tostringISO date — filter end date
typestring"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"
}
StatusDescription
createdVerification initiated
pendingUser started but hasn’t submitted
completedSubmitted — under review
approvedApproved — user is active
rejectedDeclined — initiate a new KYC flow
expiredSession expired — initiate a new KYC flow

8. Card operations

Card endpoints use the same dual-auth headers as financial endpoints.
MethodEndpointDescription
GET/api/v1/user/card/meGet the user’s primary card
GET/api/v1/user/card/statusCurrent card status
GET/api/v1/user/card/detailsFull card details
GET/api/v1/user/card/balanceCard balance
GET/api/v1/user/card/transactionsCard transactions
POST/api/v1/user/card/lockLock the card
POST/api/v1/user/card/unlockUnlock the card
PATCH/api/v1/user/card/limitUpdate 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 paramTypeDescription
statusstring"pending", "active", "suspended", "banned"
limitnumber1–200, default 100
offsetnumberDefault 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

  • X-Api-Secret stored in a secrets manager — not in source code or environment files committed to git.
  • accessToken never sent to the browser — obtained and used server-side only.
  • All API calls originate from your backend — no direct browser-to-Qash calls.
  • idempotencyKey included on every payout request, unique per attempt.
  • Your backend validates user.status === "active" before financial operations.
  • API key rotation plan in place — revoke and reissue on any suspected compromise.
  • Logs do not contain X-Api-Secret, accessToken, or refreshToken.

12. Complete endpoint reference

Headers by endpoint group

Endpoint groupX-Api-KeyX-Api-SecretAuthorization: Bearer
User management (/partner/users, /partner/kyc)RequiredRequired
Token exchange (/partner/auth/token)RequiredRequired
Financial operations (/user/*)RequiredRequiredRequired (user JWT)
API key management (/partner/api-keys)Required (business JWT)

Full endpoint map

MethodPathDescription
POST/api/v1/partner/usersRegister a new user
GET/api/v1/partner/usersList your users
GET/api/v1/partner/users/:userIdGet a specific user
POST/api/v1/partner/kycInitiate identity verification
POST/api/v1/partner/auth/tokenExchange userId for user JWT
GET/api/v1/user/kyc/statusKYC verification status
POST/api/v1/user/accountsCreate a ledger account
GET/api/v1/user/accountsList all accounts
GET/api/v1/user/balanceGet account balances
POST/api/v1/user/depositCredit a user account
GET/api/v1/user/transactionsList transactions
GET/api/v1/user/transactions/:idGet a single transaction
POST/api/v1/user/transferTransfer between accounts
POST/api/v1/user/payoutSend funds to a bank account
POST/api/v1/user/exchange/calculateGet an exchange rate quote
GET/api/v1/user/card/meGet user’s card
GET/api/v1/user/card/statusCard status
GET/api/v1/user/card/detailsCard details
GET/api/v1/user/card/balanceCard balance
GET/api/v1/user/card/transactionsCard transactions
POST/api/v1/user/card/lockLock card
POST/api/v1/user/card/unlockUnlock card
PATCH/api/v1/user/card/limitUpdate card limit
POST/api/v1/partner/api-keysCreate API key pair
DELETE/api/v1/partner/api-keys/:idRevoke API key