Billing and Usage Tracking
Learn how to monitor API usage, understand billing, and optimize costs for your CallCov integration.
Overviewβ
CallCov uses usage-based pricing where you're billed for:
- Call analysis minutes - Total audio duration processed
- API requests - Calls to any API endpoint (rate limited)
- Storage - Audio files stored in your account (optional retention)
Tracking Usageβ
Get Current Usageβ
Monitor your usage in real-time to avoid surprises:
import requestsfrom datetime import datetime, timedelta
API_KEY = "your_api_key_here"API_URL = "https://api.callcov.com/api/v1"
def get_current_usage(): """Retrieve current billing period usage""" headers = {"X-API-Key": API_KEY}
response = requests.get( f"{API_URL}/billing/usage", headers=headers )
if response.status_code == 200: return response.json() else: raise Exception(f"Error: {response.status_code} - {response.text}")
# Usageusage = get_current_usage()print(f"Billing Period: {usage['period']['start']} to {usage['period']['end']}")print(f"Minutes Analyzed: {usage['metrics']['minutes_analyzed']}")print(f"API Requests: {usage['metrics']['api_requests']}")print(f"Storage Used: {usage['metrics']['storage_gb']} GB")Usage Response Structureβ
{
"period": {
"start": "2024-01-01T00:00:00Z",
"end": "2024-01-31T23:59:59Z",
"billing_cycle": "monthly"
},
"metrics": {
"minutes_analyzed": 1250.5,
"api_requests": 3420,
"storage_gb": 15.2
},
"limits": {
"minutes_limit": 10000,
"requests_limit": 100000,
"storage_limit_gb": 100
},
"costs": {
"minutes_cost": 125.05,
"api_cost": 0.00,
"storage_cost": 3.04,
"total": 128.09,
"currency": "USD"
}
}
Understanding Pricing Tiersβ
CallCov offers tiered pricing that scales with your volume:
| Tier | Minutes/Month | Price per Minute | Monthly Base |
|---|---|---|---|
| Starter | 0 - 1,000 | $0.10 | $0 |
| Growth | 1,001 - 10,000 | $0.08 | $0 |
| Business | 10,001 - 100,000 | $0.06 | $0 |
| Enterprise | 100,000+ | Custom | Contact sales |
Calculate Estimated Costsβ
Estimate costs before processing:
Python:
def estimate_analysis_cost(duration_seconds, tier_pricing=None):
"""Calculate estimated cost for analysis"""
if tier_pricing is None:
# Default pricing tiers (per minute)
tier_pricing = [
{'max_minutes': 1000, 'price_per_minute': 0.10},
{'max_minutes': 10000, 'price_per_minute': 0.08},
{'max_minutes': 100000, 'price_per_minute': 0.06},
{'max_minutes': float('inf'), 'price_per_minute': 0.05}
]
minutes = duration_seconds / 60
total_cost = 0
# Get current usage to determine tier
usage = get_current_usage()
current_minutes = usage['metrics']['minutes_analyzed']
# Calculate cost based on tier
remaining_minutes = minutes
for tier in tier_pricing:
if current_minutes >= tier['max_minutes']:
continue
# Minutes in this tier
tier_minutes = min(
remaining_minutes,
tier['max_minutes'] - current_minutes
)
tier_cost = tier_minutes * tier['price_per_minute']
total_cost += tier_cost
remaining_minutes -= tier_minutes
current_minutes += tier_minutes
if remaining_minutes <= 0:
break
return {
'minutes': minutes,
'estimated_cost': round(total_cost, 2),
'currency': 'USD'
}
# Usage
result = estimate_analysis_cost(3600) # 1 hour = 60 minutes
print(f"Processing {result['minutes']} minutes")
print(f"Estimated cost: ${result['estimated_cost']}")
Node.js:
function estimateAnalysisCost(durationSeconds, tierPricing = null) {
if (!tierPricing) {
tierPricing = [
{ max_minutes: 1000, price_per_minute: 0.10 },
{ max_minutes: 10000, price_per_minute: 0.08 },
{ max_minutes: 100000, price_per_minute: 0.06 },
{ max_minutes: Infinity, price_per_minute: 0.05 }
];
}
const minutes = durationSeconds / 60;
let totalCost = 0;
// Get current usage to determine tier
const usage = getCurrentUsage();
let currentMinutes = usage.metrics.minutes_analyzed;
// Calculate cost based on tier
let remainingMinutes = minutes;
for (const tier of tierPricing) {
if (currentMinutes >= tier.max_minutes) continue;
const tierMinutes = Math.min(
remainingMinutes,
tier.max_minutes - currentMinutes
);
const tierCost = tierMinutes * tier.price_per_minute;
totalCost += tierCost;
remainingMinutes -= tierMinutes;
currentMinutes += tierMinutes;
if (remainingMinutes <= 0) break;
}
return {
minutes: minutes,
estimated_cost: Math.round(totalCost * 100) / 100,
currency: 'USD'
};
}
// Usage
const result = estimateAnalysisCost(3600);
console.log(`Processing ${result.minutes} minutes`);
console.log(`Estimated cost: $${result.estimated_cost}`);
PHP:
<?php
function estimateAnalysisCost($durationSeconds, $tierPricing = null) {
if ($tierPricing === null) {
$tierPricing = [
['max_minutes' => 1000, 'price_per_minute' => 0.10],
['max_minutes' => 10000, 'price_per_minute' => 0.08],
['max_minutes' => 100000, 'price_per_minute' => 0.06],
['max_minutes' => INF, 'price_per_minute' => 0.05]
];
}
$minutes = $durationSeconds / 60;
$totalCost = 0;
$usage = getCurrentUsage();
$currentMinutes = $usage['metrics']['minutes_analyzed'];
$remainingMinutes = $minutes;
foreach ($tierPricing as $tier) {
if ($currentMinutes >= $tier['max_minutes']) continue;
$tierMinutes = min(
$remainingMinutes,
$tier['max_minutes'] - $currentMinutes
);
$tierCost = $tierMinutes * $tier['price_per_minute'];
$totalCost += $tierCost;
$remainingMinutes -= $tierMinutes;
$currentMinutes += $tierMinutes;
if ($remainingMinutes <= 0) break;
}
return [
'minutes' => $minutes,
'estimated_cost' => round($totalCost, 2),
'currency' => 'USD'
];
}
// Usage
$result = estimateAnalysisCost(3600);
echo "Processing " . $result['minutes'] . " minutes\n";
echo "Estimated cost: $" . $result['estimated_cost'] . "\n";
Viewing Invoicesβ
Get Invoice Historyβ
Python:
def get_invoices(limit=10, starting_after=None):
"""List billing invoices"""
headers = {"X-API-Key": API_KEY}
params = {"limit": limit}
if starting_after:
params["starting_after"] = starting_after
response = requests.get(
f"{API_URL}/billing/invoices",
headers=headers,
params=params
)
return response.json()
# Usage
result = get_invoices(limit=5)
print(f"Total Invoices: {result['total_count']}")
for inv in result['data']:
print(f" {inv['period_start']} - ${inv['amount_paid']} ({inv['status']})")
Node.js:
async function getInvoices(limit = 10, startingAfter = null) {
try {
const params = { limit: limit };
if (startingAfter) {
params.starting_after = startingAfter;
}
const response = await axios.get(
`${API_URL}/billing/invoices`,
{
headers: { 'X-API-Key': API_KEY },
params: params
}
);
return response.data;
} catch (error) {
throw new Error(`Error: ${error.response.status} - ${error.response.data}`);
}
}
// Usage
getInvoices(5).then(result => {
console.log(`Total Invoices: ${result.total_count}`);
result.data.forEach(inv => {
console.log(` ${inv.period_start} - $${inv.amount_paid} (${inv.status})`);
});
});
Ruby:
def get_invoices(limit = 10, starting_after = nil)
uri = URI("#{API_URL}/billing/invoices")
params = { limit: limit }
params[:starting_after] = starting_after if starting_after
uri.query = URI.encode_www_form(params)
request = Net::HTTP::Get.new(uri)
request['X-API-Key'] = API_KEY
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)
end
# Usage
result = get_invoices(limit: 5)
puts "Total Invoices: #{result['total_count']}"
result['data'].each do |inv|
puts " #{inv['period_start']} - $#{inv['amount_paid']} (#{inv['status']})"
end
Download Invoice PDFβ
Get a downloadable PDF for any invoice:
def download_invoice_pdf(invoice_id, output_path): """Download invoice as PDF""" headers = {"X-API-Key": API_KEY}
response = requests.get( f"{API_URL}/billing/invoices/{invoice_id}/pdf", headers=headers, stream=True )
if response.status_code == 200: with open(output_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) return output_path else: raise Exception(f"Error: {response.status_code} - {response.text}")
# Usagepdf_path = download_invoice_pdf("inv_abc123", "./invoice_january.pdf")print(f"Invoice saved to: {pdf_path}")Usage Alerts and Monitoringβ
Set Up Usage Alertsβ
Configure alerts to notify you when approaching limits:
def create_usage_alert(metric, threshold, notification_email): """Create usage alert""" headers = {"X-API-Key": API_KEY} data = { "metric": metric, # 'minutes', 'requests', 'storage' "threshold": threshold, # Percentage (0-100) "notification_email": notification_email }
response = requests.post( f"{API_URL}/billing/alerts", headers=headers, json=data )
if response.status_code == 201: return response.json() else: raise Exception(f"Error: {response.status_code} - {response.text}")
# Usagealert = create_usage_alert( metric='minutes', threshold=80, # Alert at 80% of limit notification_email='billing@yourcompany.com')print(f"Alert created: {alert['id']}")print(f"Will notify at {alert['threshold']}% of {alert['metric']} limit")Cost Optimization Strategiesβ
1. Batch Processingβ
Process multiple calls together to reduce overhead:
def batch_process_calls(call_urls):
"""Process multiple calls efficiently"""
# Submit all analyses
analysis_ids = []
for url in call_urls:
response = submit_analysis(url)
analysis_ids.append(response['id'])
# Use webhooks to avoid polling costs
# Each poll = 1 API request
# Webhook = 0 API requests from your side
return analysis_ids
2. Cache Resultsβ
Avoid re-analyzing the same call:
from functools import lru_cache
@lru_cache(maxsize=1000)
def get_cached_analysis(call_hash):
"""Cache analysis results to avoid duplicates"""
# Check if call was already analyzed
# Use audio file hash as cache key
return get_analysis_results(call_hash)
3. Use Audio Compressionβ
Reduce storage costs by compressing audio before upload:
from pydub import AudioSegment
def compress_audio(input_path, output_path, target_bitrate="64k"):
"""Compress audio to reduce size"""
audio = AudioSegment.from_file(input_path)
# Convert to mono (reduces size by ~50%)
audio = audio.set_channels(1)
# Reduce bitrate
audio.export(
output_path,
format="mp3",
bitrate=target_bitrate
)
return output_path
4. Clean Up Old Dataβ
Delete stored audio files you no longer need:
def cleanup_old_analyses(days_old=90):
"""Delete analyses older than specified days"""
cutoff_date = datetime.now() - timedelta(days=days_old)
# Get old analyses
analyses = get_analyses(created_before=cutoff_date)
for analysis in analyses:
# Delete audio file to reduce storage costs
delete_analysis(analysis['id'])
return len(analyses)
Best Practicesβ
1. Monitor Usage Regularlyβ
Set up automated monitoring:
import smtplib
from email.mime.text import MIMEText
def check_usage_and_alert():
"""Daily usage check"""
usage = get_current_usage()
minutes_used = usage['metrics']['minutes_analyzed']
minutes_limit = usage['limits']['minutes_limit']
usage_percent = (minutes_used / minutes_limit) * 100
if usage_percent > 80:
send_alert_email(
subject="CallCov Usage Alert",
body=f"Usage at {usage_percent:.1f}% ({minutes_used}/{minutes_limit} minutes)"
)
2. Estimate Before Processingβ
Check costs before submitting large batches:
def process_with_budget_check(call_urls, max_budget=100):
"""Only process if within budget"""
# Calculate total duration
total_duration = sum(get_audio_duration(url) for url in call_urls)
# Estimate cost
estimate = estimate_analysis_cost(total_duration)
if estimate['estimated_cost'] > max_budget:
raise Exception(
f"Estimated cost ${estimate['estimated_cost']} exceeds budget ${max_budget}"
)
# Proceed with processing
return batch_process_calls(call_urls)
3. Use Webhooks Instead of Pollingβ
Polling wastes API requests:
# β BAD: Polling costs API requests
while True:
analysis = get_analysis(analysis_id) # 1 API request each time
if analysis['status'] == 'completed':
break
time.sleep(10)
# β
GOOD: Webhooks are free
submit_analysis(
audio_url=url,
webhook_url='https://yourapp.com/webhooks'
)
# Your webhook receives results when ready
4. Implement Rate Limitingβ
Avoid hitting API limits:
import time
from collections import deque
class RateLimiter:
def __init__(self, max_requests, time_window):
self.max_requests = max_requests
self.time_window = time_window
self.requests = deque()
def wait_if_needed(self):
now = time.time()
# Remove old requests outside time window
while self.requests and self.requests[0] < now - self.time_window:
self.requests.popleft()
if len(self.requests) >= self.max_requests:
sleep_time = self.time_window - (now - self.requests[0])
time.sleep(sleep_time)
self.requests.append(time.time())
# Usage
limiter = RateLimiter(max_requests=100, time_window=60) # 100 req/minute
for call in calls:
limiter.wait_if_needed()
submit_analysis(call)
Billing FAQβ
Q: When am I charged? A: Charges accrue as you use the service. You're billed monthly at the end of each billing period.
Q: What counts as an API request? A: Any HTTP request to the API (GET, POST, etc.) except webhooks you receive from us.
Q: How is audio duration calculated? A: Based on actual audio length, not wall-clock processing time. A 10-minute call = 10 billable minutes, even if processing takes 2 minutes.
Q: Can I get a refund? A: Contact support@callcov.com for refund requests. We evaluate on a case-by-case basis.
Q: What happens if I exceed my limit? A: Your requests will be rate-limited. Upgrade your plan or wait for the next billing cycle.
Next Stepsβ
- Authentication Guide - Set up API access
- Submitting Analysis - Start processing calls
- Production Best Practices - Optimize your integration
- Error Handling - Handle billing errors gracefully