Skip to main content

StableX Complete API Reference

Use the On this page menu on the right to jump to any section.

1. API Overview

1.1 GraphQL Endpoint

POST https://your-domain.com/graphql

1.2 Headers

Content-Type: application/json
Authorization: Bearer <access_token>  (for authenticated requests)

1.3 Request Format

{
  "query": "mutation { ... }",
  "variables": { ... }
}

2. Authentication

Automatically detects if user is a Merchant or Staff member. Permissions: None (public) Request:
mutation Login($email: String!, $password: String!) {
  login(email: $email, password: $password) {
    access_token
    refresh_token
    expires_in
    user_type
  }
}
Variables:
{
  "email": "merchant@example.com",
  "password": "SecurePass123!"
}
Response (Merchant):
{
  "data": {
    "login": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": null,
      "expires_in": 3600,
      "user_type": "merchant"
    }
  }
}
Response (Staff):
{
  "data": {
    "login": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "def502004a3f5e...",
      "expires_in": 3600,
      "user_type": "staff"
    }
  }
}

2.2 Merchant Login (Legacy)

Permissions: None (public) Request:
mutation MerchantLogin($email: String!, $password: String!) {
  merchantLogin(email: $email, password: $password) {
    access_token
    token_type
  }
}
Variables:
{
  "email": "merchant@example.com",
  "password": "SecurePass123!"
}
Response:
{
  "data": {
    "merchantLogin": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "token_type": "Bearer"
    }
  }
}

2.3 Staff Login (Legacy)

Permissions: None (public) Request:
mutation StaffLogin($email: String!, $password: String!) {
  staffLogin(email: $email, password: $password) {
    access_token
    refresh_token
    token_type
    expires_in
  }
}
Variables:
{
  "email": "staff@example.com",
  "password": "SecurePass123!"
}
Response:
{
  "data": {
    "staffLogin": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "def502004a3f5e...",
      "token_type": "Bearer",
      "expires_in": 3600
    }
  }
}

2.4 Refresh Token

Get a new access token without re-entering credentials. Permissions: None (public, but requires valid refresh token) Request:
mutation RefreshToken($refreshToken: String!) {
  refreshToken(refreshToken: $refreshToken) {
    access_token
    refresh_token
    token_type
    expires_in
  }
}
Variables:
{
  "refreshToken": "def502004a3f5e..."
}
Response:
{
  "data": {
    "refreshToken": {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "refresh_token": "abc123004b4g6f...",
      "token_type": "Bearer",
      "expires_in": 3600
    }
  }
}

2.5 Request Password Reset

Permissions: None (public) Request:
mutation RequestPasswordReset($email: String!) {
  requestPasswordReset(email: $email)
}
Variables:
{
  "email": "user@example.com"
}
Response:
{
  "data": {
    "requestPasswordReset": true
  }
}

2.6 Reset Password

Permissions: None (public, but requires valid token) Request:
mutation ResetPassword($token: String!, $newPassword: String!) {
  resetPassword(token: $token, newPassword: $newPassword)
}
Variables:
{
  "token": "abc123def456...",
  "newPassword": "NewSecurePass123!"
}
Response:
{
  "data": {
    "resetPassword": true
  }
}

2.7 Get Current User (Me)

Permissions: Requires authentication Request:
query Me {
  me {
    ... on Merchant {
      id
      accountNumber
      email
      name
      merchant_category
      wallets {
        id
        address
        blockchain
        nickname
      }
    }
    ... on Staff {
      id
      email
      display_name
      roles
      is_active
      merchant {
        id
        name
      }
    }
  }
}
Response (Merchant):
{
  "data": {
    "me": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "email": "merchant@example.com",
      "name": "Coffee Shop",
      "merchant_category": "Food & Beverage",
      "wallets": [
        {
          "id": "1",
          "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
          "blockchain": "solana-devnet",
          "nickname": "Main Wallet"
        }
      ]
    }
  }
}
Response (Staff):
{
  "data": {
    "me": {
      "id": "5",
      "email": "cashier@example.com",
      "display_name": "John Doe",
      "roles": ["Cashier"],
      "is_active": true,
      "merchant": {
        "id": "1",
        "name": "Coffee Shop"
      }
    }
  }
}

3. Merchant Operations

3.1 Create Merchant (Sign Up)

Permissions: None (public) Request:
mutation CreateMerchant($name: String!, $email: String!, $password: String!) {
  createMerchant(name: $name, email: $email, password: $password) {
    id
    accountNumber
    name
    email
  }
}
Variables:
{
  "name": "My Coffee Shop",
  "email": "owner@coffeeshop.com",
  "password": "SecurePass123!"
}
Response:
{
  "data": {
    "createMerchant": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "name": "My Coffee Shop",
      "email": "owner@coffeeshop.com"
    }
  }
}

3.2 Update Merchant Profile

Permissions: merchant.profile.manage (Merchant only) Request:
mutation UpdateMerchantProfile($profile: MerchantProfileInput!) {
  updateMerchantProfile(profile: $profile) {
    id
    accountNumber
    name
    email
    tax_percentage
    merchant_category
    address {
      address_line
      city
      state
      country
    }
  }
}
Variables:
{
  "profile": {
    "name": "Updated Coffee Shop",
    "tax_percentage": 8.5,
    "merchant_category": "Food & Beverage",
    "address": {
      "address_line": "123 Main St",
      "city": "San Francisco",
      "state": "CA",
      "country": "USA"
    },
    "pricing_model_id": 2
  }
}
Response:
{
  "data": {
    "updateMerchantProfile": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "name": "Updated Coffee Shop",
      "email": "owner@coffeeshop.com",
      "tax_percentage": 8.5,
      "merchant_category": "Food & Beverage",
      "address": {
        "address_line": "123 Main St",
        "city": "San Francisco",
        "state": "CA",
        "country": "USA"
      }
    }
  }
}

3.3 Merchant Account Number

Every merchant is automatically assigned a unique 16-digit account number upon creation. Format: 9891 + 12 random digits Example: 9891123456789012 Features:
  • Automatically Generated: Created when a merchant signs up
  • Unique: Enforced by database constraint
  • Immutable: Cannot be changed after creation
  • Format Validation: Checked via database constraint (^9891[0-9]{12}$)
Use Cases:
  • Merchant identification
  • Account reconciliation
  • Integration with external systems
  • Customer invoicing
Querying Account Number:
query Me {
  me {
    ... on Merchant {
      id
      accountNumber
      name
      email
    }
  }
}
Response:
{
  "data": {
    "me": {
      "id": "1",
      "accountNumber": "9891123456789012",
      "name": "My Coffee Shop",
      "email": "owner@coffeeshop.com"
    }
  }
}

4. Staff Management

4.1 Create Staff

Permissions: staff.manage (Merchant or Manager with staff.manage permission) Request:
mutation CreateStaff(
  $merchantId: ID
  $email: String!
  $password: String!
  $displayName: String
  $roles: [String!]!
) {
  createStaff(
    merchantId: $merchantId
    email: $email
    password: $password
    displayName: $displayName
    roles: $roles
  ) {
    id
    email
    display_name
    roles
    is_active
  }
}
Variables:
{
  "merchantId": "1",
  "email": "cashier@coffeeshop.com",
  "password": "SecurePass123!",
  "displayName": "Jane Smith",
  "roles": ["Cashier"]
}
Response:
{
  "data": {
    "createStaff": {
      "id": "5",
      "email": "cashier@coffeeshop.com",
      "display_name": "Jane Smith",
      "roles": ["Cashier"],
      "is_active": true
    }
  }
}
Note: The merchantId parameter is optional:
  • If omitted, the merchant ID is automatically inferred from the authenticated user (works for both Merchant and Staff users)
  • If provided, it must match the authenticated user’s merchant ID (staff cannot create users for other merchants)
  • Can be passed as either a string ("1") or number (1) - both are accepted

4.2 List Staff

Permissions: staff.list Request:
query StaffList($merchantId: ID!) {
  staffList(merchantId: $merchantId) {
    id
    email
    display_name
    roles
    is_active
  }
}
Variables:
{
  "merchantId": "1"
}
Response:
{
  "data": {
    "staffList": [
      {
        "id": "5",
        "email": "cashier@coffeeshop.com",
        "display_name": "Jane Smith",
        "roles": ["Cashier"],
        "is_active": true
      },
      {
        "id": "6",
        "email": "manager@coffeeshop.com",
        "display_name": "Bob Johnson",
        "roles": ["Manager"],
        "is_active": true
      }
    ]
  }
}

4.3 Update Staff

Permissions: staff.manage Request:
mutation UpdateStaff(
  $id: ID!
  $displayName: String
  $roles: [String!]
  $isActive: Boolean
) {
  updateStaff(
    id: $id
    displayName: $displayName
    roles: $roles
    isActive: $isActive
  ) {
    id
    email
    display_name
    roles
    is_active
  }
}
Variables:
{
  "id": "5",
  "displayName": "Jane Doe",
  "roles": ["Manager"],
  "isActive": true
}
Response:
{
  "data": {
    "updateStaff": {
      "id": "5",
      "email": "cashier@coffeeshop.com",
      "display_name": "Jane Doe",
      "roles": ["Manager"],
      "is_active": true
    }
  }
}

4.4 Deactivate Staff

Permissions: staff.manage Request:
mutation DeactivateStaff($id: ID!) {
  deactivateStaff(id: $id)
}
Variables:
{
  "id": "5"
}
Response:
{
  "data": {
    "deactivateStaff": true
  }
}

4.5 Assign Role to Staff

Permissions: staff.manage Request:
mutation AssignRoleToStaff($staffId: ID!, $roleName: String!) {
  assignRoleToStaff(staffId: $staffId, roleName: $roleName) {
    id
    email
    roles
  }
}
Variables:
{
  "staffId": "5",
  "roleName": "Manager"
}
Response:
{
  "data": {
    "assignRoleToStaff": {
      "id": "5",
      "email": "cashier@coffeeshop.com",
      "roles": ["Manager", "Cashier"]
    }
  }
}

4.6 Get Merchant Info for Staff

Permissions: Requires authentication Request:
query GetMerchantInfoForStaff($staffEmail: String!) {
  getMerchantInfoForStaff(staffEmail: $staffEmail) {
    merchantId
    accountNumber
  }
}
Variables:
{
  "staffEmail": "cashier@coffeeshop.com"
}
Response:
{
  "data": {
    "getMerchantInfoForStaff": {
      "merchantId": "1",
      "accountNumber": "9891123456789012"
    }
  }
}

5. Payment Operations

5.1 Create Payment

Permissions: payment.create Request:
mutation CreatePayment(
  $amount: Decimal!
  $tipAmount: Decimal
  $blockchain: String!
  $tokenSymbol: String!
) {
  createPayment(
    amount: $amount
    tipAmount: $tipAmount
    blockchain: $blockchain
    tokenSymbol: $tokenSymbol
  ) {
    url
    qr_code
    transaction {
      id
      reference
      amount
      tip_amount
      status
      timestamp
      tokenSymbol
    }
  }
}
Variables:
{
  "amount": "25.50",
  "blockchain": "solana-devnet",
  "tokenSymbol": "USDC"
}
Response:
{
  "data": {
    "createPayment": {
      "url": "solana:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK?amount=25.50&spl-token=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU&reference=abc123...",
      "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
      "transaction": {
        "id": "42",
        "reference": "abc123def456...",
        "amount": "25.50",
        "status": "pending",
        "timestamp": "2024-01-15T10:30:00Z",
        "tokenSymbol": "USDC"
      }
    }
  }
}

5.2 Regenerate Payment

Permissions: payment.create Request:
mutation RegeneratePayment($reference: String!) {
  regeneratePayment(reference: $reference) {
    url
    qr_code
    transaction {
      id
      reference
      amount
      status
    }
  }
}
Variables:
{
  "reference": "abc123def456..."
}
Response:
{
  "data": {
    "regeneratePayment": {
      "url": "solana:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK?amount=25.50...",
      "qr_code": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
      "transaction": {
        "id": "42",
        "reference": "abc123def456...",
        "amount": "25.50",
        "status": "pending"
      }
    }
  }
}

Creates a shareable payment link: a pending transaction, invoice, and a unique slug. The returned url is the public checkout URL (e.g. https://your-domain.com/pay/<slug>). No authentication is required for customers to open that URL and pay. Permissions: payment.create Request:
mutation CreatePaymentLink(
  $amount: Decimal!
  $tokenSymbol: String!
  $blockchain: String!
  $tipAmount: Decimal
  $description: String
  $expiresInHours: Int
) {
  createPaymentLink(
    amount: $amount
    tokenSymbol: $tokenSymbol
    blockchain: $blockchain
    tipAmount: $tipAmount
    description: $description
    expiresInHours: $expiresInHours
  ) {
    url
    slug
    transaction {
      id
      reference
      amount
      tip_amount
      status
      tokenSymbol
    }
  }
}
Variables:
{
  "amount": "25.50",
  "tokenSymbol": "USDC",
  "blockchain": "solana-devnet",
  "tipAmount": "2.00",
  "description": "Order #1234",
  "expiresInHours": 24
}
Response:
{
  "data": {
    "createPaymentLink": {
      "url": "https://your-domain.com/pay/a1b2c3d4e5f6",
      "slug": "a1b2c3d4e5f6",
      "transaction": {
        "id": "101",
        "reference": "8Kj3mNxPq7Rs...",
        "amount": "25.50",
        "tip_amount": "2.00",
        "status": "pending",
        "tokenSymbol": "USDC"
      }
    }
  }
}
  • url: Public URL to share with the customer. Opening it returns checkout JSON (see 5.4).
  • slug: Unique identifier for the link (also used in url).
  • expiresInHours: Optional. If set, the link is considered expired after that many hours.

Authentication: None (public endpoint for customers). Returns JSON for the checkout UI: merchant name, amount, token, network, payment URL (for QR / Solana Pay), and transaction reference. Used when a customer opens a payment link (e.g. https://your-domain.com/pay/<slug>). Endpoint:
GET https://your-domain.com/pay/{slug}
Example:
curl https://your-domain.com/pay/a1b2c3d4e5f6
Response (200 OK):
{
  "merchant_name": "Acme Coffee",
  "amount": "25.50",
  "tip_amount": "2.00",
  "token_symbol": "USDC",
  "network": "solana-devnet",
  "payment_url": "solana:DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK?amount=27.50&...",
  "transaction_reference": "8Kj3mNxPq7Rs...",
  "slug": "a1b2c3d4e5f6"
}
  • payment_url: Use this for QR code or “Pay with Solana” / wallet deep link.
  • transaction_reference: Use with transactionStatus(reference: ...) to poll for confirmation.
Errors:
  • 404: Payment link not found, expired, or already used (transaction confirmed).

6. Transaction & Reporting

6.1 List Transactions

Permissions: transaction.view Request:
query Transactions($limit: Int, $offset: Int) {
  transactions(limit: $limit, offset: $offset) {
    transactions {
      id
      reference
      amount
      status
      timestamp
      tokenSymbol
      transaction_hash
    }
    totalCount
  }
}
Variables:
{
  "limit": 10,
  "offset": 0
}
Response:
{
  "data": {
    "transactions": {
      "transactions": [
        {
          "id": "42",
          "reference": "abc123def456...",
          "amount": "25.50",
          "status": "confirmed",
          "timestamp": "2024-01-15T10:30:00Z",
          "tokenSymbol": "USDC",
          "transaction_hash": "5g4h8j9k2l3m4n5o6p7q8r9s0t1u2v3w..."
        }
      ],
      "totalCount": 42
    }
  }
}

6.2 Transaction Status

Permissions: transaction.view Request:
query TransactionStatus($reference: String!) {
  transactionStatus(reference: $reference) {
    id
    reference
    amount
    status
    timestamp
    transaction_hash
  }
}
Variables:
{
  "reference": "abc123def456..."
}
Response:
{
  "data": {
    "transactionStatus": {
      "id": "42",
      "reference": "abc123def456...",
      "amount": "25.50",
      "status": "confirmed",
      "timestamp": "2024-01-15T10:30:00Z",
      "transaction_hash": "5g4h8j9k2l3m4n5o6p7q8r9s0t1u2v3w..."
    }
  }
}

6.3 Daily Summary

Permissions: report.view Request:
query DailySummary($timeframe: ReportTimeframe) {
  dailySummary(timeframe: $timeframe) {
    date
    totalSales
    transactionCount
    averageTransactionValue
  }
}
Variables:
{
  "timeframe": "DAILY"
}
Response:
{
  "data": {
    "dailySummary": [
      {
        "date": "2024-01-15",
        "totalSales": "1250.00",
        "transactionCount": 42,
        "averageTransactionValue": "29.76"
      }
    ]
  }
}

6.4 Sales Summary Report

Permissions: report.view Request:
query SalesSummaryReport(
  $timeframe: ReportTimeframe!
  $startDate: String
  $endDate: String
) {
  salesSummaryReport(
    timeframe: $timeframe
    startDate: $startDate
    endDate: $endDate
  ) {
    totalSales
    transactionCount
    averageTransactionValue
    reportPeriod
  }
}
Variables:
{
  "timeframe": "MONTHLY",
  "startDate": "2024-01-01",
  "endDate": "2024-01-31"
}
Response:
{
  "data": {
    "salesSummaryReport": {
      "totalSales": "12500.00",
      "transactionCount": 420,
      "averageTransactionValue": "29.76",
      "reportPeriod": "2024-01 (Monthly)"
    }
  }
}

6.5 Sales Analytics

Permissions: report.view Request:
query SalesAnalytics($timeframe: String!) {
  salesAnalytics(timeframe: $timeframe) {
    date
    totalSales
  }
}
Variables:
{
  "timeframe": "weekly"
}
Response:
{
  "data": {
    "salesAnalytics": [
      {
        "date": "2024-01-15",
        "totalSales": "1250.00"
      },
      {
        "date": "2024-01-16",
        "totalSales": "1580.50"
      }
    ]
  }
}

6.6 Sales By Hour

Permissions: report.view Request:
query SalesByHour {
  salesByHour {
    hour
    totalSales
  }
}
Response:
{
  "data": {
    "salesByHour": [
      {
        "hour": 9,
        "totalSales": "150.00"
      },
      {
        "hour": 10,
        "totalSales": "320.50"
      }
    ]
  }
}

6.7 PDF Downloads (Invoices & Receipts)

StableX provides two types of PDF documents:
  • Invoices: For pending/unpaid transactions (blue header, red “PAYMENT PENDING” stamp)
  • Receipts: For confirmed/paid transactions (green header, green “PAID” stamp)

6.7.1 Get Invoice Info (GraphQL)

Get the download URL and QR code for an invoice PDF. Permissions: Authenticated (merchant or staff) Request:
query GetInvoiceInfo($reference: String!) {
  getInvoiceInfo(reference: $reference) {
    url
    qr_code
  }
}
Variables:
{
  "reference": "abc123def456..."
}
Response:
{
  "data": {
    "getInvoiceInfo": {
      "url": "https://your-domain.com/invoices/abc123def456.../download",
      "qr_code": "iVBORw0KGgoAAAANSUhEUgAA..." 
    }
  }
}

6.7.2 Get Receipt Info (GraphQL)

Get the download URL and QR code for a receipt PDF. Permissions: Authenticated (merchant or staff) Request:
query GetReceiptInfo($reference: String!) {
  getReceiptInfo(reference: $reference) {
    url
    qr_code
  }
}
Variables:
{
  "reference": "abc123def456..."
}
Response:
{
  "data": {
    "getReceiptInfo": {
      "url": "https://your-domain.com/receipts/abc123def456.../download",
      "qr_code": "iVBORw0KGgoAAAANSUhEUgAA..."
    }
  }
}

6.7.3 Download Invoice PDF (REST API)

Download the invoice PDF directly. Endpoint: GET /invoices/{transaction_reference}/download Authentication: Bearer token required Request:
curl -X GET https://your-domain.com/invoices/abc123def456.../download \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -o invoice.pdf
Response: PDF file (application/pdf) Status Codes:
  • 200 OK: PDF generated successfully
  • 404 Not Found: Transaction not found
  • 401 Unauthorized: Invalid or missing token
  • 500 Internal Server Error: PDF generation failed

6.7.4 Download Receipt PDF (REST API)

Download the receipt PDF directly (only for confirmed transactions). Endpoint: GET /receipts/{transaction_reference}/download Authentication: Bearer token required Request:
curl -X GET https://your-domain.com/receipts/abc123def456.../download \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -o receipt.pdf
Response: PDF file (application/pdf) Status Codes:
  • 200 OK: PDF generated successfully
  • 404 Not Found: Transaction not found or not confirmed
  • 401 Unauthorized: Invalid or missing token
  • 500 Internal Server Error: PDF generation failed
Note: Receipts can only be downloaded for transactions with status confirmed. For pending transactions, use the invoice endpoint instead.

6.7.5 Invoice Query with Transaction Reference

When querying invoices, include the transaction relationship to get the reference needed for PDF downloads. Invoices can be in lifecycle states: draft, sent, pending, paid, overdue, or void. Draft and sent invoices may have transaction_id: null until the customer pays. Request:
query GetInvoices($limit: Int, $offset: Int) {
  invoices(limit: $limit, offset: $offset) {
    invoices {
      id
      amount
      tax_amount
      status
      creation_date
      invoice_number
      due_date
      customer_email
      sent_at
      last_reminder_at
      transaction_id
      transaction {
        reference
      }
      line_items {
        id
        description
        quantity
        unit_amount
        amount
      }
    }
    totalCount
  }
}
Response:
{
  "data": {
    "invoices": {
      "invoices": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440000",
          "amount": "100.00",
          "tax_amount": "10.00",
          "status": "draft",
          "creation_date": "2024-01-15T10:30:00Z",
          "invoice_number": "INV-0001",
          "due_date": "2024-02-01T00:00:00Z",
          "customer_email": "customer@example.com",
          "sent_at": null,
          "last_reminder_at": null,
          "transaction_id": null,
          "transaction": null,
          "line_items": [
            {
              "id": "1",
              "description": "Item A",
              "quantity": "2",
              "unit_amount": "25.00",
              "amount": "50.00"
            }
          ]
        }
      ],
      "totalCount": 1
    }
  }
}
Usage: For paid (or sent) invoices with a transaction, use transaction.reference to construct the download URL:
/invoices/{transaction.reference}/download

6.8 Invoice Lifecycle (Draft, Send, Remind)

Invoices support a full lifecycle: create as draft, send to the customer (email + payment link), then paid when the customer pays. Optional overdue status and reminders are supported. Invoice statuses: draft | sent | pending | paid | overdue | void
  • draft: Created without a transaction; can be updated.
  • sent: Payment link created and (optionally) email sent; transaction_id and sent_at set.
  • pending: Legacy: invoice tied to a transaction not yet confirmed.
  • paid: Payment confirmed; payment_date set.
  • overdue: due_date has passed and status was sent.
  • void: Cancelled.

6.8.1 Create Invoice (Draft)

Create an invoice in draft state with no transaction. Optionally include line items, due date, and customer email (required later for sending). Permissions: Authenticated (merchant or staff) Request:
mutation CreateInvoice(
  $amount: Decimal!
  $taxAmount: Decimal!
  $description: String
  $dueDate: String
  $customerEmail: String
  $lineItems: [InvoiceLineItemInput!]
) {
  createInvoice(
    amount: $amount
    taxAmount: $taxAmount
    description: $description
    dueDate: $dueDate
    customerEmail: $customerEmail
    lineItems: $lineItems
  ) {
    id
    status
    amount
    tax_amount
    invoice_number
    due_date
    customer_email
    transaction_id
    line_items {
      id
      description
      quantity
      unit_amount
      amount
    }
  }
}
Variables:
{
  "amount": "150.00",
  "taxAmount": "15.00",
  "description": "Consulting services",
  "dueDate": "2025-03-15T00:00:00Z",
  "customerEmail": "client@example.com",
  "lineItems": [
    {
      "description": "Hourly consulting",
      "quantity": "10",
      "unit_amount": "15.00",
      "amount": "150.00"
    }
  ]
}
Response:
{
  "data": {
    "createInvoice": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "draft",
      "amount": "150.00",
      "tax_amount": "15.00",
      "invoice_number": "INV-0001",
      "due_date": "2025-03-15T00:00:00Z",
      "customer_email": "client@example.com",
      "transaction_id": null,
      "line_items": [
        {
          "id": "1",
          "description": "Hourly consulting",
          "quantity": "10",
          "unit_amount": "15.00",
          "amount": "150.00"
        }
      ]
    }
  }
}
Input type InvoiceLineItemInput:
FieldTypeRequiredDescription
descriptionString!YesLine item description
quantityDecimal!YesQuantity
unit_amountDecimal!YesPrice per unit
amountDecimal!YesTotal (e.g. quantity × unit)
productIdIDNoOptional link to product

6.8.2 Update Invoice (Draft Only)

Update a draft invoice: amount, tax, description, due date, customer email, and/or line items. Replacing lineItems overwrites all existing line items. Permissions: Authenticated (merchant or staff) Request:
mutation UpdateInvoice(
  $invoiceId: ID!
  $amount: Decimal
  $taxAmount: Decimal
  $description: String
  $dueDate: String
  $customerEmail: String
  $lineItems: [InvoiceLineItemInput!]
) {
  updateInvoice(
    invoiceId: $invoiceId
    amount: $amount
    taxAmount: $taxAmount
    description: $description
    dueDate: $dueDate
    customerEmail: $customerEmail
    lineItems: $lineItems
  ) {
    id
    status
    amount
    description
    due_date
    customer_email
    line_items { id description quantity unit_amount amount }
  }
}
Variables (example: update amount and due date only):
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
  "amount": "200.00",
  "dueDate": "2025-03-20T00:00:00Z"
}
Response: Returns the updated Invoice object. If the invoice is not in draft status, an error is returned.

6.8.3 Send Invoice

Moves an invoice from draft to sent: creates a pending transaction and payment link, sets sent_at, and optionally sends an email to customer_email with the payment URL. The customer can open the link to pay; when payment is confirmed, the invoice is marked paid. Permissions: Authenticated (merchant or staff) Requirements: Invoice must be draft and have customer_email set. You must have a wallet for the given blockchain. Request:
mutation SendInvoice(
  $invoiceId: ID!
  $blockchain: String!
  $tokenSymbol: String!
) {
  sendInvoice(
    invoiceId: $invoiceId
    blockchain: $blockchain
    tokenSymbol: $tokenSymbol
  ) {
    success
    message
    url
    invoice {
      id
      status
      sent_at
      transaction_id
    }
  }
}
Variables:
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000",
  "blockchain": "solana-devnet",
  "tokenSymbol": "USDC"
}
Response:
{
  "data": {
    "sendInvoice": {
      "success": true,
      "message": "Invoice sent",
      "url": "https://your-domain.com/pay/a1b2c3d4e5f6",
      "invoice": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "status": "sent",
        "sent_at": "2025-02-11T14:30:00Z",
        "transaction_id": "42"
      }
    }
  }
}
Notes:
  • url is the public payment link; share it or rely on the email sent to customer_email.
  • If the email fails, the invoice is still marked sent and the mutation succeeds; the url can be shared manually.

6.8.4 Send Invoice Reminder

Sends a reminder email for an invoice in sent or overdue status and sets last_reminder_at. The email includes the same payment link as when the invoice was sent. Permissions: Authenticated (merchant or staff) Request:
mutation SendInvoiceReminder($invoiceId: ID!) {
  sendInvoiceReminder(invoiceId: $invoiceId) {
    id
    status
    last_reminder_at
  }
}
Variables:
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000"
}
Response:
{
  "data": {
    "sendInvoiceReminder": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "overdue",
      "last_reminder_at": "2025-02-11T15:00:00Z"
    }
  }
}
Errors: Invoice must be in sent or overdue and have customer_email; the invoice must have an associated payment link (transaction).

6.8.5 Sync Invoice Status

Manually sync an invoice’s status with its linked transaction (e.g. after payment confirmation). Useful if automatic status updates did not run. The invoice must already have a transaction_id (e.g. created via sendInvoice or a payment link). Permissions: Authenticated (merchant or staff) Request:
mutation SyncInvoiceStatus($invoiceId: ID!) {
  syncInvoiceStatus(invoiceId: $invoiceId) {
    id
    status
    payment_date
    transaction_id
  }
}
Variables:
{
  "invoiceId": "550e8400-e29b-41d4-a716-446655440000"
}
Response: Returns the updated Invoice. If the linked transaction is confirmed on-chain, the invoice is updated to paid and payment_date is set.

7. Wallet Management

7.1 Add or Update Wallet

Permissions: wallet.manage Request:
mutation AddOrUpdateWallet(
  $address: String!
  $blockchain: String!
  $nickname: String
) {
  addOrUpdateWallet(
    address: $address
    blockchain: $blockchain
    nickname: $nickname
  ) {
    id
    address
    blockchain
    nickname
  }
}
Variables:
{
  "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
  "blockchain": "solana-devnet",
  "nickname": "Main Solana Wallet"
}
Response:
{
  "data": {
    "addOrUpdateWallet": {
      "id": "1",
      "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
      "blockchain": "solana-devnet",
      "nickname": "Main Solana Wallet"
    }
  }
}

7.2 Remove Wallet

Permissions: wallet.manage Request:
mutation RemoveWallet($walletId: ID!) {
  removeWallet(walletId: $walletId)
}
Variables:
{
  "walletId": "1"
}
Response:
{
  "data": {
    "removeWallet": true
  }
}

7.3 Wallet Balances

Permissions: wallet.view Request:
query WalletBalances {
  walletBalances {
    address
    blockchain
    nickname
    totalBalance
    breakdown {
      USDC
      USDT
    }
  }
}
Response:
{
  "data": {
    "walletBalances": [
      {
        "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
        "blockchain": "solana-devnet",
        "nickname": "Main Wallet",
        "totalBalance": "1250.50",
        "breakdown": {
          "USDC": "850.00",
          "USDT": "400.50"
        }
      }
    ]
  }
}

7.4 Token Balances

Permissions: wallet.view Request:
query TokenBalances(
  $blockchain: String!
  $walletAddress: String!
  $tokenSymbols: [String!]!
) {
  tokenBalances(
    blockchain: $blockchain
    walletAddress: $walletAddress
    tokenSymbols: $tokenSymbols
  ) {
    tokenSymbol
    balance
  }
}
Variables:
{
  "blockchain": "solana-devnet",
  "walletAddress": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
  "tokenSymbols": ["USDC", "USDT"]
}
Response:
{
  "data": {
    "tokenBalances": [
      {
        "tokenSymbol": "USDC",
        "balance": "850.00"
      },
      {
        "tokenSymbol": "USDT",
        "balance": "400.50"
      }
    ]
  }
}

8. Customer Management

8.1 List Customers

Permissions: customer.view Request:
query Customers {
  customers {
    id
    wallet_address
    name
    email
    phone_number
  }
}
Response:
{
  "data": {
    "customers": [
      {
        "id": "10",
        "wallet_address": "7xKs9mPqR8TuV3Wy0Zd5AcYb2MeFiHgF7Vj6Pu9Xq1Rs4",
        "name": "John Doe",
        "email": "john@example.com",
        "phone_number": "+1234567890"
      }
    ]
  }
}

8.2 Upsert Customer

Permissions: customer.manage Request:
mutation UpsertCustomer(
  $wallet_address: String!
  $details: CustomerUpdate!
) {
  upsertCustomer(
    wallet_address: $wallet_address
    details: $details
  ) {
    id
    wallet_address
    name
    email
    phone_number
  }
}
Variables:
{
  "wallet_address": "7xKs9mPqR8TuV3Wy0Zd5AcYb2MeFiHgF7Vj6Pu9Xq1Rs4",
  "details": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone_number": "+1987654321"
  }
}
Response:
{
  "data": {
    "upsertCustomer": {
      "id": "11",
      "wallet_address": "7xKs9mPqR8TuV3Wy0Zd5AcYb2MeFiHgF7Vj6Pu9Xq1Rs4",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "phone_number": "+1987654321"
    }
  }
}

9. Error Handling

9.1 Error Response Format

{
  "errors": [
    {
      "message": "Access denied. Required permission: payment.create",
      "extensions": {
        "code": "PERMISSION_DENIED",
        "details": {
          "required_permission": "payment.create"
        }
      }
    }
  ]
}

9.2 Standard Error Codes

CodeHTTP StatusDescriptionAction
NOT_AUTHENTICATED401Missing or invalid tokenRedirect to login
PERMISSION_DENIED403User lacks required permissionShow access denied message
INVALID_CREDENTIALS401Wrong email/passwordShow login error
TOKEN_EXPIRED401Access token expiredCall refreshToken
TOKEN_REVOKED401Refresh token revokedRedirect to login
RECORD_NOT_FOUND404Resource doesn’t existShow 404 state
VALIDATION_ERROR400Invalid input dataShow validation errors
INTERNAL_ERROR500Server errorShow generic error

9.3 Common Error Scenarios

1. Expired Token
{
  "errors": [{
    "message": "Not authenticated",
    "extensions": { "code": "NOT_AUTHENTICATED" }
  }]
}
Solution: Call refreshToken mutation or redirect to login 2. Permission Denied
{
  "errors": [{
    "message": "Access denied. Required permission: staff.manage",
    "extensions": {
      "code": "PERMISSION_DENIED",
      "details": { "required_permission": "staff.manage" }
    }
  }]
}
Solution: Show user-friendly “You don’t have permission” message 3. Validation Error
{
  "errors": [{
    "message": "Email already registered",
    "extensions": {
      "code": "VALIDATION_ERROR",
      "details": { "field": "email" }
    }
  }]
}
Solution: Highlight the email field and show error

10. Refund Operations

10.1 Create Refund Request

Permissions: refund.create (Staff Manager) Request:
mutation CreateRefund($transactionReference: String!, $refundAmount: Decimal!, $reason: String!) {
    createRefundRequest(
        transactionReference: $transactionReference
        refundAmount: $refundAmount
        reason: $reason
    ) {
        id
        refundReference
        status
    }
}
Variables:
{
  "transactionReference": "TX_FULL_REFUND_UNIQUE_123",
  "refundAmount": "50.00",
  "reason": "Test reason"
}
Response:
{
  "data": {
    "createRefundRequest": {
      "id": "1",
      "refundReference": "REF123",
      "status": "pending"
    }
  }
}

10.2 Approve Refund

Permissions: refund.approve (Staff Manager) Request:
mutation ApproveRefund($refundReference: String!) {
    approveRefund(refundReference: $refundReference) {
        id
        status
    }
}
Variables:
{
  "refundReference": "REF_TEST_APPROVE"
}
Response:
{
  "data": {
    "approveRefund": {
      "id": "1",
      "status": "approved"
    }
  }
}

10.3 Reject Refund

Permissions: refund.reject (Staff Manager) Request:
mutation RejectRefund($refundReference: String!, $rejectionReason: String!) {
    rejectRefund(refundReference: $refundReference, rejectionReason: $rejectionReason) {
        id
        status
    }
}
Variables:
{
  "refundReference": "REF-123",
  "rejectionReason": "Invalid request"
}
Response:
{
  "data": {
    "rejectRefund": {
      "id": "1",
      "status": "rejected"
    }
  }
}

10.4 Complete Refund

Permissions: refund.complete (Merchant) Request:
mutation CompleteRefund($refundReference: String!, $transactionHash: String) {
    completeRefund(refundReference: $refundReference, transactionHash: $transactionHash) {
        id
        status
        refundTransactionHash
    }
}
Variables:
{
  "refundReference": "REF_COMPLETE_TEST",
  "transactionHash": "tx_hash_123"
}
Response:
{
  "data": {
    "completeRefund": {
      "id": "1",
      "status": "completed",
      "refundTransactionHash": "tx_hash_123"
    }
  }
}

10.5 List Refunds

Permissions: refund.view (Staff Manager) Request:
query Refunds($status: String) {
    refunds(status: $status) {
        refunds {
            id
            refundReference
            refundAmount
            status
        }
        totalCount
    }
}
Variables (optional):
{
  "status": "approved"
}
Response:
{
  "data": {
    "refunds": {
      "refunds": [
        {
          "id": "6",
          "refundReference": "REF_FILTER_2",
          "refundAmount": "20.00",
          "status": "approved"
        }
      ],
      "totalCount": 1
    }
  }
}

10.6 Get Refund by Reference

Permissions: refund.view (Staff Manager) Request:
query RefundByReference($reference: String!) {
    refundByReference(refundReference: $reference) {
        id
        refundReference
        status
    }
}
Variables:
{
  "reference": "REF_BY_REFERENCE"
}
Response:
{
  "data": {
    "refundByReference": {
      "id": "100",
      "refundReference": "REF_BY_REFERENCE",
      "status": "pending"
    }
  }
}

10.7 Get Refund Summary

Permissions: refund.view (Staff Manager) Request:
query RefundSummary {
    refundSummary {
        totalRefunded
        refundCount
        completedRefunds
    }
}
Response:
{
  "data": {
    "refundSummary": {
      "totalRefunded": "50.00",
      "refundCount": 1,
      "completedRefunds": 1
    }
  }
}

11. Product & Price Management

11.1 List Products

Permissions: product.view Request:
query GetProducts($active: Boolean, $category: String, $limit: Int, $offset: Int) {
    products(active: $active, category: $category, limit: $limit, offset: $offset) {
        products {
            id
            name
            description
            active
            sku
            productCode
            category
            tags
            archivedAt
            images
            createdAt
        }
        totalCount
    }
}
Variables:
{
  "active": true,
  "category": "Electronics",
  "limit": 10,
  "offset": 0
}
Response:
{
  "data": {
    "products": {
      "products": [
        {
          "id": "1",
          "name": "Premium Coffee Beans",
          "description": "High-quality arabica coffee beans",
          "active": true,
          "sku": "COFFEE-001",
          "productCode": "PC-001",
          "category": "Beverages",
          "tags": ["coffee", "premium", "arabica"],
          "archivedAt": null,
          "images": ["https://example.com/coffee.jpg"],
          "createdAt": "2024-01-15T10:30:00Z"
        }
      ],
      "totalCount": 25
    }
  }
}
Note: The active filter can be used to show only active or inactive products. The category filter allows filtering by product category. Pagination is supported via limit and offset parameters.

11.2 Get Product by ID

Permissions: product.view Request:
query GetProduct($id: ID!) {
    product(id: $id) {
        id
        merchantId
        name
        description
        active
        sku
        productCode
        category
        tags
        archivedAt
        images
        metadata
        prices {
            id
            amount
            currency
            type
            active
        }
        createdAt
        updatedAt
    }
}
Variables:
{
  "id": "1"
}
Response:
{
  "data": {
    "product": {
      "id": "1",
      "merchantId": "1",
      "name": "Premium Coffee Beans",
      "description": "High-quality arabica coffee beans",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Beverages",
      "tags": ["coffee", "premium", "arabica"],
      "archivedAt": null,
      "images": ["https://example.com/coffee.jpg"],
      "metadata": {
        "origin": "Colombia",
        "roast": "Medium"
      },
      "prices": [
        {
          "id": "1",
          "amount": "29.99",
          "currency": "USD",
          "type": "ONE_TIME",
          "active": true
        }
      ],
      "createdAt": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
  }
}

11.3 Get Product by SKU

Permissions: product.view Request:
query GetProductBySku($sku: String!) {
    productBySku(sku: $sku) {
        id
        name
        description
        active
        sku
        productCode
        category
        tags
        archivedAt
    }
}
Variables:
{
  "sku": "COFFEE-001"
}
Response:
{
  "data": {
    "productBySku": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "description": "High-quality arabica coffee beans",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Beverages",
      "tags": ["coffee", "premium", "arabica"],
      "archivedAt": null
    }
  }
}
Note: SKU must be unique per merchant. If a product with the given SKU is not found, an error will be returned.

11.4 Get Product Prices

Permissions: product.view Request:
query GetProductPrices($productId: ID!) {
    productPrices(productId: $productId) {
        id
        amount
        currency
        type
        billingPeriod
        active
        nickname
        description
        pricingType
        minAmount
        maxAmount
        trialPeriodDays
        usageType
        unitLabel
        billingScheme
        taxBehavior
        taxCode
        createdAt
    }
}
Variables:
{
  "productId": "1"
}
Response:
{
  "data": {
    "productPrices": [
      {
        "id": "1",
        "amount": "29.99",
        "currency": "USD",
        "type": "ONE_TIME",
        "billingPeriod": null,
        "active": true,
        "nickname": "Standard Price",
        "description": "One-time purchase price",
        "pricingType": "FIXED",
        "minAmount": null,
        "maxAmount": null,
        "trialPeriodDays": null,
        "usageType": null,
        "unitLabel": null,
        "billingScheme": null,
        "taxBehavior": "EXCLUSIVE",
        "taxCode": null,
        "createdAt": "2024-01-15T10:30:00Z"
      },
      {
        "id": "2",
        "amount": "9.99",
        "currency": "USD",
        "type": "RECURRING",
        "billingPeriod": "month",
        "active": true,
        "nickname": "Monthly Subscription",
        "description": "Monthly recurring subscription",
        "pricingType": "FIXED",
        "minAmount": null,
        "maxAmount": null,
        "trialPeriodDays": 7,
        "usageType": null,
        "unitLabel": null,
        "billingScheme": null,
        "taxBehavior": "EXCLUSIVE",
        "taxCode": null,
        "createdAt": "2024-01-15T10:30:00Z"
      }
    ]
  }
}

11.5 Create Product

Permissions: product.manage Request:
mutation CreateProduct(
    $name: String!
    $description: String
    $active: Boolean
    $metadata: JSON
    $images: [String!]
    $sku: String
    $productCode: String
    $category: String
    $tags: [String!]
) {
    createProduct(
        name: $name
        description: $description
        active: $active
        metadata: $metadata
        images: $images
        sku: $sku
        productCode: $productCode
        category: $category
        tags: $tags
    ) {
        id
        name
        description
        active
        sku
        productCode
        category
        tags
        images
        createdAt
    }
}
Variables:
{
  "name": "Premium Coffee Beans",
  "description": "High-quality arabica coffee beans from Colombia",
  "active": true,
  "sku": "COFFEE-001",
  "productCode": "PC-001",
  "category": "Beverages",
  "tags": ["coffee", "premium", "arabica"],
  "images": ["https://example.com/coffee.jpg"],
  "metadata": {
    "origin": "Colombia",
    "roast": "Medium"
  }
}
Response:
{
  "data": {
    "createProduct": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "description": "High-quality arabica coffee beans from Colombia",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Beverages",
      "tags": ["coffee", "premium", "arabica"],
      "images": ["https://example.com/coffee.jpg"],
      "createdAt": "2024-01-15T10:30:00Z"
    }
  }
}
Note: The name field is required. All other fields are optional. The sku must be unique per merchant. If a product with the same SKU already exists, an error will be returned.

11.6 Update Product

Permissions: product.manage Request:
mutation UpdateProduct(
    $id: ID!
    $name: String
    $description: String
    $active: Boolean
    $metadata: JSON
    $images: [String!]
    $sku: String
    $productCode: String
    $category: String
    $tags: [String!]
) {
    updateProduct(
        id: $id
        name: $name
        description: $description
        active: $active
        metadata: $metadata
        images: $images
        sku: $sku
        productCode: $productCode
        category: $category
        tags: $tags
    ) {
        id
        name
        description
        active
        sku
        productCode
        category
        tags
        updatedAt
    }
}
Variables:
{
  "id": "1",
  "name": "Updated Coffee Beans",
  "description": "Updated description",
  "category": "Premium Beverages",
  "tags": ["coffee", "premium", "arabica", "organic"]
}
Response:
{
  "data": {
    "updateProduct": {
      "id": "1",
      "name": "Updated Coffee Beans",
      "description": "Updated description",
      "active": true,
      "sku": "COFFEE-001",
      "productCode": "PC-001",
      "category": "Premium Beverages",
      "tags": ["coffee", "premium", "arabica", "organic"],
      "updatedAt": "2024-01-16T14:20:00Z"
    }
  }
}
Note: Only the id field is required. All other fields are optional and will only be updated if provided. If updating the sku, it must remain unique per merchant.

11.7 Delete Product

Permissions: product.manage Request:
mutation DeleteProduct($id: ID!) {
    deleteProduct(id: $id)
}
Variables:
{
  "id": "1"
}
Response:
{
  "data": {
    "deleteProduct": true
  }
}
Note: This performs a soft delete by setting active=false. The product record remains in the database but is marked as inactive.

11.8 Archive Product

Permissions: product.manage Request:
mutation ArchiveProduct($id: ID!) {
    archiveProduct(id: $id) {
        id
        name
        archivedAt
    }
}
Variables:
{
  "id": "1"
}
Response:
{
  "data": {
    "archiveProduct": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "archivedAt": "2024-01-16T14:20:00Z"
    }
  }
}
Note: Archiving a product sets the archivedAt timestamp. Archived products can be filtered out in queries and can be unarchived later.

11.9 Unarchive Product

Permissions: product.manage Request:
mutation UnarchiveProduct($id: ID!) {
    unarchiveProduct(id: $id) {
        id
        name
        archivedAt
    }
}
Variables:
{
  "id": "1"
}
Response:
{
  "data": {
    "unarchiveProduct": {
      "id": "1",
      "name": "Premium Coffee Beans",
      "archivedAt": null
    }
  }
}
Note: Unarchiving a product clears the archivedAt timestamp, making the product available again.

11.10 Create Price

Permissions: product.manage Request:
mutation CreatePrice(
    $productId: ID!
    $amount: Decimal!
    $currency: String!
    $type: PriceType!
    $billingPeriod: String
    $active: Boolean
    $metadata: JSON
    $nickname: String
    $description: String
    $pricingType: PricingType
    $minAmount: Decimal
    $maxAmount: Decimal
    $trialPeriodDays: Int
    $usageType: UsageType
    $unitLabel: String
    $billingScheme: BillingScheme
    $taxBehavior: TaxBehavior
    $taxCode: String
) {
    createPrice(
        productId: $productId
        amount: $amount
        currency: $currency
        type: $type
        billingPeriod: $billingPeriod
        active: $active
        metadata: $metadata
        nickname: $nickname
        description: $description
        pricingType: $pricingType
        minAmount: $minAmount
        maxAmount: $maxAmount
        trialPeriodDays: $trialPeriodDays
        usageType: $usageType
        unitLabel: $unitLabel
        billingScheme: $billingScheme
        taxBehavior: $taxBehavior
        taxCode: $taxCode
    ) {
        id
        amount
        currency
        type
        billingPeriod
        active
        nickname
        description
        pricingType
        minAmount
        maxAmount
        trialPeriodDays
        usageType
        unitLabel
        billingScheme
        taxBehavior
        taxCode
        createdAt
    }
}
Variables (One-time price):
{
  "productId": "1",
  "amount": "29.99",
  "currency": "USD",
  "type": "ONE_TIME",
  "active": true,
  "nickname": "Standard Price",
  "description": "One-time purchase price",
  "pricingType": "FIXED",
  "taxBehavior": "EXCLUSIVE"
}
Variables (Recurring price with trial):
{
  "productId": "1",
  "amount": "9.99",
  "currency": "USD",
  "type": "RECURRING",
  "billingPeriod": "month",
  "active": true,
  "nickname": "Monthly Subscription",
  "description": "Monthly recurring subscription",
  "pricingType": "FIXED",
  "trialPeriodDays": 7,
  "taxBehavior": "EXCLUSIVE"
}
Variables (Pay-What-You-Want price):
{
  "productId": "1",
  "amount": "20.00",
  "currency": "USD",
  "type": "ONE_TIME",
  "active": true,
  "nickname": "PWYW Price",
  "description": "Customer chooses their price",
  "pricingType": "PAY_WHAT_YOU_WANT",
  "minAmount": "10.00",
  "maxAmount": "50.00",
  "taxBehavior": "EXCLUSIVE"
}
Variables (Usage-based pricing):
{
  "productId": "1",
  "amount": "0.01",
  "currency": "USD",
  "type": "RECURRING",
  "billingPeriod": "month",
  "active": true,
  "nickname": "Per API Call",
  "description": "Charged per API call",
  "pricingType": "FIXED",
  "usageType": "METERED",
  "unitLabel": "API calls",
  "billingScheme": "PER_UNIT",
  "taxBehavior": "EXCLUSIVE"
}
Response:
{
  "data": {
    "createPrice": {
      "id": "1",
      "amount": "29.99",
      "currency": "USD",
      "type": "ONE_TIME",
      "billingPeriod": null,
      "active": true,
      "nickname": "Standard Price",
      "description": "One-time purchase price",
      "pricingType": "FIXED",
      "minAmount": null,
      "maxAmount": null,
      "trialPeriodDays": null,
      "usageType": null,
      "unitLabel": null,
      "billingScheme": null,
      "taxBehavior": "EXCLUSIVE",
      "taxCode": null,
      "createdAt": "2024-01-15T10:30:00Z"
    }
  }
}
Note:
  • Required fields: productId, amount, currency, type
  • For RECURRING prices, billingPeriod is required (e.g., “month”, “year”)
  • For PAY_WHAT_YOU_WANT pricing, minAmount and/or maxAmount must be provided
  • trialPeriodDays is only allowed for RECURRING prices
  • For usage-based pricing (usageType: METERED), unitLabel is required
  • amount must be greater than 0
  • currency must be a 3-character code (e.g., “USD”, “EUR”)

11.11 Update Price

Permissions: product.manage Request:
mutation UpdatePrice(
    $id: ID!
    $amount: Decimal
    $currency: String
    $active: Boolean
    $metadata: JSON
    $nickname: String
    $description: String
    $pricingType: PricingType
    $minAmount: Decimal
    $maxAmount: Decimal
    $trialPeriodDays: Int
    $usageType: UsageType
    $unitLabel: String
    $billingScheme: BillingScheme
    $taxBehavior: TaxBehavior
    $taxCode: String
) {
    updatePrice(
        id: $id
        amount: $amount
        currency: $currency
        active: $active
        metadata: $metadata
        nickname: $nickname
        description: $description
        pricingType: $pricingType
        minAmount: $minAmount
        maxAmount: $maxAmount
        trialPeriodDays: $trialPeriodDays
        usageType: $usageType
        unitLabel: $unitLabel
        billingScheme: $billingScheme
        taxBehavior: $taxBehavior
        taxCode: $taxCode
    ) {
        id
        amount
        currency
        active
        nickname
        description
        pricingType
        minAmount
        maxAmount
        updatedAt
    }
}
Variables:
{
  "id": "1",
  "amount": "39.99",
  "nickname": "Updated Price",
  "active": false
}
Response:
{
  "data": {
    "updatePrice": {
      "id": "1",
      "amount": "39.99",
      "currency": "USD",
      "active": false,
      "nickname": "Updated Price",
      "description": "One-time purchase price",
      "pricingType": "FIXED",
      "minAmount": null,
      "maxAmount": null,
      "updatedAt": "2024-01-16T14:20:00Z"
    }
  }
}
Note: Only the id field is required. All other fields are optional and will only be updated if provided. The same validation rules apply as for creating prices (e.g., trial period only for recurring prices).

11.12 Delete Price

Permissions: product.manage Request:
mutation DeletePrice($id: ID!) {
    deletePrice(id: $id)
}
Variables:
{
  "id": "1"
}
Response:
{
  "data": {
    "deletePrice": true
  }
}
Note: This performs a hard delete, permanently removing the price from the database.

Permission Matrix

EndpointMerchantManager (Staff)Cashier (Staff)
login
createMerchant
createPayment
transactions
salesSummaryReport
addOrUpdateWallet
createStaff
customers
upsertCustomer
createRefundRequest
approveRefund
rejectRefund
completeRefund
refunds
refundByReference
refundSummary
products
product
productBySku
productPrices
createProduct
updateProduct
deleteProduct
archiveProduct
unarchiveProduct
createPrice
updatePrice
deletePrice

Quick Start Example (cURL)

# 1. Login
curl -X POST https://your-domain.com/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation { login(email: \"merchant@example.com\", password: \"SecurePass123!\") { access_token user_type } }"
  }'

# 2. Create Payment (with token)
curl -X POST https://your-domain.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -d '{
    "query": "mutation { createPayment(amount: \"25.50\", blockchain: \"solana-devnet\", tokenSymbol: \"USDC\") { url qr_code } }"
  }'

This API reference covers all available endpoints in the StableX platform. For any questions or issues, please contact the development team.