Gateway ARQ API
Complete API Reference v1.5.0 - 28 Endpoints
Introduction
Gateway ARQ is a real-time data ingestion system for WebCom TLC and Asterisk PBX. This API provides access to call data, system metrics, and real-time updates via WebSocket.
🔐 Authentication
⚠️ REQUIRED - All endpoints now require API key authentication
All API requests MUST include a valid API key in the X-API-Key header.
For WebSocket connections, use the ?api_key=xxx query parameter.
Example Request
curl -H "X-API-Key: sk_live_arq_2024_production_key_change_me" \
http://localhost:8000/api/webcom/lines
Configure API Keys
Set keys in .env file:
API_KEY_1=sk_live_arq_2024_production_key_change_me
API_KEY_2=sk_live_arq_2024_dashboard_key_change_me
API_KEY_3=sk_live_arq_2024_monitoring_key_change_me
Authentication Errors
Missing or invalid API key returns 401 Unauthorized:
{
"detail": "Missing X-API-Key header. Authentication required."
}
API Endpoints
/health
Health check endpoint - returns service status
Response
{
"status": "healthy"
}
Examples
curl -X GET http://localhost:8000/health
fetch('http://localhost:8000/health')
.then(res => res.json())
.then(data => console.log(data));
const axios = require('axios');
axios.get('http://localhost:8000/health')
.then(response => console.log(response.data));
/version
Get API version and build information
Response
{
"title": "Gateway ARQ",
"version": "1.0.0",
"description": "Real-time data ingestion system",
"copyright": "© 2025 sanvil"
}
Examples
curl -X GET http://localhost:8000/version
fetch('http://localhost:8000/version')
.then(res => res.json())
.then(data => console.log(`Version: ${data.version}`));
/metrics
Get system metrics (workers, calls, costs)
Response
{
"workers": {
"webcom": { "status": "healthy", "last_run": "2025-11-15T13:30:00Z" },
"asterisk": { "status": "healthy", "last_run": "2025-11-15T13:30:00Z" }
},
"calls": {
"webcom_active": 3,
"asterisk_active": 1,
"total_today": 245
},
"webcom": {
"line_ids": ["Sickprod90TLW", "Sickprod80FW", ...]
},
"costs": {
"today": 45.50
},
"timestamp": "2025-11-15T13:30:00Z"
}
Examples
curl -X GET http://localhost:8000/metrics
const axios = require('axios');
async function getMetrics() {
const { data } = await axios.get('http://localhost:8000/metrics');
console.log(`Active calls: ${data.calls.webcom_active}`);
console.log(`Cost today: €${data.costs.today}`);
}
getMetrics();
/api/webcom/lines
Get all WebCom line IDs and details
Response
{
"count": 59,
"lines": [
{
"lineId": "Sickprod28GN",
"number": "800026028",
"lineType": "Green",
"group": "Green",
"relations": ["Read", "Write", "Owner"],
"name": null
},
...
]
}
Examples
curl -X GET http://localhost:8000/api/webcom/lines
fetch('http://localhost:8000/api/webcom/lines')
.then(res => res.json())
.then(data => {
console.log(`Total lines: ${data.count}`);
data.lines.forEach(line => {
console.log(`${line.number}: ${line.lineType}`);
});
});
const axios = require('axios');
axios.get('http://localhost:8000/api/webcom/lines')
.then(response => {
console.log(`Total lines: ${response.data.count}`);
console.log('Lines:', response.data.lines);
});
/api/webcom/calls
Get active WebCom calls
Query Parameters
limit(optional) - Max number of calls to return (default: 50)
Response
{
"count": 5,
"calls": [
{
"id": "691880b00a5a19c9db354ac8",
"lineID": "Sickprod90TLW",
"state": "Pending",
"direction": "Entering",
"caller": "331234567",
"called": "899748963",
"callerOperator": "TIM",
"cost": 0.15,
"lineNumber": "899748963",
"timestamp": 1731671400
},
...
]
}
Examples
curl -X GET "http://localhost:8000/api/webcom/calls?limit=10"
fetch('http://localhost:8000/api/webcom/calls?limit=10')
.then(res => res.json())
.then(data => {
console.log(`Active calls: ${data.count}`);
data.calls.forEach(call => {
console.log(`${call.id}: ${call.state} - ${call.caller} → ${call.called}`);
});
});
const axios = require('axios');
async function getCalls() {
const { data } = await axios.get('http://localhost:8000/api/webcom/calls', {
params: { limit: 10 }
});
console.log(`Found ${data.count} calls`);
return data.calls;
}
getCalls();
/api/webcom/calls/{call_id}
Get specific WebCom call by ID
Path Parameters
call_id- Call identifier (e.g., "691880b00a5a19c9db354ac8")
Response (Success)
{
"id": "691880b00a5a19c9db354ac8",
"lineID": "Sickprod90TLW",
"state": "Ok",
"direction": "Entering",
"caller": "331234567",
"called": "899748963",
"callerOperator": "TIM",
"cost": 0.15,
"duration": 45,
"timestamp": 1731671400
}
Response (Not Found)
{
"error": "Call not found"
}
Examples
curl -X GET http://localhost:8000/api/webcom/calls/691880b00a5a19c9db354ac8
const callId = '691880b00a5a19c9db354ac8';
fetch(`http://localhost:8000/api/webcom/calls/${callId}`)
.then(res => res.json())
.then(call => {
if (call.error) {
console.error('Call not found');
} else {
console.log(`Call ${call.id}: ${call.state}`);
console.log(`Duration: ${call.duration}s, Cost: €${call.cost}`);
}
});
const axios = require('axios');
async function getCall(callId) {
try {
const { data } = await axios.get(
`http://localhost:8000/api/webcom/calls/${callId}`
);
return data;
} catch (error) {
console.error('Error fetching call:', error.message);
}
}
getCall('691880b00a5a19c9db354ac8');
/api/webcom/rebates
Get WebCom rebates for a specific month
Query Parameters
month(optional) - Month in YYYY-MM format (default: current month)
Response
{
"month": "2025-11",
"count": 5,
"updated_at": "1731671400",
"rebates": [
{
"type": "Vas",
"operator": "TIMG",
"cost": 2092.68,
"counter": 923
},
{
"type": "Green",
"operator": "WIND",
"cost": -0.07,
"counter": 8
},
...
]
}
Examples
curl -X GET http://localhost:8000/api/webcom/rebates
curl -X GET "http://localhost:8000/api/webcom/rebates?month=2025-10"
fetch('http://localhost:8000/api/webcom/rebates?month=2025-11')
.then(res => res.json())
.then(data => {
console.log(`Rebates for ${data.month}`);
let totalCost = 0;
data.rebates.forEach(rebate => {
console.log(`${rebate.operator} (${rebate.type}): €${rebate.cost}`);
totalCost += rebate.cost;
});
console.log(`Total: €${totalCost.toFixed(2)}`);
});
const axios = require('axios');
async function getRebates(month = null) {
const params = month ? { month } : {};
const { data } = await axios.get(
'http://localhost:8000/api/webcom/rebates',
{ params }
);
console.log(`Rebates for ${data.month}: ${data.count} entries`);
return data.rebates;
}
// Get current month
getRebates();
// Get specific month
getRebates('2025-10');
/
Root endpoint - returns API information and available endpoints
Response
{
"message": "Gateway ARQ API",
"version": "1.0.0",
"status": "healthy",
"endpoints": {
"health": "/health",
"version": "/version",
"metrics": "/metrics",
"webcom": "/api/webcom/*",
"webtv": "/api/webtv/*",
"websocket": "/ws"
}
}
Examples
curl -H "X-API-Key: sk_live_arq_2024_production_key_change_me" \
http://localhost:8000/
📺 /api/webtv/viewers
Get current WebTV CDN viewers count (real-time)
Response
{
"viewers": 23,
"timestamp": "2025-11-15T18:57:08.177349+00:00"
}
Response Fields
viewers- Current number of viewers (integer)timestamp- Timestamp of data fetch (ISO 8601)
Update Frequency
Data is updated every 5 seconds by the WebTV worker.
Examples
curl -H "X-API-Key: sk_live_arq_2024_production_key_change_me" \
http://localhost:8000/api/webtv/viewers
const response = await fetch('http://localhost:8000/api/webtv/viewers', {
headers: { 'X-API-Key': 'sk_live_arq_2024_production_key_change_me' }
});
const data = await response.json();
console.log(`Current viewers: ${data.viewers}`);
import requests
headers = {'X-API-Key': 'sk_live_arq_2024_production_key_change_me'}
response = requests.get('http://localhost:8000/api/webtv/viewers', headers=headers)
data = response.json()
print(f"Current viewers: {data['viewers']}")
📺 /api/webtv/stats
Get WebTV statistics (today + yesterday + comparison)
Response
{
"today": {
"date": "2025-11-15",
"min": 5,
"max": 23,
"avg": 10,
"current": 23,
"hourly": [0, 0, 0, ..., 12, 23, 0, ...] // 24 values
},
"yesterday": {
"date": "2025-11-14",
"min": 0,
"max": 0,
"avg": 0,
"hourly": [0, 0, 0, ...]
},
"comparison": {
"min_change_pct": 0.0,
"max_change_pct": 0.0,
"avg_change_pct": 0.0
},
"timestamp": "2025-11-15T18:57:08.381807+00:00"
}
Response Fields
today.date- Current date (YYYY-MM-DD)today.min- Minimum viewers todaytoday.max- Maximum viewers todaytoday.avg- Average viewers today (rounded)today.current- Current viewerstoday.hourly- Array of 24 hourly samplesyesterday.*- Same structure for yesterdaycomparison.*_change_pct- Percentage change vs yesterday
Data Retention
Hourly samples and daily stats are stored for 7 days.
Examples
curl -H "X-API-Key: sk_live_arq_2024_production_key_change_me" \
http://localhost:8000/api/webtv/stats
const response = await fetch('http://localhost:8000/api/webtv/stats', {
headers: { 'X-API-Key': 'sk_live_arq_2024_production_key_change_me' }
});
const data = await response.json();
// Create Chart.js graph
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: Array.from({length: 24}, (_, i) => `${i}:00`),
datasets: [{
label: 'Today',
data: data.today.hourly,
borderColor: 'rgb(59, 130, 246)'
}]
}
});
🎰 OCR Lotto Endpoints
/api/ocr/process
Process image with OCR and extract Italian Lotto numbers. Includes AI validation for error correction.
Request Body
{
"image_base64": "...", // Base64 encoded JPEG/PNG
"data_estrazione": "2025-12-02", // Extraction date
"ai_validation": true, // Enable AI error correction
"send_telegram": false, // Send result to Telegram
"auto_upload": false // Upload to lotto.sanvil.net
}
Response
{
"success": true,
"wheels": {
"BARI": ["50", "29", "12", "47", "73"],
"CAGLIARI": ["04", "79", "80", "66", "84"],
...
"NAZIONALE": ["16", "25", "48", "81", "88"]
},
"wheels_count": 11,
"confidence": 100.0,
"ai_validation": { "corrections": 5, "confidence": 98 },
"telegram": { "success": true },
"upload": { "success": true, "wheels_uploaded": 11 }
}
/api/ocr/autobot/status
Get AutoBOT worker status (scheduled OCR extraction at 20:01)
Response
{
"status": "idle", // idle|capturing|ocr|validating|completed|error
"wheels_completed": 0,
"total_wheels": 11,
"last_run": "2025-12-02T20:01:00Z"
}
/api/ocr/autobot/start
Manually start AutoBOT OCR extraction from live stream
Request Body
{
"ai_validation": true,
"telegram_notify": true,
"auto_upload": false
}
/api/ocr/telegram/send
Send OCR results to configured Telegram chat
Request Body
{
"message": "ESTRAZIONE LOTTO...",
"parse_mode": "HTML",
"image_base64": "..." // Optional: attach image
}
/api/ocr/lotto/upload
Upload extracted numbers to lotto.sanvil.net
Request Body
{
"wheels": {
"BARI": ["50", "29", "12", "47", "73"],
...
},
"data_estrazione": "2025-12-02"
}
Response
{
"success": true,
"wheels_uploaded": 11,
"results": [
{ "wheel": "BARI", "success": true },
...
]
}
/ws
WebSocket endpoint for real-time call updates
Connection URL
ws://localhost:8000/ws
Event Types
system- Connection establishedheartbeat- Keep-alive ping (every 30s)call_event_realtime- WebCom call updaterebates_updated- Rebates data updatedasterisk_event- Asterisk AMI event (Newchannel, Hangup, etc.)
Call Event Example
{
"id": "691880b00a5a19c9db354ac8",
"lineID": "Sickprod90TLW",
"state": "Pending",
"direction": "Entering",
"caller": "331234567",
"called": "899748963",
"callerOperator": "TIM",
"cost": 0.15,
"lineNumber": "899748963"
}
Examples
const ws = new WebSocket('ws://localhost:8000/ws');
ws.onopen = () => {
console.log('Connected to Gateway ARQ');
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event received:', data);
if (data.id && data.state) {
console.log(`Call ${data.id}: ${data.state}`);
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Disconnected');
};
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8000/ws');
ws.on('open', () => {
console.log('Connected to Gateway ARQ');
});
ws.on('message', (data) => {
const event = JSON.parse(data);
if (event.id && event.state) {
console.log(`Call ${event.id}: ${event.state}`);
console.log(` Caller: ${event.caller}`);
console.log(` Cost: €${event.cost}`);
}
});
ws.on('error', (error) => {
console.error('Error:', error);
});
📞 Asterisk PBX Integration
✅ ACTIVE - Asterisk worker is running via SSH tunnel
Gateway ARQ monitors Asterisk PBX servers in real-time through the Asterisk Manager Interface (AMI). The integration uses SSH tunneling for secure connections to remote PBX servers.
Connection Details
astpbx.sanvil.net
9022
5038
localhost:65532 → astpbx.sanvil.net:9022 → localhost:5038
Data Access
⚠️ NOTE: Asterisk data is available ONLY via WebSocket
Unlike WebCom (which has REST endpoints), Asterisk events are streamed in real-time exclusively through WebSocket. There are no dedicated REST API endpoints for Asterisk call data.
WebSocket Event Format
{
"type": "asterisk_event",
"event": "Newchannel",
"server": "asterisk_1",
"channel": "SIP/trunk-00000042",
"uniqueid": "1731671400.123",
"calleridnum": "331234567",
"calleridname": "John Doe",
"context": "from-trunk",
"exten": "100",
"state": "Ring",
"timestamp": 1731671400
}
AMI Event Types
Newchannel- New call channel createdNewstate- Channel state changed (Ring, Up, etc.)Hangup- Call terminatedDialBegin- Outbound dial initiatedDialEnd- Outbound dial completed
Worker Status (via /metrics)
Check Asterisk worker health through the metrics endpoint:
curl -H "X-API-Key: sk_live_arq_2024_production_key_change_me" \
http://localhost:8000/metrics
Response includes Asterisk worker status:
{
"workers": {
"asterisk": {
"status": "healthy",
"last_run": "2025-11-15T14:30:00Z"
}
},
"stats": {
"asterisk_active": 5
}
}
WebSocket Example (Asterisk Events)
const ws = new WebSocket('ws://localhost:8000/ws?api_key=sk_live_arq_2024_production_key_change_me');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Filter Asterisk events
if (data.type === 'asterisk_event') {
console.log(`[Asterisk] ${data.event} on ${data.server}`);
console.log(` Channel: ${data.channel}`);
console.log(` CallerID: ${data.calleridnum} (${data.calleridname})`);
console.log(` State: ${data.state}`);
}
};
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8000/ws?api_key=sk_live_arq_2024_production_key_change_me');
ws.on('message', (data) => {
const event = JSON.parse(data);
if (event.type === 'asterisk_event') {
if (event.event === 'Newchannel') {
console.log(`📞 New call on ${event.server}: ${event.calleridnum}`);
} else if (event.event === 'Hangup') {
console.log(`📴 Call ended: ${event.channel}`);
}
}
});
Rate Limiting
Currently no rate limiting is enforced. Future versions will implement:
- 100 requests/minute per IP address
- Rate limit headers in responses
- HTTP 429 (Too Many Requests) when exceeded
Error Codes
| Code | Description |
|---|---|
200 |
OK - Request successful |
400 |
Bad Request - Invalid parameters |
404 |
Not Found - Endpoint does not exist |
500 |
Internal Server Error - Server error |