Skip to content

Transaction History

View and manage your billing transactions, including credit purchases, usage, and refunds.

Overview

The Transactions API provides access to your complete billing history. Track:

  • Credit purchases
  • Message usage (credits spent)
  • Automatic refunds for failed messages
  • Manual adjustments

Get Transactions

Retrieve transaction history with filtering and pagination.

Endpoint

GET /api/v1/billing/transactions

Authentication

Requires JWT token (dashboard authentication).

Authorization: Bearer YOUR_JWT_TOKEN

Query Parameters

ParameterTypeDescription
typestringFilter by type: purchase, usage, refund, adjustment
startDatestringFilter from date (ISO 8601 format)
endDatestringFilter until date (ISO 8601 format)
pageintegerPage number (default: 1)
limitintegerItems per page (default: 50, max: 100)

Request

bash
# Get all transactions
curl -X GET "https://api.yebolink.com/api/v1/billing/transactions" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# Filter by type
curl -X GET "https://api.yebolink.com/api/v1/billing/transactions?type=purchase" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

# Filter by date range
curl -X GET "https://api.yebolink.com/api/v1/billing/transactions?startDate=2025-10-01T00:00:00Z&endDate=2025-11-02T23:59:59Z" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
javascript
async function getTransactions(filters = {}) {
  const params = new URLSearchParams(filters);

  const response = await fetch(
    `https://api.yebolink.com/api/v1/billing/transactions?${params}`,
    {
      headers: {
        'Authorization': `Bearer ${jwtToken}`
      }
    }
  );

  return await response.json();
}

// Usage examples

// Get all transactions
const allTransactions = await getTransactions();

// Get only purchases
const purchases = await getTransactions({ type: 'purchase' });

// Get usage for last month
const lastMonth = await getTransactions({
  type: 'usage',
  startDate: '2025-10-01T00:00:00Z',
  endDate: '2025-10-31T23:59:59Z'
});

// Paginated results
const page2 = await getTransactions({ page: 2, limit: 50 });
python
import requests
from datetime import datetime, timedelta

def get_transactions(jwt_token: str, filters: dict = None):
    params = filters or {}

    response = requests.get(
        'https://api.yebolink.com/api/v1/billing/transactions',
        headers={'Authorization': f'Bearer {jwt_token}'},
        params=params
    )

    return response.json()

# Usage examples

# Get all transactions
all_transactions = get_transactions(jwt_token)

# Get only purchases
purchases = get_transactions(jwt_token, {'type': 'purchase'})

# Get usage for last 30 days
end_date = datetime.now()
start_date = end_date - timedelta(days=30)

usage = get_transactions(jwt_token, {
    'type': 'usage',
    'startDate': start_date.isoformat() + 'Z',
    'endDate': end_date.isoformat() + 'Z'
})

for txn in usage['data']['transactions']:
    print(f"{txn['created_at']}: -{txn['amount']} credits - {txn['description']}")
php
<?php
function getTransactions($jwtToken, $filters = []) {
    $params = http_build_query($filters);
    $url = "https://api.yebolink.com/api/v1/billing/transactions";

    if ($params) {
        $url .= "?$params";
    }

    $curl = curl_init();

    curl_setopt_array($curl, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $jwtToken
        ]
    ]);

    $response = curl_exec($curl);
    curl_close($curl);

    return json_decode($response, true);
}

// Usage examples

// Get all transactions
$allTransactions = getTransactions($jwtToken);

// Get only purchases
$purchases = getTransactions($jwtToken, ['type' => 'purchase']);

// Get usage for October 2025
$octTransactions = getTransactions($jwtToken, [
    'type' => 'usage',
    'startDate' => '2025-10-01T00:00:00Z',
    'endDate' => '2025-10-31T23:59:59Z'
]);

foreach ($octTransactions['data']['transactions'] as $txn) {
    echo $txn['created_at'] . ": " . $txn['amount'] . " credits - " . $txn['description'] . "\n";
}
?>
ruby
require 'net/http'
require 'json'
require 'uri'

def get_transactions(jwt_token, filters = {})
  uri = URI('https://api.yebolink.com/api/v1/billing/transactions')
  uri.query = URI.encode_www_form(filters) unless filters.empty?

  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Get.new(uri)
  request['Authorization'] = "Bearer #{jwt_token}"

  response = http.request(request)
  JSON.parse(response.body)
end

# Usage examples

# Get all transactions
all_transactions = get_transactions(jwt_token)

# Get only purchases
purchases = get_transactions(jwt_token, { type: 'purchase' })

# Get usage for last month
usage = get_transactions(jwt_token, {
  type: 'usage',
  startDate: '2025-10-01T00:00:00Z',
  endDate: '2025-10-31T23:59:59Z'
})

usage['data']['transactions'].each do |txn|
  puts "#{txn['created_at']}: -#{txn['amount']} credits - #{txn['description']}"
end
go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
)

type TransactionFilters struct {
    Type      string `json:"type,omitempty"`
    StartDate string `json:"startDate,omitempty"`
    EndDate   string `json:"endDate,omitempty"`
    Page      int    `json:"page,omitempty"`
    Limit     int    `json:"limit,omitempty"`
}

func getTransactions(jwtToken string, filters TransactionFilters) (map[string]interface{}, error) {
    baseURL := "https://api.yebolink.com/api/v1/billing/transactions"

    // Build query parameters
    params := url.Values{}
    if filters.Type != "" {
        params.Add("type", filters.Type)
    }
    if filters.StartDate != "" {
        params.Add("startDate", filters.StartDate)
    }
    if filters.EndDate != "" {
        params.Add("endDate", filters.EndDate)
    }
    if filters.Page > 0 {
        params.Add("page", fmt.Sprintf("%d", filters.Page))
    }
    if filters.Limit > 0 {
        params.Add("limit", fmt.Sprintf("%d", filters.Limit))
    }

    url := baseURL
    if len(params) > 0 {
        url += "?" + params.Encode()
    }

    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("Authorization", "Bearer "+jwtToken)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)

    var result map[string]interface{}
    json.Unmarshal(body, &result)

    return result, nil
}

// Usage
func main() {
    // Get all transactions
    allTxns, _ := getTransactions(jwtToken, TransactionFilters{})

    // Get purchases only
    purchases, _ := getTransactions(jwtToken, TransactionFilters{
        Type: "purchase",
    })

    // Get usage for date range
    usage, _ := getTransactions(jwtToken, TransactionFilters{
        Type:      "usage",
        StartDate: "2025-10-01T00:00:00Z",
        EndDate:   "2025-10-31T23:59:59Z",
    })

    fmt.Println(usage)
}

Response

json
{
  "success": true,
  "data": {
    "transactions": [
      {
        "id": "txn_1234567890",
        "type": "purchase",
        "amount": 500,
        "balance_after": 750,
        "description": "Credit purchase - 500 credits",
        "metadata": {
          "package": "500",
          "price": 45.00,
          "stripe_session_id": "cs_test_..."
        },
        "created_at": "2025-11-02T10:00:00Z"
      },
      {
        "id": "txn_0987654321",
        "type": "usage",
        "amount": -1,
        "balance_after": 749,
        "description": "SMS sent to +26878422613",
        "metadata": {
          "message_id": "550e8400-e29b-41d4-a716-446655440000",
          "channel": "sms",
          "recipient": "+26878422613"
        },
        "created_at": "2025-11-02T11:30:00Z"
      },
      {
        "id": "txn_1122334455",
        "type": "refund",
        "amount": 1,
        "balance_after": 750,
        "description": "Refund for failed SMS",
        "metadata": {
          "message_id": "660e8400-e29b-41d4-a716-446655440001",
          "reason": "invalid_number"
        },
        "created_at": "2025-11-02T12:00:00Z"
      }
    ],
    "pagination": {
      "total": 150,
      "page": 1,
      "pages": 3,
      "limit": 50
    }
  }
}

Transaction Types

Purchase

Credits added to account through payment.

json
{
  "type": "purchase",
  "amount": 500,
  "description": "Credit purchase - 500 credits",
  "metadata": {
    "package": "500",
    "price": 45.00,
    "stripe_session_id": "cs_test_..."
  }
}

Usage

Credits deducted for sent messages.

json
{
  "type": "usage",
  "amount": -1,
  "description": "SMS sent to +26878422613",
  "metadata": {
    "message_id": "550e8400-e29b-41d4-a716-446655440000",
    "channel": "sms",
    "recipient": "+26878422613"
  }
}

Refund

Credits returned for failed messages.

json
{
  "type": "refund",
  "amount": 1,
  "description": "Refund for failed SMS",
  "metadata": {
    "message_id": "660e8400-e29b-41d4-a716-446655440001",
    "reason": "invalid_number"
  }
}

Adjustment

Manual credit adjustments by admin.

json
{
  "type": "adjustment",
  "amount": 100,
  "description": "Promotional credits",
  "metadata": {
    "reason": "promotion",
    "admin_id": "admin_123"
  }
}

Usage Analytics

Calculate Total Spending

javascript
async function calculateSpending(startDate, endDate) {
  const response = await getTransactions({
    type: 'purchase',
    startDate,
    endDate
  });

  const total = response.data.transactions.reduce(
    (sum, txn) => sum + (txn.metadata?.price || 0),
    0
  );

  return {
    total: total.toFixed(2),
    transactions: response.data.transactions.length,
    averagePerPurchase: (total / response.data.transactions.length).toFixed(2)
  };
}

// Usage
const spending = await calculateSpending(
  '2025-10-01T00:00:00Z',
  '2025-10-31T23:59:59Z'
);
console.log(`Total spent in October: $${spending.total}`);

Track Credit Usage by Channel

javascript
async function analyzeUsageByChannel(startDate, endDate) {
  const response = await getTransactions({
    type: 'usage',
    startDate,
    endDate
  });

  const byChannel = response.data.transactions.reduce((acc, txn) => {
    const channel = txn.metadata?.channel || 'unknown';
    acc[channel] = (acc[channel] || 0) + Math.abs(txn.amount);
    return acc;
  }, {});

  return byChannel;
}

// Usage
const usage = await analyzeUsageByChannel(
  '2025-10-01T00:00:00Z',
  '2025-10-31T23:59:59Z'
);

console.log('Credit usage by channel:');
console.log(`SMS: ${usage.sms || 0} credits`);
console.log(`WhatsApp: ${usage.whatsapp || 0} credits`);
console.log(`Email: ${usage.email || 0} credits`);

Generate Monthly Report

javascript
async function generateMonthlyReport(year, month) {
  const startDate = new Date(year, month - 1, 1).toISOString();
  const endDate = new Date(year, month, 0, 23, 59, 59).toISOString();

  // Get all transactions
  const allTxns = await getTransactions({ startDate, endDate });

  // Separate by type
  const purchases = allTxns.data.transactions.filter(t => t.type === 'purchase');
  const usage = allTxns.data.transactions.filter(t => t.type === 'usage');
  const refunds = allTxns.data.transactions.filter(t => t.type === 'refund');

  // Calculate totals
  const totalSpent = purchases.reduce(
    (sum, t) => sum + (t.metadata?.price || 0), 0
  );
  const totalCreditsUsed = Math.abs(
    usage.reduce((sum, t) => sum + t.amount, 0)
  );
  const totalRefunds = refunds.reduce((sum, t) => sum + t.amount, 0);

  return {
    period: `${year}-${String(month).padStart(2, '0')}`,
    purchases: {
      count: purchases.length,
      totalSpent: totalSpent.toFixed(2),
      creditsPurchased: purchases.reduce((sum, t) => sum + t.amount, 0)
    },
    usage: {
      count: usage.length,
      creditsUsed: totalCreditsUsed,
      byChannel: analyzeByChannel(usage)
    },
    refunds: {
      count: refunds.length,
      creditsRefunded: totalRefunds
    }
  };
}

// Usage
const report = await generateMonthlyReport(2025, 10);
console.log('Monthly Report:', JSON.stringify(report, null, 2));

Export Transactions

Export to CSV

javascript
async function exportToCSV(startDate, endDate) {
  let allTransactions = [];
  let page = 1;
  let hasMore = true;

  // Fetch all pages
  while (hasMore) {
    const response = await getTransactions({
      startDate,
      endDate,
      page,
      limit: 100
    });

    allTransactions = allTransactions.concat(response.data.transactions);

    hasMore = page < response.data.pagination.pages;
    page++;
  }

  // Convert to CSV
  const headers = ['Date', 'Type', 'Amount', 'Balance After', 'Description'];
  const rows = allTransactions.map(txn => [
    new Date(txn.created_at).toLocaleString(),
    txn.type,
    txn.amount,
    txn.balance_after,
    txn.description
  ]);

  const csv = [headers, ...rows]
    .map(row => row.join(','))
    .join('\n');

  return csv;
}

// Usage
const csv = await exportToCSV(
  '2025-10-01T00:00:00Z',
  '2025-10-31T23:59:59Z'
);

// Download as file
const blob = new Blob([csv], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'transactions.csv';
a.click();

Export to JSON

javascript
async function exportToJSON(startDate, endDate) {
  const response = await getTransactions({ startDate, endDate, limit: 100 });

  const exportData = {
    exported_at: new Date().toISOString(),
    period: {
      start: startDate,
      end: endDate
    },
    transactions: response.data.transactions
  };

  return JSON.stringify(exportData, null, 2);
}

Real-Time Balance Tracking

javascript
// Track balance changes in real-time
class BalanceTracker {
  constructor(jwtToken) {
    this.jwtToken = jwtToken;
    this.currentBalance = null;
    this.listeners = [];
  }

  async initialize() {
    // Get initial balance
    const loginResponse = await this.refreshBalance();
    this.currentBalance = loginResponse.data.workspace.credits_balance;
    return this.currentBalance;
  }

  async refreshBalance() {
    // You would implement this based on your auth endpoint
    const response = await fetch('https://api.yebolink.com/api/v1/auth/me', {
      headers: { 'Authorization': `Bearer ${this.jwtToken}` }
    });
    return await response.json();
  }

  subscribe(callback) {
    this.listeners.push(callback);
  }

  async checkForChanges() {
    const latest = await this.refreshBalance();
    const newBalance = latest.data.workspace.credits_balance;

    if (newBalance !== this.currentBalance) {
      const change = newBalance - this.currentBalance;
      this.currentBalance = newBalance;

      this.listeners.forEach(callback => {
        callback({
          balance: newBalance,
          change,
          timestamp: new Date()
        });
      });
    }
  }

  startPolling(intervalMs = 30000) {
    return setInterval(() => this.checkForChanges(), intervalMs);
  }
}

// Usage
const tracker = new BalanceTracker(jwtToken);
await tracker.initialize();

tracker.subscribe(({ balance, change, timestamp }) => {
  console.log(`Balance updated: ${balance} (${change > 0 ? '+' : ''}${change})`);
  if (change > 0) {
    console.log('Credits added!');
  } else if (change < 0) {
    console.log('Credits used');
  }
});

// Check every 30 seconds
tracker.startPolling(30000);

Best Practices

1. Regular Audits

javascript
// Run monthly audits
async function monthlyAudit() {
  const now = new Date();
  const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
  const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);

  const report = await generateMonthlyReport(
    now.getFullYear(),
    now.getMonth() + 1
  );

  // Alert if spending is high
  if (parseFloat(report.purchases.totalSpent) > 500) {
    console.warn('⚠️  High spending this month:', report.purchases.totalSpent);
  }

  // Alert if refunds are high
  if (report.refunds.count > 10) {
    console.warn('⚠️  High refund count:', report.refunds.count);
  }

  return report;
}

2. Set Spending Alerts

javascript
async function checkSpendingLimit(monthlyLimit) {
  const now = new Date();
  const spending = await calculateSpending(
    new Date(now.getFullYear(), now.getMonth(), 1).toISOString(),
    now.toISOString()
  );

  if (parseFloat(spending.total) > monthlyLimit) {
    // Send alert email or notification
    console.error(`Monthly limit exceeded! Spent: $${spending.total}, Limit: $${monthlyLimit}`);
    return false;
  }

  return true;
}

3. Monitor Refund Rate

javascript
async function calculateRefundRate(startDate, endDate) {
  const usage = await getTransactions({ type: 'usage', startDate, endDate });
  const refunds = await getTransactions({ type: 'refund', startDate, endDate });

  const totalUsage = Math.abs(
    usage.data.transactions.reduce((sum, t) => sum + t.amount, 0)
  );
  const totalRefunds = refunds.data.transactions.reduce(
    (sum, t) => sum + t.amount, 0
  );

  const refundRate = (totalRefunds / totalUsage) * 100;

  if (refundRate > 5) {
    console.warn(`⚠️  High refund rate: ${refundRate.toFixed(2)}%`);
  }

  return refundRate.toFixed(2);
}

Need Help?

Built with VitePress