Skip to content
Last updated

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:

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:

FieldTypeRequiredDescription
customer_idstringYesBank's internal customer identifier
customer_emailstringYesCustomer email address
customer_phonestringYesCustomer phone number (E.164 format)
blockchainstringNoBlockchain network (default: "ethereum")
kyc_levelstringNoKYC level: BASIC, ENHANCED, COMPREHENSIVE

Response (200 OK):

{
  "wallet_id": "wlt_abc123xyz",
  "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "blockchain": "ethereum",
  "status": "ACTIVE",
  "kyc_level": "ENHANCED",
  "created_at": "2025-11-19T10:30:00Z"
}

Response Fields:

FieldTypeDescription
wallet_idstringSaturnX wallet identifier (use in future API calls)
wallet_addressstringBlockchain address (DO NOT expose to senders)
blockchainstringBlockchain network
statusstringACTIVE, BLOCKED, BLACKLISTED, PENDING
kyc_levelstringInherited KYC level
created_atstringISO 8601 timestamp

Error Responses:

// 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:

GET /v1/sdk/wallets/wlt_abc123xyz HTTP/1.1
Host: api.saturnx.money
Authorization: Bearer {JWT_TOKEN}

Response (200 OK):

{
  "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:

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:

FieldTypeRequiredDescription
wallet_idstringYesRecipient wallet ID
amount.typestringYes"crypto", "fiat", or "flexible"
amount.valuestringConditionalRequired if type is "crypto" or "fiat"
amount.currencystringConditionalRequired if not "flexible"
purposestringNoPayment description/purpose
expires_in_hoursintegerNoLink expiry (default: 24 hours)

Response (200 OK):

{
  "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:

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):

{
  "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:

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):

{
  "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:

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):

{
  "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:

FieldTypeDescription
ratestringExchange rate (1 USDC = X PKR)
spreadstringSaturnX fee/spread (0.5% = 0.005)
estimated_amountstringPKR amount recipient will receive
valid_untilstringRate validity expiration
rate_lockedbooleanWhether rate is locked

4.2 Request Exchange

Endpoint: POST /v1/sdk/orders

When to call: When customer initiates crypto-to-PKR conversion.

Request:

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:

FieldTypeRequiredDescription
order_typestringYesOrder type (EXCHANGE)
wallet_idstringYesSource wallet
amountstringYesAmount to convert
from_currencystringYesSource currency (USDC, USDT)
to_currencystringYesDestination currency (PKR)
bank_accountobjectYesPakistani bank account details
lock_ratebooleanNoLock rate for 15 minutes (default: false)

Response (200 OK):

{
  "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:

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:

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:

FieldTypeRequiredDescription
reasonstringYesReason code (see below)
detailsstringYesHuman-readable explanation
auto_unblock_hoursintegerNoAutomatic 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):

{
  "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:

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):

{
  "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:

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:

FieldTypeRequiredDescription
regulatory_authoritystringYesRegulatory body name
regulatory_reference_numberstringYesOfficial case/reference number
reasonstringYesBlacklist reason
effective_datestringYesISO date (YYYY-MM-DD)

Response (200 OK):

{
  "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:

{
  "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/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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "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:

{
  "wallet_id": "wlt_abc123xyz",
  "customer_id": "BANK_CUST_001",
  "created_at": "2025-11-19T10:00:00Z"
}

Common Data Types

Wallet

{
  "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

{
  "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

{
  "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:

{
  "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 StatusError CodeDescriptionAction
400INVALID_REQUESTInvalid input parametersFix request body
401UNAUTHORIZEDInvalid/missing JWT tokenRefresh authentication
403FORBIDDENInsufficient permissionsCheck user roles
404NOT_FOUNDResource not foundVerify IDs
409CONFLICTResource already existsHandle duplicate
422VALIDATION_FAILEDBusiness logic validation failedReview validation errors
429RATE_LIMIT_EXCEEDEDToo many requestsImplement backoff
500INTERNAL_ERRORServer errorRetry with exponential backoff
503SERVICE_UNAVAILABLETemporarily unavailableRetry later

Error Examples

Invalid Input:

{
  "error": "INVALID_REQUEST",
  "message": "customer_email is not a valid email address",
  "field": "customer_email",
  "request_id": "req_abc123xyz"
}

Wallet Not Found:

{
  "error": "NOT_FOUND",
  "message": "Wallet with ID wlt_abc123xyz not found",
  "field": "wallet_id",
  "request_id": "req_def456ghi"
}

Insufficient Balance:

{
  "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:

{
  "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 PatternRate LimitWindow
/v1/sdk/wallets/*100 req/minPer tenant
/v1/sdk/payment-requests/*200 req/minPer tenant
/v1/sdk/orders/*50 req/minPer tenant
/v1/sdk/transactions/*200 req/minPer tenant
Webhooks (incoming)Unlimited-

Rate Limit Headers

All responses include rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1735776000

Handling Rate Limits

Implement exponential backoff when rate limit is hit:

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:

{
  "wallet_id": "wlt_test_001",
  "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
  "blockchain": "ethereum",
  "balance": {
    "USDC": "10000.00",
    "USDT": "10000.00"
  }
}

Generate test payment links that automatically simulate sender payments:

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

TermDefinition
Bank-Custodied WalletCrypto wallet owned and controlled by bank on behalf of customer
Escrow WalletBank-owned wallet where payments arrive for risk checks before recipient
Payment LinkUnique URL allowing sender to pay recipient without knowing wallet address
Risk Check TierAmount-based compliance check level (Fast Track, Standard, Comprehensive)
BlockingTemporary wallet freeze by bank (platform-level)
BlacklistingPermanent wallet freeze by regulatory authority (on-chain USDC contract)
Travel RuleFATF requirement to exchange customer information for transactions >$1,000 USD
Whitelisted Custodial ProviderApproved sender wallet service (Binance, Coinbase, etc.)
MPC WalletMulti-Party Computation wallet (secure custody via DFNS)
PKRPakistani Rupee (fiat currency)
USDCUSD Coin (stablecoin)
USDTTether (stablecoin)