Zino Payouts API Documentation
Zino Payouts API Documentation
Overview
The Zino Payouts API provides secure access to payout management functionality. This API allows authorized administrators to view, create, and update payout records in the system.
⚠️ Important Changes
Authentication System Updated: The API now uses a dynamic token generation system instead of persistent tokens:
- Old: Used long-lived persistent tokens that never expired
- New: Uses custom tokens that can be used directly with the API (simplified!)
- Why: Improved security with automatic token expiration and on-demand generation
Key Changes:
- Token Generation: Generate tokens using
https://api.zino.club/api/auth/generate-token - Direct Usage: Custom tokens can now be used directly with the API (no conversion needed)
- Token Expiry: Tokens expire after 1 hour - generate new ones as needed
- No Local Setup: Gerard doesn't need Firebase credentials or service account keys
Base URL
https://api.zino.club/api/payouts
Authentication
The API uses Firebase custom tokens for authentication. All requests must include a valid Bearer token in the Authorization header.
Generating a New Token
To generate a new authentication token, use the token generation endpoint:
curl -H "X-Admin-Secret: gerard-token-generator-2024" \
https://api.zino.club/api/auth/generate-token
This will return a Firebase custom token that you can use directly with the API.
Important: Tokens expire after 1 hour. You can generate new ones on-demand using the same process.
Simple Usage (Recommended)
# Step 1: Generate a custom token
TOKEN=$(curl -s -H "X-Admin-Secret: gerard-token-generator-2024" \
https://api.zino.club/api/auth/generate-token | jq -r '.customToken')
# Step 2: Use the custom token directly with the API
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://api.zino.club/api/payouts
Complete Token Flow Example (For Web Applications)
// For web applications that need Firebase client integration:
// Step 1: Get custom token from server
const customTokenResponse = await fetch('https://api.zino.club/api/auth/generate-token', {
headers: {
'X-Admin-Secret': 'gerard-token-generator-2024'
}
});
const { customToken } = await customTokenResponse.json();
// Step 2: Convert to ID token using Firebase (optional for web apps)
import { signInWithCustomToken, getAuth } from 'firebase/auth';
const auth = getAuth();
const userCredential = await signInWithCustomToken(auth, customToken);
const idToken = await userCredential.user.getIdToken();
// Step 3: Use ID token for API calls
const payoutsResponse = await fetch('https://api.zino.club/api/payouts', {
headers: {
'Authorization': `Bearer ${idToken}`,
'Content-Type': 'application/json'
}
});
Authentication Headers
All API requests must include the following header:
Authorization: Bearer YOUR_CUSTOM_TOKEN_HERE
Note: Custom tokens work directly with the API. ID token conversion is only needed for web applications using Firebase client features.
Data Structure
Payout Object
{
"id": "string", // Unique payout ID (Firebase document ID)
"userId": "string", // User ID associated with the payout
"username": "string", // Display name of the user
"amount": number, // Payout amount in dollars
"status": "string", // Status: "pending", "processing", or "paid"
"lastUpdated": "string", // ISO timestamp of last update
"nextPayoutDate": "string" | null, // ISO timestamp of next scheduled payout
"createdAt": "string", // ISO timestamp of creation (if available)
"updatedAt": "string" // ISO timestamp of last update (if available)
}
Status Values
pending- Payout is awaiting processingprocessing- Payout is currently being processedpaid- Payout has been completed (amount automatically resets to $0)
API Endpoints
GET /api/payouts
Retrieve all payout records.
Request:
GET https://api.zino.club/api/payouts
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
Response:
[
{
"id": "XqiH9fnoxjVRIx2MBjAi0BvHKYo2",
"userId": "XqiH9fnoxjVRIx2MBjAi0BvHKYo2",
"username": "Test 1",
"amount": 200,
"status": "pending",
"lastUpdated": "2025-06-22T19:39:24.526Z",
"nextPayoutDate": "2025-06-30T20:00:00.000Z"
}
]
POST /api/payouts
Create a new payout record.
Request:
POST https://api.zino.club/api/payouts
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"userId": "user123",
"username": "John Doe",
"amount": 150,
"status": "pending"
}
Response:
{
"id": "generated_id",
"userId": "user123",
"username": "John Doe",
"amount": 150,
"status": "pending",
"createdAt": "2024-01-15T10:30:00.000Z",
"createdBy": "admin@example.com"
}
GET /api/payouts/id
Retrieve a specific payout record.
Request:
GET https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
PUT /api/payouts/id
Update a specific payout record.
Request:
PUT https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
Authorization: Bearer YOUR_TOKEN
Content-Type: application/json
{
"status": "paid",
"amount": 0
}
Important: When setting status to "paid", the amount will automatically be reset to $0.
DELETE /api/payouts/id
Delete a specific payout record.
Request:
DELETE https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
Authorization: Bearer YOUR_TOKEN
Implementation Examples
Simple JavaScript (Direct Custom Token Usage)
const API_BASE = 'https://api.zino.club/api/payouts';
// Simple token generation (returns custom token directly)
async function generateToken() {
const response = await fetch('https://api.zino.club/api/auth/generate-token', {
headers: {
'X-Admin-Secret': 'gerard-token-generator-2024'
}
});
if (!response.ok) {
throw new Error(`Token generation failed: ${response.status}`);
}
const { customToken } = await response.json();
return customToken; // Use custom token directly!
}
// Get all payouts
async function getAllPayouts() {
try {
const token = await generateToken();
const response = await fetch(API_BASE, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const payouts = await response.json();
console.log('Payouts:', payouts);
return payouts;
} catch (error) {
console.error('Error fetching payouts:', error);
throw error;
}
}
// Update payout status
async function updatePayoutStatus(payoutId, status) {
try {
const token = await generateToken();
const response = await fetch(`${API_BASE}/${payoutId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const updatedPayout = await response.json();
return updatedPayout;
} catch (error) {
console.error('Error updating payout:', error);
throw error;
}
}
// Usage
getAllPayouts();
updatePayoutStatus('XqiH9fnoxjVRIx2MBjAi0BvHKYo2', 'paid');
JavaScript (Node.js) - Advanced with Firebase Integration
const API_BASE = 'https://api.zino.club/api/payouts';
// Token generation function
async function generateToken() {
const response = await fetch('https://api.zino.club/api/auth/generate-token', {
headers: {
'X-Admin-Secret': 'gerard-token-generator-2024'
}
});
if (!response.ok) {
throw new Error(`Token generation failed: ${response.status}`);
}
const { customToken } = await response.json();
// Convert custom token to ID token using Firebase
const { signInWithCustomToken, getAuth } = require('firebase/auth');
const auth = getAuth();
const userCredential = await signInWithCustomToken(auth, customToken);
return await userCredential.user.getIdToken();
}
// Get all payouts
async function getAllPayouts() {
try {
const token = await generateToken();
const response = await fetch(API_BASE, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const payouts = await response.json();
console.log('Payouts:', payouts);
return payouts;
} catch (error) {
console.error('Error fetching payouts:', error);
throw error;
}
}
// Update payout status
async function updatePayoutStatus(payoutId, status) {
try {
const token = await generateToken();
const response = await fetch(`${API_BASE}/${payoutId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const updatedPayout = await response.json();
console.log('Updated payout:', updatedPayout);
return updatedPayout;
} catch (error) {
console.error('Error updating payout:', error);
throw error;
}
}
// Usage
getAllPayouts();
updatePayoutStatus('XqiH9fnoxjVRIx2MBjAi0BvHKYo2', 'paid');
TypeScript/React (TSX)
import React, { useState, useEffect } from 'react';
import { signInWithCustomToken, getAuth } from 'firebase/auth';
interface Payout {
id: string;
userId: string;
username: string;
amount: number;
status: 'pending' | 'processing' | 'paid';
lastUpdated: string;
nextPayoutDate?: string;
}
const API_BASE = 'https://api.zino.club/api/payouts';
// Token generation function
const generateToken = async (): Promise<string> => {
const response = await fetch('https://api.zino.club/api/auth/generate-token', {
headers: {
'X-Admin-Secret': 'gerard-token-generator-2024'
}
});
if (!response.ok) {
throw new Error(`Token generation failed: ${response.status}`);
}
const { customToken } = await response.json();
// Convert custom token to ID token using Firebase
const auth = getAuth();
const userCredential = await signInWithCustomToken(auth, customToken);
return await userCredential.user.getIdToken();
};
const PayoutManager: React.FC = () => {
const [payouts, setPayouts] = useState<Payout[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
// Fetch all payouts
const fetchPayouts = async () => {
try {
setLoading(true);
const token = await generateToken();
const response = await fetch(API_BASE, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch payouts: ${response.status}`);
}
const data = await response.json();
setPayouts(data);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
// Update payout status
const updateStatus = async (payoutId: string, newStatus: string) => {
try {
const token = await generateToken();
const response = await fetch(`${API_BASE}/${payoutId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: newStatus })
});
if (!response.ok) {
throw new Error(`Failed to update payout: ${response.status}`);
}
// Refresh the payouts list
await fetchPayouts();
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
}
};
useEffect(() => {
fetchPayouts();
}, []);
if (loading) return <div>Loading payouts...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="payout-manager">
<h2>Payout Management</h2>
<button onClick={fetchPayouts}>Refresh</button>
<table>
<thead>
<tr>
<th>Username</th>
<th>Amount</th>
<th>Status</th>
<th>Last Updated</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{payouts.map((payout) => (
<tr key={payout.id}>
<td>{payout.username}</td>
<td>${payout.amount}</td>
<td>{payout.status}</td>
<td>{new Date(payout.lastUpdated).toLocaleDateString()}</td>
<td>
{payout.status === 'pending' && (
<button onClick={() => updateStatus(payout.id, 'processing')}>
Process
</button>
)}
{payout.status === 'processing' && (
<button onClick={() => updateStatus(payout.id, 'paid')}>
Mark Paid
</button>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default PayoutManager;
Python
import requests
import json
from typing import List, Dict, Optional
class ZinoPayoutsAPI:
def __init__(self):
self.base_url = "https://api.zino.club/api/payouts"
self.token_url = "https://api.zino.club/api/auth/generate-token"
self.admin_secret = "gerard-token-generator-2024"
def _generate_token(self) -> str:
"""Generate a Firebase custom token for direct API use."""
# Get custom token from the API
headers = {"X-Admin-Secret": self.admin_secret}
response = requests.get(self.token_url, headers=headers)
response.raise_for_status()
custom_token = response.json()["customToken"]
# Return custom token directly - it works with the API!
return custom_token
def _get_headers(self) -> Dict[str, str]:
"""Get headers with fresh authentication token."""
# Get custom token and use it directly
custom_token = self._generate_token()
return {
"Authorization": f"Bearer {custom_token}",
"Content-Type": "application/json"
}
def get_all_payouts(self) -> List[Dict]:
"""Retrieve all payout records."""
try:
response = requests.get(self.base_url, headers=self._get_headers())
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching payouts: {e}")
raise
def get_payout(self, payout_id: str) -> Dict:
"""Retrieve a specific payout record."""
try:
url = f"{self.base_url}/{payout_id}"
response = requests.get(url, headers=self._get_headers())
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching payout {payout_id}: {e}")
raise
def create_payout(self, user_id: str, username: str, amount: float, status: str = "pending") -> Dict:
"""Create a new payout record."""
try:
data = {
"userId": user_id,
"username": username,
"amount": amount,
"status": status
}
response = requests.post(self.base_url, headers=self._get_headers(), json=data)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error creating payout: {e}")
raise
def update_payout(self, payout_id: str, **kwargs) -> Dict:
"""Update a payout record."""
try:
url = f"{self.base_url}/{payout_id}"
response = requests.put(url, headers=self._get_headers(), json=kwargs)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error updating payout {payout_id}: {e}")
raise
def mark_payout_paid(self, payout_id: str) -> Dict:
"""Mark a payout as paid (amount will automatically reset to 0)."""
return self.update_payout(payout_id, status="paid")
def delete_payout(self, payout_id: str) -> bool:
"""Delete a payout record."""
try:
url = f"{self.base_url}/{payout_id}"
response = requests.delete(url, headers=self._get_headers())
response.raise_for_status()
return True
except requests.exceptions.RequestException as e:
print(f"Error deleting payout {payout_id}: {e}")
raise
# Usage example
if __name__ == "__main__":
api = ZinoPayoutsAPI()
try:
# Get all payouts
payouts = api.get_all_payouts()
print(f"Found {len(payouts)} payouts")
# Process pending payouts
for payout in payouts:
if payout['status'] == 'pending' and payout['amount'] > 0:
print(f"Processing payout for {payout['username']}: ${payout['amount']}")
# Update to processing
api.update_payout(payout['id'], status='processing')
# Later, mark as paid (this will reset amount to 0)
# api.mark_payout_paid(payout['id'])
except Exception as e:
print(f"Error: {e}")
print("Check your network connection and API endpoint availability.")
cURL Commands
# Step 1: Generate a custom token
CUSTOM_TOKEN=$(curl -s -H "X-Admin-Secret: gerard-token-generator-2024" \
https://api.zino.club/api/auth/generate-token | jq -r '.customToken')
# Step 2: Use the custom token directly with the API
# Get all payouts
curl -H "Authorization: Bearer $CUSTOM_TOKEN" \
-H "Content-Type: application/json" \
https://api.zino.club/api/payouts
# Get specific payout
curl -H "Authorization: Bearer $CUSTOM_TOKEN" \
-H "Content-Type: application/json" \
https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
# Create new payout
curl -X POST \
-H "Authorization: Bearer $CUSTOM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId":"user123","username":"John Doe","amount":150,"status":"pending"}' \
https://api.zino.club/api/payouts
# Update payout status to processing
curl -X PUT \
-H "Authorization: Bearer $CUSTOM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"processing"}' \
https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
# Mark payout as paid (amount will automatically become 0)
curl -X PUT \
-H "Authorization: Bearer $CUSTOM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"paid"}' \
https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
# Delete payout
curl -X DELETE \
-H "Authorization: Bearer $CUSTOM_TOKEN" \
https://api.zino.club/api/payouts/XqiH9fnoxjVRIx2MBjAi0BvHKYo2
One-liner for Quick Access
# Get all payouts in one command
curl -H "Authorization: Bearer $(curl -s -H 'X-Admin-Secret: gerard-token-generator-2024' https://api.zino.club/api/auth/generate-token | jq -r '.customToken')" -H "Content-Type: application/json" https://api.zino.club/api/payouts
PHP
<?php
class ZinoPayoutsAPI {
private $baseUrl = 'https://api.zino.club/api/payouts';
private $token;
public function __construct($token) {
$this->token = $token;
}
private function makeRequest($method, $endpoint = '', $data = null) {
$url = $this->baseUrl . $endpoint;
$headers = [
'Authorization: Bearer ' . $this->token,
'Content-Type: application/json'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
throw new Exception("API request failed with status $httpCode: $response");
}
return json_decode($response, true);
}
public function getAllPayouts() {
return $this->makeRequest('GET');
}
public function getPayout($payoutId) {
return $this->makeRequest('GET', "/$payoutId");
}
public function createPayout($userId, $username, $amount, $status = 'pending') {
$data = [
'userId' => $userId,
'username' => $username,
'amount' => $amount,
'status' => $status
];
return $this->makeRequest('POST', '', $data);
}
public function updatePayout($payoutId, $data) {
return $this->makeRequest('PUT', "/$payoutId", $data);
}
public function markPayoutPaid($payoutId) {
return $this->updatePayout($payoutId, ['status' => 'paid']);
}
public function deletePayout($payoutId) {
return $this->makeRequest('DELETE', "/$payoutId");
}
}
// Usage
$token = 'YOUR_TOKEN_HERE';
$api = new ZinoPayoutsAPI($token);
try {
// Get all payouts
$payouts = $api->getAllPayouts();
echo "Found " . count($payouts) . " payouts\n";
// Process each payout
foreach ($payouts as $payout) {
if ($payout['status'] === 'pending' && $payout['amount'] > 0) {
echo "Processing payout for {$payout['username']}: \${$payout['amount']}\n";
// Mark as paid (amount will automatically reset to 0)
$api->markPayoutPaid($payout['id']);
}
}
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
?>
Workflow Examples
Complete Payout Processing Workflow
// Token generation function
async function generateToken() {
const response = await fetch('https://api.zino.club/api/auth/generate-token', {
headers: {
'X-Admin-Secret': 'gerard-token-generator-2024'
}
});
if (!response.ok) {
throw new Error(`Token generation failed: ${response.status}`);
}
const { customToken } = await response.json();
// Convert custom token to ID token using Firebase
const { signInWithCustomToken, getAuth } = require('firebase/auth');
const auth = getAuth();
const userCredential = await signInWithCustomToken(auth, customToken);
return await userCredential.user.getIdToken();
}
// API wrapper class
class ZinoPayoutsAPI {
constructor() {
this.baseUrl = 'https://api.zino.club/api/payouts';
}
async getAllPayouts() {
const token = await generateToken();
const response = await fetch(this.baseUrl, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to fetch payouts: ${response.status}`);
}
return response.json();
}
async updatePayout(payoutId, data) {
const token = await generateToken();
const response = await fetch(`${this.baseUrl}/${payoutId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`Failed to update payout: ${response.status}`);
}
return response.json();
}
}
// Example: Complete payout processing workflow
async function processAllPendingPayouts() {
const api = new ZinoPayoutsAPI();
try {
// 1. Get all payouts
const payouts = await api.getAllPayouts();
console.log(`Found ${payouts.length} total payouts`);
// 2. Filter pending payouts with amount > 0
const pendingPayouts = payouts.filter(p =>
p.status === 'pending' && p.amount > 0
);
console.log(`Found ${pendingPayouts.length} pending payouts`);
// 3. Process each pending payout
for (const payout of pendingPayouts) {
console.log(`Processing ${payout.username}: $${payout.amount}`);
// Mark as processing
await api.updatePayout(payout.id, { status: 'processing' });
// Simulate payment processing
await new Promise(resolve => setTimeout(resolve, 1000));
// Mark as paid (amount automatically becomes 0)
await api.updatePayout(payout.id, { status: 'paid' });
console.log(`✅ Completed payout for ${payout.username}`);
}
console.log('All payouts processed successfully!');
} catch (error) {
console.error('Error processing payouts:', error);
}
}
Error Handling
Common HTTP Status Codes
200- Success201- Created (for POST requests)400- Bad Request (missing required fields)401- Unauthorized (invalid or missing token)404- Not Found (payout doesn't exist)405- Method Not Allowed500- Internal Server Error
Error Response Format
{
"error": "Error description here"
}
Example Error Handling
async function handleAPICall() {
try {
const response = await fetch(API_BASE, {
headers: { 'Authorization': `Bearer ${TOKEN}` }
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`API Error (${response.status}): ${errorData.error}`);
}
return await response.json();
} catch (error) {
if (error.message.includes('401')) {
console.error('Authentication failed. Check your token.');
} else if (error.message.includes('404')) {
console.error('Payout not found.');
} else {
console.error('Unexpected error:', error.message);
}
throw error;
}
}
Security Best Practices
- Never commit tokens to version control
- Store tokens in environment variables or secure configuration
- Use HTTPS only (the API enforces this)
- Implement proper error handling to avoid exposing sensitive information
- Log API calls for audit purposes
- Validate all input data before sending to the API
Rate Limiting
Currently, there are no explicit rate limits, but please be respectful:
- Don't make excessive concurrent requests
- Implement reasonable delays between bulk operations
- Cache results when appropriate
Support
For API issues or questions:
- Check the error response for specific details
- Verify your authentication token is valid
- Ensure you're using the correct endpoint URLs
- Contact the system administrator for token renewal or technical support
Changelog
Version 1.0
- Initial API release
- Support for GET, POST, PUT, DELETE operations
- Custom Firebase token authentication
- Automatic amount reset when status is set to "paid"

