add api spec and mock api server

This commit is contained in:
Timur Gordon 2025-07-14 15:36:20 +02:00
parent a5b46bffb1
commit f523f0fbc9
10 changed files with 4560 additions and 0 deletions

2
src/api/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
package-lock.json

157
src/api/README.md Normal file
View File

@ -0,0 +1,157 @@
# Freezone Backend API
A comprehensive API for managing digital residents, free zone companies, invoices, and expenses. Designed for authorized resellers to register and manage entities in the freezone system.
## Quick Start
### 1. Authentication
```bash
curl -X POST https://api.freezone.com/v1/auth/api-key \
-H "Content-Type: application/json" \
-d '{
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"access_password": "freezone_access_password"
}'
```
### 2. Use API Key
```bash
curl -H "X-API-Key: your_api_key" https://api.freezone.com/v1/digital-residents
```
## Key Concepts
### Dual ID System
- **Input**: Provide your reseller IDs when creating entities
- **Output**: Receive Freezone-issued IDs for all operations
- **Operations**: Use Freezone IDs for all subsequent CRUD operations
| Entity | Reseller ID (Input) | Freezone ID (Operations) |
|--------|-------------------|-------------------------|
| Digital Resident | `reseller_user_id` | `resident_id` |
| Free Zone Company | `reseller_company_id` | `fzc_id` |
| Invoice | `reseller_invoice_id` | `fz_invoice_id` |
| Expense | `reseller_expense_id` | `fz_expense_id` |
### Prerequisites & Dependencies
#### Creating Free Zone Companies
- **Required**: All `shareholder_resident_ids` must be registered digital residents
- **Validation**: API validates that all provided resident IDs exist before company creation
- **Example**: To create a company with shareholders `["fz_resident_abc123", "fz_resident_def456"]`, both residents must exist
#### Creating Invoices/Expenses
- **Required**: Valid `fzc_id` (company must exist)
- **Scope**: All invoices and expenses are company-scoped
## API Endpoints
### Digital Residents
```
GET /digital-residents # List residents
POST /digital-residents # Create (provide reseller_user_id)
GET /digital-residents/{resident_id} # Get by Freezone ID
PUT /digital-residents/{resident_id} # Update by Freezone ID
DELETE /digital-residents/{resident_id} # Delete by Freezone ID
```
### Free Zone Companies
```
GET /free-zone-companies # List companies
POST /free-zone-companies # Create (provide reseller_company_id)
GET /free-zone-companies/{fzc_id} # Get by Freezone ID
PUT /free-zone-companies/{fzc_id} # Update by Freezone ID
DELETE /free-zone-companies/{fzc_id} # Delete by Freezone ID
```
### Invoices (Company-scoped)
```
GET /free-zone-companies/{fzc_id}/invoices # List invoices
POST /free-zone-companies/{fzc_id}/invoices # Create invoice
GET /free-zone-companies/{fzc_id}/invoices/{fz_invoice_id} # Get invoice
PUT /free-zone-companies/{fzc_id}/invoices/{fz_invoice_id} # Update invoice
DELETE /free-zone-companies/{fzc_id}/invoices/{fz_invoice_id} # Delete invoice
```
### Expenses (Company-scoped)
```
GET /free-zone-companies/{fzc_id}/expenses # List expenses
POST /free-zone-companies/{fzc_id}/expenses # Create expense
GET /free-zone-companies/{fzc_id}/expenses/{fz_expense_id} # Get expense
PUT /free-zone-companies/{fzc_id}/expenses/{fz_expense_id} # Update expense
DELETE /free-zone-companies/{fzc_id}/expenses/{fz_expense_id} # Delete expense
```
## Example Workflow
### 1. Create Digital Residents
```json
POST /digital-residents
{
"reseller_user_id": "reseller_user_123",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"kyc_provider_id": "kyc_john_789"
}
// Returns: resident_id: "fz_resident_abc123"
```
### 2. Create Free Zone Company
```json
POST /free-zone-companies
{
"reseller_company_id": "reseller_comp_456",
"name": "Tech Innovations FZC",
"type": "company",
"shareholder_resident_ids": ["fz_resident_abc123"], // Must exist!
"crypto_wallets": [
{
"public_key": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"chain": "bitcoin",
"label": "Main Bitcoin Wallet"
}
]
}
// Returns: fzc_id: "fz_company_xyz789"
```
### 3. Create Invoice
```json
POST /free-zone-companies/fz_company_xyz789/invoices
{
"reseller_invoice_id": "reseller_inv_789",
"invoice_number": "INV-2024-001",
"amount": 1500.00,
"currency": "USD",
"issue_date": "2024-01-15"
}
// Returns: fz_invoice_id: "fz_invoice_inv123"
```
## Rate Limiting
- **Limit**: 100 requests per minute
- **Headers**: Check `X-RateLimit-Remaining` and `X-RateLimit-Reset`
## Error Handling
- **400**: Bad request (validation errors)
- **401**: Invalid/missing API key
- **404**: Resource not found
- **409**: Conflict (duplicate reseller IDs)
- **429**: Rate limit exceeded
## KYC Integration
Use the `kyc_provider_id` from digital residents to query your KYC provider for verification data.
## Crypto Wallet Support
Supported chains: `bitcoin`, `ethereum`, `polygon`, `binance_smart_chain`, `solana`
## Documentation
- **Interactive API Docs**: [Swagger UI](./swagger-ui.html)
- **OpenAPI Spec**: [openapi_updated.yaml](./openapi_updated.yaml)
## Support
- **Email**: api-support@freezone.com
- **Rate Limiting**: 100 requests/minute
- **Authentication**: API key required for all endpoints

1743
src/api/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

2032
src/api/openapi_updated.yaml Normal file

File diff suppressed because it is too large Load Diff

30
src/api/package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "freezone-api-docs",
"version": "1.0.0",
"description": "Documentation and mock server for Freezone API",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"install-deps": "npm install",
"test-mock": "./test-mock-api.sh"
},
"dependencies": {
"express": "^4.18.2",
"http-proxy-middleware": "^2.0.6",
"@stoplight/prism-cli": "^5.8.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"keywords": [
"openapi",
"mock",
"api",
"freezone",
"swagger",
"documentation"
],
"author": "Freezone API Team",
"license": "MIT"
}

40
src/api/serve.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
# Simple HTTP server to serve the Swagger UI and OpenAPI spec
# Usage: ./serve.sh [port]
PORT=${1:-8080}
API_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "🚀 Starting Freezone API Documentation Server..."
echo "📁 Serving from: $API_DIR"
echo "🌐 URL: http://localhost:$PORT"
echo "📖 Swagger UI: http://localhost:$PORT/swagger-ui.html"
echo ""
echo "📋 Available files:"
echo " - swagger-ui.html (Interactive API documentation)"
echo " - openapi_updated.yaml (OpenAPI specification)"
echo " - README.md (API documentation)"
echo ""
echo "Press Ctrl+C to stop the server"
echo "----------------------------------------"
# Check if Python 3 is available
if command -v python3 &> /dev/null; then
cd "$API_DIR"
python3 -m http.server $PORT
elif command -v python &> /dev/null; then
cd "$API_DIR"
python -m http.server $PORT
elif command -v node &> /dev/null; then
# Use Node.js if available
cd "$API_DIR"
npx http-server -p $PORT
else
echo "❌ Error: No suitable HTTP server found."
echo "Please install one of the following:"
echo " - Python 3: python3 -m http.server"
echo " - Python 2: python -m SimpleHTTPServer"
echo " - Node.js: npx http-server"
exit 1
fi

175
src/api/server.js Normal file
View File

@ -0,0 +1,175 @@
const express = require('express');
const path = require('path');
const { createProxyMiddleware } = require('http-proxy-middleware');
const { spawn } = require('child_process');
const app = express();
const PORT = process.env.PORT || 3000;
// Serve static files (documentation)
app.use(express.static(__dirname));
// Start Prism mock server on a different port internally
let prismProcess;
function startPrismServer() {
console.log('🔧 Starting internal mock server...');
prismProcess = spawn('npx', ['prism', 'mock', 'openapi_updated.yaml', '--host', '127.0.0.1', '--port', '4001', '--dynamic'], {
cwd: __dirname,
stdio: ['pipe', 'pipe', 'pipe']
});
prismProcess.stdout.on('data', (data) => {
const output = data.toString().trim();
if (output && !output.includes('Prism is listening')) {
console.log(`[Internal] ${output}`);
}
});
prismProcess.stderr.on('data', (data) => {
const output = data.toString().trim();
if (output) {
console.log(`[Internal] ${output}`);
}
});
prismProcess.on('close', (code) => {
console.log(`[Internal] Mock server process exited with code ${code}`);
});
// Give Prism time to start
setTimeout(() => {
console.log('✅ Mock API service ready at /api/mock');
}, 3000);
}
// Proxy /api/mock/* to the internal Prism server
app.use('/api/mock', createProxyMiddleware({
target: 'http://127.0.0.1:4001',
changeOrigin: true,
pathRewrite: {
'^/api/mock': '', // Remove /api/mock prefix when forwarding to Prism
},
onError: (err, req, res) => {
console.error('Proxy error:', err.message);
res.status(503).json({
error: 'mock_server_unavailable',
message: 'Mock API server is not available. Please wait a moment and try again.',
details: {
endpoint: req.originalUrl,
internal_error: err.message
}
});
},
onProxyReq: (proxyReq, req, res) => {
console.log(`[Mock API] ${req.method} ${req.originalUrl} -> http://127.0.0.1:4001${req.url}`);
}
}));
// API status endpoint
app.get('/api/status', (req, res) => {
res.json({
status: 'running',
services: {
documentation: {
status: 'active',
endpoints: [
'GET /',
'GET /swagger-ui.html',
'GET /openapi_updated.yaml',
'GET /README.md'
]
},
mock_api: {
status: prismProcess ? 'active' : 'starting',
base_url: '/api/mock',
endpoints: [
'POST /api/mock/auth/api-key',
'GET /api/mock/digital-residents',
'POST /api/mock/digital-residents',
'GET /api/mock/free-zone-companies',
'POST /api/mock/free-zone-companies',
'GET /api/mock/free-zone-companies/{fzc_id}/invoices',
'GET /api/mock/free-zone-companies/{fzc_id}/expenses'
]
}
},
timestamp: new Date().toISOString()
});
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
// Default route - redirect to Swagger UI
app.get('/', (req, res) => {
res.redirect('/swagger-ui.html');
});
// 404 handler
app.use((req, res) => {
res.status(404).json({
error: 'not_found',
message: `Endpoint ${req.method} ${req.path} not found`,
available_endpoints: {
documentation: [
'GET /',
'GET /swagger-ui.html',
'GET /openapi_updated.yaml',
'GET /README.md'
],
mock_api: [
'POST /api/mock/auth/api-key',
'GET /api/mock/digital-residents',
'POST /api/mock/digital-residents',
'GET /api/mock/free-zone-companies',
'POST /api/mock/free-zone-companies'
],
status: [
'GET /api/status',
'GET /health'
]
}
});
});
// Graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down servers...');
if (prismProcess) {
prismProcess.kill();
}
process.exit(0);
});
// Start the server
app.listen(PORT, () => {
console.log('🚀 Freezone API Documentation & Mock Server');
console.log('==========================================');
console.log(`🌐 Server running on: http://localhost:${PORT}`);
console.log('');
console.log('📋 Available Services:');
console.log(` 📖 Documentation: http://localhost:${PORT}/swagger-ui.html`);
console.log(` 🔧 Mock API: http://localhost:${PORT}/api/mock`);
console.log(` 📄 OpenAPI Spec: http://localhost:${PORT}/openapi_updated.yaml`);
console.log(` 📊 Status: http://localhost:${PORT}/api/status`);
console.log('');
console.log('🔧 Mock API Examples:');
console.log(` POST http://localhost:${PORT}/api/mock/auth/api-key`);
console.log(` GET http://localhost:${PORT}/api/mock/digital-residents`);
console.log(` POST http://localhost:${PORT}/api/mock/digital-residents`);
console.log(` GET http://localhost:${PORT}/api/mock/free-zone-companies`);
console.log('');
console.log('💡 Tips:');
console.log(' - Mock API returns example responses from OpenAPI spec');
console.log(' - Use X-API-Key header for authenticated endpoints');
console.log(' - All services run on the same port for convenience');
console.log('');
console.log('Press Ctrl+C to stop the server');
console.log('==========================================');
// Start Prism after Express server is running
startPrismServer();
});

75
src/api/start-mock-server.sh Executable file
View File

@ -0,0 +1,75 @@
#!/bin/bash
# Start both documentation and mock API server
# Usage: ./start-mock-server.sh
API_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$API_DIR"
echo "🚀 Starting Freezone API Mock Server & Documentation..."
echo ""
echo "📋 Services:"
echo " 📖 Documentation: http://localhost:3000/swagger-ui.html"
echo " 🔧 Mock API: http://localhost:4000"
echo " 📄 OpenAPI Spec: http://localhost:3000/openapi_updated.yaml"
echo ""
echo "🔧 Mock API Endpoints:"
echo " POST http://localhost:4000/auth/api-key"
echo " GET http://localhost:4000/digital-residents"
echo " POST http://localhost:4000/digital-residents"
echo " GET http://localhost:4000/free-zone-companies"
echo " POST http://localhost:4000/free-zone-companies"
echo " GET http://localhost:4000/free-zone-companies/{fzc_id}/invoices"
echo " GET http://localhost:4000/free-zone-companies/{fzc_id}/expenses"
echo ""
echo "💡 Tips:"
echo " - Mock API returns example responses from OpenAPI spec"
echo " - Use 'X-API-Key: test-key' header for authenticated endpoints"
echo " - Dynamic responses include realistic data variations"
echo ""
echo "Press Ctrl+C to stop all servers"
echo "----------------------------------------"
# Check if Node.js and npm are available
if ! command -v node &> /dev/null; then
echo "❌ Error: Node.js is required but not installed."
echo "Please install Node.js from https://nodejs.org/"
exit 1
fi
if ! command -v npm &> /dev/null; then
echo "❌ Error: npm is required but not installed."
echo "Please install npm (usually comes with Node.js)"
exit 1
fi
# Check if Prism is installed globally
if ! command -v prism &> /dev/null; then
echo "📦 Installing Prism CLI globally..."
npm install -g @stoplight/prism-cli
if [ $? -ne 0 ]; then
echo "❌ Failed to install Prism CLI. Trying local installation..."
npm install @stoplight/prism-cli
PRISM_CMD="npx prism"
else
PRISM_CMD="prism"
fi
else
PRISM_CMD="prism"
fi
# Check if concurrently is available
if ! command -v concurrently &> /dev/null; then
echo "📦 Installing concurrently..."
npm install concurrently
CONCURRENTLY_CMD="npx concurrently"
else
CONCURRENTLY_CMD="concurrently"
fi
# Start both servers concurrently
$CONCURRENTLY_CMD \
--names "DOCS,MOCK" \
--prefix-colors "blue,green" \
"python3 -m http.server 3000" \
"$PRISM_CMD mock openapi_updated.yaml --host 0.0.0.0 --port 4000 --dynamic"

153
src/api/swagger-ui.html Normal file
View File

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Freezone API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui.css" />
<style>
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
.swagger-ui .topbar {
background-color: #1f4e79;
}
.swagger-ui .topbar .download-url-wrapper .select-label {
color: #fff;
}
.swagger-ui .topbar .download-url-wrapper input[type=text] {
border: 2px solid #547ca3;
}
.swagger-ui .info .title {
color: #1f4e79;
}
.custom-header {
background: linear-gradient(135deg, #1f4e79 0%, #2980b9 100%);
color: white;
padding: 20px;
text-align: center;
margin-bottom: 20px;
}
.custom-header h1 {
margin: 0;
font-size: 2.5em;
font-weight: 300;
}
.custom-header p {
margin: 10px 0 0 0;
font-size: 1.2em;
opacity: 0.9;
}
.api-info {
background: white;
padding: 20px;
margin: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.api-info h3 {
color: #1f4e79;
margin-top: 0;
}
.api-info .highlight {
background: #e8f4fd;
padding: 15px;
border-left: 4px solid #2980b9;
margin: 15px 0;
}
.api-info code {
background: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: './openapi_updated.yaml',
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
tryItOutEnabled: true,
requestInterceptor: function(request) {
// Add API key header if available
const apiKey = localStorage.getItem('freezone_api_key');
if (apiKey) {
request.headers['X-API-Key'] = apiKey;
}
return request;
},
onComplete: function() {
// Add custom functionality after Swagger UI loads
console.log('Freezone API Documentation loaded');
// Add API key input functionality
const topbar = document.querySelector('.topbar');
if (topbar) {
const apiKeyInput = document.createElement('div');
apiKeyInput.innerHTML = `
<div style="display: inline-block; margin-left: 20px;">
<label style="color: white; margin-right: 10px;">API Key:</label>
<input type="password" id="api-key-input" placeholder="Enter your API key"
style="padding: 5px; border-radius: 3px; border: 1px solid #ccc; width: 200px;">
<button onclick="setApiKey()" style="margin-left: 5px; padding: 5px 10px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer;">Set</button>
<button onclick="clearApiKey()" style="margin-left: 5px; padding: 5px 10px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer;">Clear</button>
</div>
`;
topbar.appendChild(apiKeyInput);
// Load saved API key
const savedKey = localStorage.getItem('freezone_api_key');
if (savedKey) {
document.getElementById('api-key-input').value = savedKey;
}
}
}
});
// End Swagger UI call region
window.ui = ui;
};
function setApiKey() {
const apiKey = document.getElementById('api-key-input').value;
if (apiKey) {
localStorage.setItem('freezone_api_key', apiKey);
alert('API key saved! It will be included in all requests.');
} else {
alert('Please enter an API key.');
}
}
function clearApiKey() {
localStorage.removeItem('freezone_api_key');
document.getElementById('api-key-input').value = '';
alert('API key cleared.');
}
</script>
</body>
</html>

153
src/api/test-mock-api.sh Executable file
View File

@ -0,0 +1,153 @@
#!/bin/bash
# Test script for the mock API server
# Usage: ./test-mock-api.sh
BASE_URL="http://localhost:3000/api/mock"
API_KEY="test-api-key"
echo "🧪 Testing Freezone Mock API Server"
echo "🌐 Base URL: $BASE_URL"
echo "🔑 API Key: $API_KEY"
echo "----------------------------------------"
# Function to make API calls with proper formatting
test_endpoint() {
local method=$1
local endpoint=$2
local description=$3
local data=$4
echo ""
echo "📋 Testing: $description"
echo "🔗 $method $endpoint"
if [ -n "$data" ]; then
echo "📤 Request Body:"
echo "$data" | jq '.' 2>/dev/null || echo "$data"
echo ""
response=$(curl -s -X "$method" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d "$data" \
"$BASE_URL$endpoint")
else
response=$(curl -s -X "$method" \
-H "X-API-Key: $API_KEY" \
"$BASE_URL$endpoint")
fi
echo "📥 Response:"
echo "$response" | jq '.' 2>/dev/null || echo "$response"
echo "----------------------------------------"
}
# Check if jq is available for JSON formatting
if ! command -v jq &> /dev/null; then
echo "💡 Tip: Install 'jq' for better JSON formatting: brew install jq"
echo ""
fi
# Check if mock server is running
echo "🔍 Checking if mock server is running..."
if ! curl -s "$BASE_URL" > /dev/null; then
echo "❌ Mock server is not running on $BASE_URL"
echo "💡 Start it with: ./start-mock-server.sh"
exit 1
fi
echo "✅ Mock server is running!"
# Test Authentication
test_endpoint "POST" "/auth/api-key" "Generate API Key" '{
"client_id": "reseller_123",
"client_secret": "secret_abc123xyz",
"access_password": "freezone_access_2024"
}'
# Test Digital Residents
test_endpoint "GET" "/digital-residents" "List Digital Residents"
test_endpoint "POST" "/digital-residents" "Create Digital Resident" '{
"reseller_user_id": "reseller_user_456",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"kyc_provider_id": "kyc_john_doe_789",
"phone": "+1234567890"
}'
test_endpoint "GET" "/digital-residents/fz_resident_abc123" "Get Digital Resident"
# Test Free Zone Companies
test_endpoint "GET" "/free-zone-companies" "List Free Zone Companies"
test_endpoint "POST" "/free-zone-companies" "Create Free Zone Company" '{
"reseller_company_id": "reseller_comp_123",
"name": "Tech Innovations FZC",
"type": "company",
"description": "Technology consulting company",
"registration_number": "FZC-2024-001",
"tax_id": "TAX123456789",
"address": {
"street": "123 Business Bay",
"city": "Dubai",
"state": "Dubai",
"postal_code": "12345",
"country": "AE"
},
"shareholder_resident_ids": ["fz_resident_abc123", "fz_resident_def456"],
"crypto_wallets": [
{
"public_key": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"chain": "bitcoin",
"label": "Main Bitcoin Wallet"
}
]
}'
test_endpoint "GET" "/free-zone-companies/fz_company_xyz789" "Get Free Zone Company"
# Test Invoices
test_endpoint "GET" "/free-zone-companies/fz_company_xyz789/invoices" "List Company Invoices"
test_endpoint "POST" "/free-zone-companies/fz_company_xyz789/invoices" "Create Invoice" '{
"reseller_invoice_id": "reseller_inv_456",
"invoice_number": "INV-2024-001",
"amount": 1500.00,
"currency": "USD",
"tax_amount": 150.00,
"description": "Consulting services for Q1 2024",
"line_items": [
{
"description": "Strategy consulting",
"quantity": 10,
"unit_price": 150.00,
"amount": 1500.00
}
],
"issue_date": "2024-01-15",
"due_date": "2024-02-15"
}'
test_endpoint "GET" "/free-zone-companies/fz_company_xyz789/invoices/fz_invoice_inv123" "Get Invoice"
# Test Expenses
test_endpoint "GET" "/free-zone-companies/fz_company_xyz789/expenses" "List Company Expenses"
test_endpoint "POST" "/free-zone-companies/fz_company_xyz789/expenses" "Create Expense" '{
"reseller_expense_id": "reseller_exp_456",
"amount": 500.00,
"currency": "USD",
"category": "office_supplies",
"description": "Office equipment purchase",
"vendor": "Office Depot",
"receipt_url": "https://receipts.example.com/receipt123.pdf",
"date": "2024-01-15"
}'
test_endpoint "GET" "/free-zone-companies/fz_company_xyz789/expenses/fz_expense_exp123" "Get Expense"
echo ""
echo "✅ Mock API testing completed!"
echo "💡 All endpoints should return example responses from the OpenAPI specification"
echo "🔧 Mock server provides realistic data for development and testing"