# Stablecoin Remittance API v 1.0.0 ## 1. Wallet Provisioning ### 1.1 Create Recipient Wallet **Endpoint:** `POST /v1/sdk/wallets/provision` **When to call:** When a bank customer opts-in to enable crypto wallet functionality. **Authentication:** JWT Bearer Token with `wallet_manager` role **Request:** ```http POST /v1/sdk/wallets/provision HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "customer_id": "BANK_CUST_001", "customer_email": "customer@bank.pk", "customer_phone": "+923001234567", "blockchain": "ethereum", "kyc_level": "ENHANCED" } ``` **Request Parameters:** | Field | Type | Required | Description | | --- | --- | --- | --- | | `customer_id` | string | Yes | Bank's internal customer identifier | | `customer_email` | string | Yes | Customer email address | | `customer_phone` | string | Yes | Customer phone number (E.164 format) | | `blockchain` | string | No | Blockchain network (default: "ethereum") | | `kyc_level` | string | No | KYC level: BASIC, ENHANCED, COMPREHENSIVE | **Response (200 OK):** ```json { "wallet_id": "wlt_abc123xyz", "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "blockchain": "ethereum", "status": "ACTIVE", "kyc_level": "ENHANCED", "created_at": "2025-11-19T10:30:00Z" } ``` **Response Fields:** | Field | Type | Description | | --- | --- | --- | | `wallet_id` | string | SaturnX wallet identifier (use in future API calls) | | `wallet_address` | string | Blockchain address (DO NOT expose to senders) | | `blockchain` | string | Blockchain network | | `status` | string | ACTIVE, BLOCKED, BLACKLISTED, PENDING | | `kyc_level` | string | Inherited KYC level | | `created_at` | string | ISO 8601 timestamp | **Error Responses:** ```json // 400 Bad Request - Invalid input { "error": "INVALID_REQUEST", "message": "customer_email is not a valid email address", "field": "customer_email" } // 409 Conflict - Wallet already exists { "error": "WALLET_EXISTS", "message": "Wallet already provisioned for customer_id BANK_CUST_001", "existing_wallet_id": "wlt_abc123xyz" } // 403 Forbidden - KYC requirement not met { "error": "KYC_REQUIRED", "message": "Customer KYC verification required before wallet provisioning", "required_kyc_level": "ENHANCED" } ``` ### 1.2 Get Wallet Details **Endpoint:** `GET /v1/sdk/wallets/{wallet_id}` **When to call:** To check wallet status, balance, or retrieve details. **Request:** ```http GET /v1/sdk/wallets/wlt_abc123xyz HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} ``` **Response (200 OK):** ```json { "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "blockchain": "ethereum", "status": "ACTIVE", "kyc_level": "ENHANCED", "balance": { "total_value_usd": "1250.00", "assets": [ { "currency": "USDC", "amount": "1000.00", "value_usd": "1000.00", "value_pkr": "280000.00" }, { "currency": "USDT", "amount": "250.00", "value_usd": "250.00", "value_pkr": "70000.00" } ] }, "created_at": "2025-11-19T10:30:00Z", "last_activity_at": "2025-11-19T14:22:00Z" } ``` ## 2. Payment Request Management ### 2.1 Create Payment Request **Endpoint:** `POST /v1/sdk/payment-requests` **When to call:** When recipient customer wants to request payment from a sender. **Request:** ```http POST /v1/sdk/payment-requests HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "wallet_id": "wlt_abc123xyz", "amount": { "type": "crypto", "value": "1000", "currency": "USDC" }, "purpose": "Family support payment", "expires_in_hours": 24 } ``` **Request Parameters:** | Field | Type | Required | Description | | --- | --- | --- | --- | | `wallet_id` | string | Yes | Recipient wallet ID | | `amount.type` | string | Yes | "crypto", "fiat", or "flexible" | | `amount.value` | string | Conditional | Required if type is "crypto" or "fiat" | | `amount.currency` | string | Conditional | Required if not "flexible" | | `purpose` | string | No | Payment description/purpose | | `expires_in_hours` | integer | No | Link expiry (default: 24 hours) | **Response (200 OK):** ```json { "payment_request_id": "preq_xyz789abc", "payment_link": "https://saturnx.money/pay/xyz789abc", "qr_code_url": "https://cdn.saturnx.money/qr/xyz789abc.png", "escrow_address": "0x8ba1f109551bD432803012645Ac136ddd64DBA72", "amount": { "type": "crypto", "value": "1000", "currency": "USDC" }, "status": "ACTIVE", "created_at": "2025-11-19T15:00:00Z", "expires_at": "2025-11-20T15:00:00Z" } ``` **Important Notes:** - `escrow_address` is a **proxy address** - NOT the recipient's actual wallet - This maintains recipient privacy - Payment link can be shared via any channel (WhatsApp, email, SMS, etc.) ### 2.2 List Payment Requests **Endpoint:** `GET /v1/sdk/payment-requests` **Query Parameters:** - `wallet_id` (required): Filter by wallet - `status` (optional): Filter by status (ACTIVE, PAID, EXPIRED, CANCELLED) - `page` (optional): Page number (default: 0) - `limit` (optional): Results per page (default: 20, max: 100) **Request:** ```http GET /v1/sdk/payment-requests?wallet_id=wlt_abc123xyz&status=ACTIVE&page=0&limit=20 HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} ``` **Response (200 OK):** ```json { "payment_requests": [ { "payment_request_id": "preq_xyz789abc", "payment_link": "https://saturnx.money/pay/xyz789abc", "amount": { "type": "crypto", "value": "1000", "currency": "USDC" }, "status": "ACTIVE", "created_at": "2025-11-19T15:00:00Z", "expires_at": "2025-11-20T15:00:00Z" } ], "pagination": { "page": 0, "limit": 20, "total_count": 45, "total_pages": 3 } } ``` ## 3. Transaction History ### 3.1 Get Transaction History **Endpoint:** `GET /v1/sdk/transactions` **When to call:** When customer views transaction history in banking app. **Query Parameters:** - `wallet_id` (required): Filter by wallet - `type` (optional): Filter by type (RECEIVED, EXCHANGED, BLOCKED) - `page` (optional): Page number (default: 0) - `limit` (optional): Results per page (default: 20, max: 100) **Request:** ```http GET /v1/sdk/transactions?wallet_id=wlt_abc123xyz&page=0&limit=20 HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} ``` **Response (200 OK):** ```json { "transactions": [ { "transaction_id": "tx_def456ghi", "type": "RECEIVED", "amount": "1000.00", "currency": "USDC", "sender": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone_number": "+14155551234", "custodial_provider": "Binance" }, "timestamp": "2025-11-19T15:30:00Z", "status": "COMPLETED", "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "blockchain_explorer_url": "https://etherscan.io/tx/0x1234567890abcdef..." }, { "transaction_id": "tx_ghi789jkl", "type": "EXCHANGED", "amount": "500.00", "currency": "USDC", "exchange_rate": "280.50", "received_amount": "140250.00", "received_currency": "PKR", "timestamp": "2025-11-18T10:15:00Z", "status": "SETTLED", "settlement_reference": "PKR_SETTLE_001" } ], "pagination": { "page": 0, "limit": 20, "total_count": 127, "total_pages": 7 } } ``` ## 4. Crypto-to-Fiat Exchange ### 4.1 Get Exchange Rate **Endpoint:** `GET /v1/sdk/orders/exchange-rate` **When to call:** When customer wants to check current exchange rate before converting. **Query Parameters:** - `from_currency` (required): Source crypto (USDC, USDT) - `to_currency` (required): Destination fiat (PKR) - `amount` (optional): Amount to convert (for exact quote) **Request:** ```http GET /v1/sdk/orders/exchange-rate?from_currency=USDC&to_currency=PKR&amount=1000 HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} ``` **Response (200 OK):** ```json { "from_currency": "USDC", "to_currency": "PKR", "rate": "280.75", "spread": "0.005", "estimated_amount": "279346.25", "valid_until": "2025-11-19T16:00:00Z", "rate_locked": false } ``` **Response Fields:** | Field | Type | Description | | --- | --- | --- | | `rate` | string | Exchange rate (1 USDC = X PKR) | | `spread` | string | SaturnX fee/spread (0.5% = 0.005) | | `estimated_amount` | string | PKR amount recipient will receive | | `valid_until` | string | Rate validity expiration | | `rate_locked` | boolean | Whether rate is locked | ### 4.2 Request Exchange **Endpoint:** `POST /v1/sdk/orders` **When to call:** When customer initiates crypto-to-PKR conversion. **Request:** ```http POST /v1/sdk/orders HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "order_type": "EXCHANGE", "wallet_id": "wlt_abc123xyz", "amount": "1000", "from_currency": "USDC", "to_currency": "PKR", "bank_account": { "iban": "PK36SCBL0000001123456702", "account_number": "0000001123456702", "bank_code": "SCBL", "account_holder_name": "Muhammad Ahmed" }, "lock_rate": false } ``` **Request Parameters:** | Field | Type | Required | Description | | --- | --- | --- | --- | | `order_type` | string | Yes | Order type (EXCHANGE) | | `wallet_id` | string | Yes | Source wallet | | `amount` | string | Yes | Amount to convert | | `from_currency` | string | Yes | Source currency (USDC, USDT) | | `to_currency` | string | Yes | Destination currency (PKR) | | `bank_account` | object | Yes | Pakistani bank account details | | `lock_rate` | boolean | No | Lock rate for 15 minutes (default: false) | **Response (200 OK):** ```json { "order_id": "ord_mno123pqr", "order_type": "EXCHANGE", "from_amount": "1000.00", "from_currency": "USDC", "to_amount": "280187.50", "to_currency": "PKR", "exchange_rate": "280.75", "spread": "0.005", "status": "PENDING", "rate_locked": false, "created_at": "2025-11-19T16:00:00Z", "estimated_settlement": "2025-11-19T18:00:00Z" } ``` **Status Values:** - `PENDING` - Exchange initiated, awaiting processing - `PROCESSING` - Exchange in progress - `SETTLED` - PKR transferred to bank account - `FAILED` - Exchange failed (reason in error message) - `CANCELLED` - Cancelled by user or timeout ### 4.3 Get Exchange History **Endpoint:** `GET /v1/sdk/orders` **Query Parameters:** - `wallet_id` (required) - `order_type` (optional): Filter by order type (EXCHANGE) - `page` (optional) - `limit` (optional) **Request:** ```http GET /v1/sdk/orders?wallet_id=wlt_abc123xyz&order_type=EXCHANGE&page=0&limit=20 HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} ``` **Response:** Similar to transaction history but specific to exchanges. ## 5. Wallet Control & Compliance ### 5.1 Block Wallet (Temporary) **Endpoint:** `POST /v1/sdk/wallets/{wallet_id}/block` **When to call:** When bank needs to temporarily freeze wallet for: - Suspicious activity investigation - Non-trusted transfer detected - Risk threshold exceeded - Bank policy violation **Request:** ```http POST /v1/sdk/wallets/wlt_abc123xyz/block HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "reason": "SUSPICIOUS_ACTIVITY", "details": "Multiple high-value transactions from unknown sources within 24 hours", "auto_unblock_hours": 72 } ``` **Request Parameters:** | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string | Yes | Reason code (see below) | | `details` | string | Yes | Human-readable explanation | | `auto_unblock_hours` | integer | No | Automatic unblock after N hours | **Reason Codes:** - `NON_TRUSTED_TRANSFER` - Transfer from non-whitelisted source - `SUSPICIOUS_ACTIVITY` - Detected suspicious patterns - `RISK_THRESHOLD_EXCEEDED` - High risk score - `BANK_POLICY_VIOLATION` - Violated bank's policies - `PENDING_INVESTIGATION` - Under investigation **Response (200 OK):** ```json { "wallet_id": "wlt_abc123xyz", "status": "BLOCKED", "reason": "SUSPICIOUS_ACTIVITY", "blocked_at": "2025-11-19T16:30:00Z", "auto_unblock_at": "2025-11-22T16:30:00Z" } ``` **Blocked Wallet Behavior:** - ✅ **Can receive** funds (funds accepted but frozen) - ❌ **Cannot send** or withdraw funds - ❌ **Cannot initiate** exchanges - ❌ **Cannot create** payment requests ### 5.2 Unblock Wallet **Endpoint:** `POST /v1/sdk/wallets/{wallet_id}/unblock` **Request:** ```http POST /v1/sdk/wallets/wlt_abc123xyz/unblock HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "unblock_reason": "Investigation completed - no violations found" } ``` **Response (200 OK):** ```json { "wallet_id": "wlt_abc123xyz", "status": "ACTIVE", "unblocked_at": "2025-11-20T10:00:00Z" } ``` ### 5.3 Blacklist Wallet (Permanent - Regulatory) **Endpoint:** `POST /v1/sdk/wallets/{wallet_id}/blacklist` **When to call:** When regulatory authority (e.g., State Bank of Pakistan, FIA) requires permanent wallet freeze. **⚠️ CRITICAL:** Blacklisting adds the wallet address to USDC smart contract blacklist. This is **irreversible** without regulatory approval. **Request:** ```http POST /v1/sdk/wallets/wlt_abc123xyz/blacklist HTTP/1.1 Host: api.saturnx.money Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "regulatory_authority": "State Bank of Pakistan", "regulatory_reference_number": "SBP/AML/2025/001234", "reason": "AML violation - proceeds of crime", "effective_date": "2025-11-19" } ``` **Request Parameters:** | Field | Type | Required | Description | | --- | --- | --- | --- | | `regulatory_authority` | string | Yes | Regulatory body name | | `regulatory_reference_number` | string | Yes | Official case/reference number | | `reason` | string | Yes | Blacklist reason | | `effective_date` | string | Yes | ISO date (YYYY-MM-DD) | **Response (200 OK):** ```json { "wallet_id": "wlt_abc123xyz", "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "status": "BLACKLISTED", "regulatory_authority": "State Bank of Pakistan", "regulatory_reference": "SBP/AML/2025/001234", "blacklisted_at": "2025-11-19T17:00:00Z", "usdc_contract_blacklist_applied": true } ``` **Blacklisted Wallet Behavior:** - ❌ **Cannot receive** any USDC transactions - ❌ **Cannot send** any USDC transactions - ❌ **All USDC frozen** at smart contract level - ✅ **Enforced on-chain** - not just platform-level **Blocking vs Blacklisting:** ``` ┌───────────────────────────────────────────────────────────┐ │ BLOCKING vs BLACKLISTING │ ├───────────────────────────────────────────────────────────┤ │ BLOCKING (Temporary) │ │ • Trigger: Policy violation, suspicious activity │ │ • Scope: Platform level (SaturnX) │ │ • Duration: Temporary (hours/days) │ │ • Can Receive: YES (but frozen) │ │ • Can Send: NO │ │ • Reversible: YES (bank can unblock) │ │ │ │ BLACKLISTING (Permanent) │ │ • Trigger: Regulatory request (SBP, FIA, court order) │ │ • Scope: USDC smart contract (on-chain) │ │ • Duration: Permanent until regulatory clearance │ │ • Can Receive: NO (all transactions blocked) │ │ • Can Send: NO (all transactions blocked) │ │ • Reversible: Requires regulatory approval │ └───────────────────────────────────────────────────────────┘ ``` ## 6. Webhook Events (SaturnX → Bank Backend) ### Webhook Setup **Bank must provide webhook endpoint during onboarding:** ``` https://bank-backend.example.pk/webhooks/saturnx/{event_type} ``` **Webhook Security:** - HTTPS required - HMAC signature verification (see Authentication section) - IP whitelist recommended - Timestamp validation (reject old/replayed events) ### 6.1 Payment Received Event **Webhook:** `POST /webhooks/saturnx/payment-received` **When triggered:** When funds arrive in bank's escrow wallet for a payment request. **Payload:** ```json { "event_id": "evt_abc123xyz", "event_type": "payment.received", "timestamp": "2025-11-19T15:30:00Z", "data": { "payment_request_id": "preq_xyz789abc", "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "amount": "1000.00", "currency": "USDC", "blockchain": "ethereum", "tx_hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "from_address": "0x8ba1f109551bD432803012645Ac136ddd64DBA72", "to_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "confirmations": 12, "sender_info": { "first_name": "John", "last_name": "Doe", "email": "john.doe@example.com", "phone_number": "+14155551234", "custodial_provider": "Binance" }, "status": "RECEIVED_IN_ESCROW" } } ``` **Bank Backend Response:** ```http HTTP/1.1 200 OK Content-Type: application/json { "received": true, "processed_at": "2025-11-19T15:30:05Z" } ``` **Next Steps:** - SaturnX performs risk assessment - If approved, funds transfer from escrow to recipient wallet - Bank receives `transfer-complete` webhook ### 6.2 Risk Check Complete Event **Webhook:** `POST /webhooks/saturnx/risk-check-complete` **When triggered:** After risk assessment is complete (approved, rejected, or manual review). **Payload:** ```json { "event_id": "evt_def456ghi", "event_type": "risk_check.complete", "timestamp": "2025-11-19T15:35:00Z", "data": { "payment_request_id": "preq_xyz789abc", "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "risk_check_result": "APPROVED", "risk_score": 25, "risk_level": "LOW", "checks_performed": [ "ON_CHAIN_ANALYSIS", "SANCTION_SCREENING", "SOURCE_VERIFICATION" ], "decision_reason": "Low risk - auto-approved", "next_action": "TRANSFER_TO_RECIPIENT" } } ``` **Risk Check Results:** - `APPROVED` - Payment approved, will transfer to recipient - `REJECTED` - Payment rejected, held in escrow - `MANUAL_REVIEW_REQUIRED` - Flagged for manual review - `BLOCKED` - Wallet automatically blocked ### 6.3 Transfer Complete Event **Webhook:** `POST /webhooks/saturnx/transfer-complete` **When triggered:** After funds transfer from escrow to recipient wallet. **Payload:** ```json { "event_id": "evt_ghi789jkl", "event_type": "transfer.complete", "timestamp": "2025-11-19T15:40:00Z", "data": { "payment_request_id": "preq_xyz789abc", "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "amount": "1000.00", "currency": "USDC", "transfer_tx_hash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", "status": "COMPLETED", "completed_at": "2025-11-19T15:40:00Z" } } ``` **Next Steps:** - Bank can notify customer via push notification/SMS/email - Customer can view updated balance in banking app ### 6.4 Exchange Complete Event **Webhook:** `POST /webhooks/saturnx/exchange-complete` **When triggered:** After crypto-to-PKR exchange is settled to bank account. **Payload:** ```json { "event_id": "evt_jkl012mno", "event_type": "exchange.complete", "timestamp": "2025-11-19T18:00:00Z", "data": { "order_id": "ord_mno123pqr", "order_type": "EXCHANGE", "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "from_amount": "1000.00", "from_currency": "USDC", "to_amount": "280187.50", "to_currency": "PKR", "exchange_rate": "280.75", "settlement_reference": "PKR_SETTLE_001", "bank_account": { "iban": "PK36SCBL0000001123456702", "account_holder_name": "Muhammad Ahmed" }, "status": "SETTLED", "settled_at": "2025-11-19T18:00:00Z" } } ``` ### 6.5 Wallet Blocked Event **Webhook:** `POST /webhooks/saturnx/wallet-blocked` **When triggered:** When wallet is automatically blocked by risk system or manually by bank. **Payload:** ```json { "event_id": "evt_pqr345stu", "event_type": "wallet.blocked", "timestamp": "2025-11-19T16:30:00Z", "data": { "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "reason": "SUSPICIOUS_ACTIVITY", "details": "Multiple high-value transactions from unknown sources", "blocked_at": "2025-11-19T16:30:00Z", "auto_unblock_at": "2025-11-22T16:30:00Z", "triggered_by": "AUTOMATIC" } } ``` ### 6.6 Transaction Failed Event **Webhook:** `POST /webhooks/saturnx/transaction-failed` **When triggered:** When on-chain transaction fails or is rejected. **Payload:** ```json { "event_id": "evt_stu678vwx", "event_type": "transaction.failed", "timestamp": "2025-11-19T15:45:00Z", "data": { "payment_request_id": "preq_xyz789abc", "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "failure_reason": "INSUFFICIENT_GAS", "error_message": "Transaction failed due to insufficient gas fees", "failed_at": "2025-11-19T15:45:00Z" } } ``` ## Data Models ### Property Naming Convention All JSON properties use **snake_case** as per ADR-0004: ```json { "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "created_at": "2025-11-19T10:00:00Z" } ``` ### Common Data Types #### Wallet ```json { "wallet_id": "wlt_abc123xyz", "customer_id": "BANK_CUST_001", "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "blockchain": "ethereum", "status": "ACTIVE", "kyc_level": "ENHANCED", "created_at": "2025-11-19T10:00:00Z" } ``` **Status Values:** - `ACTIVE` - Wallet operational - `BLOCKED` - Temporarily frozen - `BLACKLISTED` - Permanently blacklisted (regulatory) - `PENDING` - Provisioning in progress **KYC Levels:** - `BASIC` - Name, DOB, email - `ENHANCED` - + ID document, selfie, address - `COMPREHENSIVE` - + proof of address, source of funds #### Balance ```json { "wallet_id": "wlt_abc123xyz", "total_value_usd": "1250.00", "assets": [ { "currency": "USDC", "amount": "1000.00", "value_usd": "1000.00", "value_pkr": "280000.00" }, { "currency": "USDT", "amount": "250.00", "value_usd": "250.00", "value_pkr": "70000.00" } ] } ``` #### Transaction ```json { "transaction_id": "tx_def456ghi", "type": "RECEIVED", "amount": "1000.00", "currency": "USDC", "status": "COMPLETED", "tx_hash": "0x1234567890abcdef...", "timestamp": "2025-11-19T15:30:00Z" } ``` **Transaction Types:** - `RECEIVED` - Incoming crypto payment - `EXCHANGED` - Crypto-to-fiat conversion - `BLOCKED` - Transaction blocked by compliance **Transaction Statuses:** - `PENDING` - Awaiting confirmation - `RECEIVED_IN_ESCROW` - In escrow wallet - `COMPLETED` - In recipient wallet - `FAILED` - Transaction failed - `REJECTED` - Rejected by compliance ## Error Handling ### Standard Error Response All errors follow this structure: ```json { "error": "ERROR_CODE", "message": "Human-readable error description", "field": "field_name", "details": { "additional_context": "value" }, "request_id": "req_abc123xyz", "timestamp": "2025-11-19T16:00:00Z" } ``` ### Error Codes | HTTP Status | Error Code | Description | Action | | --- | --- | --- | --- | | 400 | `INVALID_REQUEST` | Invalid input parameters | Fix request body | | 401 | `UNAUTHORIZED` | Invalid/missing JWT token | Refresh authentication | | 403 | `FORBIDDEN` | Insufficient permissions | Check user roles | | 404 | `NOT_FOUND` | Resource not found | Verify IDs | | 409 | `CONFLICT` | Resource already exists | Handle duplicate | | 422 | `VALIDATION_FAILED` | Business logic validation failed | Review validation errors | | 429 | `RATE_LIMIT_EXCEEDED` | Too many requests | Implement backoff | | 500 | `INTERNAL_ERROR` | Server error | Retry with exponential backoff | | 503 | `SERVICE_UNAVAILABLE` | Temporarily unavailable | Retry later | ### Error Examples **Invalid Input:** ```json { "error": "INVALID_REQUEST", "message": "customer_email is not a valid email address", "field": "customer_email", "request_id": "req_abc123xyz" } ``` **Wallet Not Found:** ```json { "error": "NOT_FOUND", "message": "Wallet with ID wlt_abc123xyz not found", "field": "wallet_id", "request_id": "req_def456ghi" } ``` **Insufficient Balance:** ```json { "error": "INSUFFICIENT_BALANCE", "message": "Insufficient USDC balance for exchange", "details": { "requested_amount": "1000.00", "available_balance": "500.00", "currency": "USDC" }, "request_id": "req_ghi789jkl" } ``` **Rate Limit:** ```json { "error": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded: 100 requests per minute", "details": { "limit": 100, "window": "1 minute", "retry_after": "45 seconds" }, "request_id": "req_jkl012mno" } ``` ## Rate Limits ### Default Rate Limits | Endpoint Pattern | Rate Limit | Window | | --- | --- | --- | | `/v1/sdk/wallets/*` | 100 req/min | Per tenant | | `/v1/sdk/payment-requests/*` | 200 req/min | Per tenant | | `/v1/sdk/orders/*` | 50 req/min | Per tenant | | `/v1/sdk/transactions/*` | 200 req/min | Per tenant | | Webhooks (incoming) | Unlimited | - | ### Rate Limit Headers All responses include rate limit headers: ```http X-RateLimit-Limit: 100 X-RateLimit-Remaining: 87 X-RateLimit-Reset: 1735776000 ``` ### Handling Rate Limits Implement exponential backoff when rate limit is hit: ```javascript async function callApiWithRetry(apiCall, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await apiCall(); } catch (error) { if (error.status === 429) { const retryAfter = parseInt(error.headers['retry-after']) || Math.pow(2, attempt); await sleep(retryAfter * 1000); } else { throw error; } } } throw new Error('Max retries exceeded'); } ``` ## Testing ### Sandbox Environment **Base URL:** `https://api-sandbox.saturnx.money` **Test Credentials:** - JWT tokens provided during onboarding - Test tenant ID: `test-bank-001` - Test webhook endpoint configured during setup ### Test Wallets SaturnX provides test wallets with pre-funded balances: ```json { "wallet_id": "wlt_test_001", "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "blockchain": "ethereum", "balance": { "USDC": "10000.00", "USDT": "10000.00" } } ``` ### Test Payment Links Generate test payment links that automatically simulate sender payments: ```bash curl -X POST https://api-sandbox.saturnx.money/v1/sdk/payment-requests \ -H "Authorization: Bearer TEST_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "wallet_id": "wlt_test_001", "amount": {"type": "crypto", "value": "100", "currency": "USDC"}, "purpose": "Test payment" }' ``` ## Glossary | Term | Definition | | --- | --- | | **Bank-Custodied Wallet** | Crypto wallet owned and controlled by bank on behalf of customer | | **Escrow Wallet** | Bank-owned wallet where payments arrive for risk checks before recipient | | **Payment Link** | Unique URL allowing sender to pay recipient without knowing wallet address | | **Risk Check Tier** | Amount-based compliance check level (Fast Track, Standard, Comprehensive) | | **Blocking** | Temporary wallet freeze by bank (platform-level) | | **Blacklisting** | Permanent wallet freeze by regulatory authority (on-chain USDC contract) | | **Travel Rule** | FATF requirement to exchange customer information for transactions >$1,000 USD | | **Whitelisted Custodial Provider** | Approved sender wallet service (Binance, Coinbase, etc.) | | **MPC Wallet** | Multi-Party Computation wallet (secure custody via DFNS) | | **PKR** | Pakistani Rupee (fiat currency) | | **USDC** | USD Coin (stablecoin) | | **USDT** | Tether (stablecoin) |