A fiat-to-crypto and crypto-to-fiat exchange platform built with Payload CMS and Next.js. Users submit the USDT amount they need, an admin sets the exchange rate and markup, and the system processes on-chain transfers via configurable blockchain networks.
Base URL: https://exc-api-stag.spinzo.io
All API endpoints require authentication. Two methods are supported:
Each user account can have an API key generated from the admin panel. Send it in the Authorization header:
Authorization: users API-Key s3cret
How to get an API key:
Example with cURL:
curl -H "Authorization: users API-Key xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
https://exc-api-stag.spinzo.io/api/networks/available
Login with email/password to get a JWT token, then use it for subsequent requests.
Login:
POST /api/users/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "your-password"
}
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": { "id": 1, "email": "user@example.com" },
"exp": 3600
}
Use the token:
Authorization: JWT eyJhbGciOiJIUzI1NiIs...
Fetch active blockchain networks the user can choose for their exchange.
GET /api/networks/available
Authorization: users API-Key YOUR_API_KEY
Response:
{
"networks": [
{
"id": 1,
"name": "BEP20 (BSC)",
"symbol": "bep20",
"networkType": "mainnet",
"gasFeeTokenName": "BNB"
},
{
"id": 2,
"name": "Ethereum",
"symbol": "eth",
"networkType": "mainnet",
"gasFeeTokenName": "ETH"
}
]
}
Submit an exchange request (fiat-to-crypto or crypto-to-fiat). The user specifies the USDT amount they need. A treasury wallet is automatically assigned based on the chosen network. The exchange rate and markup are set later by an admin in the panel, and the PHP amount is auto-computed.
POST /api/transactions/create-exchange
Content-Type: application/json
Authorization: users API-Key YOUR_API_KEY
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | fiat_to_crypto or crypto_to_fiat |
amountUsdt |
number | Yes | Amount of USDT |
network |
number | Yes | Network ID (from the available list) |
targetAddress |
string | Yes | Destination wallet address to receive USDT |
Example Request:
{
"type": "fiat_to_crypto",
"amountUsdt": 100,
"network": 1,
"targetAddress": "0x1234567890abcdef1234567890abcdef12345678"
}
Response:
{
"success": true,
"transaction": {
"id": 42,
"type": "fiat_to_crypto",
"amountUsdt": 100,
"network": 1,
"targetAddress": "0x1234567890abcdef1234567890abcdef12345678",
"status": "awaiting_fiat",
"createdAt": "2026-03-07T12:00:00.000Z"
}
}
Note:
exchangeRate,markup, andamountPhpare not returned on creation. An admin sets the exchange rate and markup from the admin panel after the transaction is created. The PHP amount is then auto-computed as:amountUsdt × exchangeRate × (1 + markup%).
Error Responses:
| Status | Reason |
|---|---|
| 401 | Unauthorized — missing or invalid credentials |
| 400 | Invalid type — must be fiat_to_crypto or crypto_to_fiat |
| 400 | Missing/invalid fields, inactive network |
| 400 | No treasury wallet available for chosen network |
Allows a third-party vault to check whether fiat has been settled for a given transaction. Fiat settlement is confirmed manually by an admin in the admin panel — this endpoint lets external systems poll the current status.
GET /api/transactions/check-settlement/:id
Authorization: users API-Key YOUR_API_KEY
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id |
number | Transaction ID to check |
Example Request:
curl -H "Authorization: users API-Key YOUR_API_KEY" \
https://exc-api-stag.spinzo.io/api/transactions/check-settlement/42
Response (fiat not yet settled):
{
"success": true,
"transaction": {
"id": 42,
"type": "fiat_to_crypto",
"amountUsdt": 100,
"status": "awaiting_fiat",
"fiatSettled": false,
"fiatSettlementId": null,
"txHash": null,
"createdAt": "2026-03-07T12:00:00.000Z",
"updatedAt": "2026-03-07T12:00:00.000Z"
}
}
Response (fiat settled, crypto completed):
{
"success": true,
"transaction": {
"id": 42,
"type": "fiat_to_crypto",
"amountUsdt": 100,
"status": "completed",
"fiatSettled": true,
"fiatSettlementId": "PAY-20260307-ABC123",
"txHash": "0xabc123...",
"createdAt": "2026-03-07T12:00:00.000Z",
"updatedAt": "2026-03-07T14:30:00.000Z"
}
}
Response Fields:
| Field | Type | Description |
|---|---|---|
fiatSettled |
boolean | true once admin has confirmed fiat receipt |
fiatSettlementId |
string | Admin-provided payment reference (null if not yet settled) |
txHash |
string | On-chain transaction hash (null if transfer not yet completed) |
status |
string | Current transaction status (see lifecycle below) |
Error Responses:
| Status | Reason |
|---|---|
| 401 | Unauthorized — missing or invalid credentials |
| 400 | Missing or invalid transaction ID |
| 404 | Transaction not found |
awaiting_fiat → fiat_received → crypto_transfer_pending → completed
↘ review_needed (on failure → retryable from admin)
awaiting_fiat — Transaction created, waiting for admin to confirm fiat paymentfiat_received — Fiat settlement confirmed by admin in the panel; transaction is queued for batchingcrypto_transfer_pending — Batch collector picked up the transaction; on-chain transfer in progresscompleted — USDT successfully transferred to user’s walletreview_needed — Transfer failed; admin can retry from the Payload admin panel