Request Tracking and Costs
SipPulse AI provides a complete request tracking system that allows you to monitor costs, performance, and usage of each API call individually. This is especially useful for:
- Cost control by customer or project
- Detailed usage reports
- Cost pass-through to end customers
What is x-request-id?
Every consumption request (TTS, ASR, LLM, etc.) returns an HTTP header called x-request-id. This unique identifier allows you to track the costs and metrics specific to that request.
ID Format
The ID follows the pattern: req_ + 32 alphanumeric characters
Example: req_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Routes that Return x-request-id
| Route | Method | Response Type | Description |
|---|---|---|---|
/audio/speech | POST | Streaming | Text-to-Speech (OpenAI compatible) |
/tts/generate | POST | JSON | Text-to-Speech |
/asr/transcribe | POST | JSON | Speech-to-Text |
/asr/transcribe/batch | POST | JSON | Speech-to-Text (async batch). Also accepts x-request-id as input (must be a valid UUID). |
/audio/transcriptions | POST | JSON | Speech-to-Text (OpenAI compatible) |
/llms/completion | POST | Streaming | LLM text generation |
/chat/completions | POST | Streaming | LLM Chat (OpenAI compatible) |
/text-inteligence | POST | JSON | Text analysis |
/anonymize | POST | JSON | Text anonymization |
How to Extract x-request-id
const response = await fetch('https://api.sippulse.ai/tts/generate', {
method: 'POST',
headers: {
'Authorization': 'Bearer your_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'openai-tts-1',
input: 'Hello, how can I help you?',
voice: 'alloy'
})
});
// Extract the request ID from the header
const requestId = response.headers.get('x-request-id');
console.log('Request ID:', requestId);
const data = await response.json();import requests
response = requests.post(
'https://api.sippulse.ai/tts/generate',
headers={
'Authorization': 'Bearer your_token',
'Content-Type': 'application/json'
},
json={
'model': 'openai-tts-1',
'input': 'Hello, how can I help you?',
'voice': 'alloy'
}
)
# Extract the request ID from the header
request_id = response.headers.get('x-request-id')
print(f'Request ID: {request_id}')
data = response.json()curl -i -X POST https://api.sippulse.ai/tts/generate \
-H "Authorization: Bearer your_token" \
-H "Content-Type: application/json" \
-d '{
"model": "openai-tts-1",
"input": "Hello, how can I help you?",
"voice": "alloy"
}'
# The x-request-id header will appear in the responseQuerying Usage Details
With the request_id in hand, you can query the complete cost and performance details:
GET https://api.sippulse.ai/usage-requests/{request_id}Asynchronous Processing
Cost calculation and billing are processed asynchronously. This means that immediately after receiving the x-request-id, the usage details may not be available yet (typically a few milliseconds delay).
For programmatic access, consider implementing a small delay or retry logic to ensure you retrieve the data correctly.
Retry Implementation Example
When querying usage details programmatically, it's recommended to implement a retry mechanism:
async function getUsageWithRetry(requestId, maxRetries = 3, delayMs = 100) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(
`https://api.sippulse.ai/usage-requests/${requestId}`,
{ headers: { 'Authorization': 'Bearer your_token' } }
);
if (response.ok) {
const data = await response.json();
// Check if cost data is available
if (data.total_price_local !== undefined) {
return data;
}
}
// Wait before retrying (exponential backoff)
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, delayMs * attempt));
}
} catch (error) {
if (attempt === maxRetries) throw error;
}
}
throw new Error(`Failed to get usage after ${maxRetries} attempts`);
}
// Usage
const usage = await getUsageWithRetry('req_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6');
console.log('Cost:', usage.total_price_local);import time
import requests
def get_usage_with_retry(request_id, max_retries=3, delay_ms=100):
for attempt in range(1, max_retries + 1):
try:
response = requests.get(
f'https://api.sippulse.ai/usage-requests/{request_id}',
headers={'Authorization': 'Bearer your_token'}
)
if response.ok:
data = response.json()
# Check if cost data is available
if data.get('total_price_local') is not None:
return data
# Wait before retrying (exponential backoff)
if attempt < max_retries:
time.sleep((delay_ms * attempt) / 1000)
except Exception as e:
if attempt == max_retries:
raise e
raise Exception(f'Failed to get usage after {max_retries} attempts')
# Usage
usage = get_usage_with_retry('req_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6')
print(f"Cost: {usage['total_price_local']}")Request Example
curl -X GET https://api.sippulse.ai/usage-requests/req_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 \
-H "Authorization: Bearer your_token"Response Example
{
"id": "req_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"organization_id": "org_xxxx",
"user_id": "usr_yyyy",
"agent_id": "agt_zzzz",
"thread_id": "thr_wwww",
"project_id": null,
"auth_mode": "jwt",
"created_at": "2026-01-07T10:30:45.000Z",
"event_timestamp": "2026-01-07T10:30:45.000Z",
"updated_at": "2026-01-07T10:30:47.000Z",
"execution_time_ms": 2350,
"processing_time_ms": 50,
"total_price_local": 0.0025,
"speed": 10.21,
"items": [
{
"charge_subtype": "text-to-speech",
"pricing_rule": "tts_char",
"identifier": "openai-tts-1",
"quantity": 24,
"unit": "character",
"billed_quantity": 24,
"billed_unit": "character",
"price_amount": 0.0024,
"currency": "BRL",
"event_timestamp": "2026-01-07T10:30:45.000Z"
}
]
}Response Structure
Main Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique request ID |
organization_id | string | Organization ID |
user_id | string | ID of the user who made the request |
agent_id | string | Agent ID (if applicable) |
thread_id | string | Conversation thread ID (if applicable) |
auth_mode | string | Authentication type: jwt, api_key, guest_token, oauth |
execution_time_ms | number | Execution time in milliseconds |
processing_time_ms | number | Processing/setup time in milliseconds |
total_price_local | number | Total cost in local currency |
speed | number | Throughput (tokens/s, characters/s, etc.) |
Item Fields
Each item represents a billed component of the request:
| Field | Type | Description |
|---|---|---|
charge_subtype | string | Usage type, such as text-to-speech, speech-to-text, text-generation or agent |
pricing_rule | string | Applied pricing rule, such as tts_char or voice |
identifier | string | Identifier of the model, resource or agent used. Voice items carry the agent ID |
quantity | number | Metered amount, in unit |
unit | string | Unit of quantity: token, character, second, etc. |
billed_quantity | number or null | Amount the price was multiplied by, in billed_unit. Null for usage recorded before billed quantities existed |
billed_unit | string or null | Unit of billed_quantity, such as minute for voice agent sessions. Null under the same conditions as billed_quantity |
price_amount | number | Selling price of the item, in currency |
currency | string | Currency of price_amount |
event_timestamp | string | ISO-8601 time of the event |
Billed quantity and rounding explains how billed_quantity is rounded per product.
Usage Fields (LLM)
In /v1/llms/completion responses and in thread message metadata, the usage object may carry:
| Field | Type | Description |
|---|---|---|
input_tokens_details | object | Input cache, present only when the provider reports it. May carry cached_tokens and/or cache_write_tokens |
output_tokens_details | object | Reasoning tokens, present only when the provider reports reasoning_tokens |
The platform never invents these numbers: the field appears only when the provider reports it. For cache, a reported zero is preserved and appears as 0. input_tokens still counts cached tokens in full. In items[].performance, is_cached means the provider reported cache reads for this request; it is not a platform calculation.
Billed Voice Sessions
GET /usage/sessions lists billed voice sessions: one row per voice session (agent voice time), newest first, with the metered duration, the billed minutes and the selling price. Use it to reconcile voice billing with your own records without querying each request.
Authentication: an API key with the billing:read permission. A project-scoped API key, or the X-Project-ID header, narrows the results to one project.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
from | Yes | First local day, YYYY-MM-DD, inclusive, in tz |
to | Yes | Last local day, YYYY-MM-DD, inclusive, in tz. Must be on or after from |
tz | No | IANA timezone the days are read in. Default UTC |
agent_id | No | Only sessions of this agent |
cursor | No | next_cursor of the previous page |
limit | No | Page size, from 1 to 1000. Default 100 |
A session belongs to the period of its end time (ended_at). A session that starts on one day and ends on the next counts for the day it ends.
Request Example
curl -X GET "https://api.sippulse.ai/usage/sessions?from=2026-09-01&to=2026-09-30&tz=America/Sao_Paulo&limit=100" \
-H "Authorization: Bearer your_api_key"Response Example
{
"data": [
{
"request_id": "vce_a1b2c3d4e5f6g7h8",
"thread_id": "thr_wwww",
"room_name": "room_xxxx",
"agent_id": "agt_zzzz",
"project_id": "prj_qqqq",
"ended_at": "2026-09-15T17:22:01.000Z",
"duration_seconds": 61,
"billed_quantity": 2,
"billed_unit": "minute",
"price": 0.0480,
"currency": "BRL"
}
],
"has_more": true,
"next_cursor": "eyJ0IjoiMjAyNi0wOS0xNVQxNzoyMjowMS4wMDBaIiwiaWQiOiJ2Y2VfYTFiMmMzZDRlNWY2ZzdoOCJ9"
}Pagination
Results are ordered by end time, newest first. To read the next page, send the next_cursor value as cursor, and repeat until has_more is false. The cursor is opaque: pass it back unchanged and do not build or edit it.
Response Fields
| Field | Type | Description |
|---|---|---|
request_id | string | Session ID, prefixed vce_ |
thread_id | string or null | Conversation thread |
room_name | string or null | Voice room name |
agent_id | string or null | Agent that handled the session |
project_id | string or null | Project of the session |
ended_at | string | ISO-8601 end of the session |
duration_seconds | number | Metered session length, in seconds |
billed_quantity | number or null | Billed quantity, in billed_unit. Null for sessions billed before billed quantities were recorded |
billed_unit | string or null | Unit of billed_quantity: minute for voice sessions. Null under the same conditions as billed_quantity |
price | number | Selling price of the session voice time, in currency |
currency | string | Currency of price |
Sessions billed before billed quantities were recorded return billed_quantity and billed_unit as null. Their price is still returned.
Errors
The endpoint returns 400 when from or to is not a valid YYYY-MM-DD date, when to is before from, when tz is not an IANA timezone name, or when cursor was not returned by this endpoint.
Use Cases
1. Cost Pass-Through to Customers
Store the request_id along with your customer's identifier:
async function processRequest(clientId, text) {
const response = await ttsGenerate(text);
const requestId = response.headers.get('x-request-id');
// Save to database
await db.usageLog.create({
client_id: clientId,
request_id: requestId,
timestamp: new Date()
});
return response;
}
// Later, to generate cost report:
async function generateClientReport(clientId) {
const logs = await db.usageLog.findByClientId(clientId);
let totalCost = 0;
for (const log of logs) {
const usage = await fetch(`/usage-requests/${log.request_id}`);
totalCost += usage.total_price_local;
}
return totalCost;
}2. Performance Monitoring
Track performance metrics:
const response = await asrTranscribe(audioFile);
const requestId = response.headers.get('x-request-id');
const usage = await getUsageRequest(requestId);
const metrics = {
executionTime: usage.execution_time_ms,
throughput: usage.speed,
cost: usage.total_price_local
};
// Send to monitoring system
monitoring.track('asr_performance', metrics);Best Practices
- Always store the request_id for important requests you may need to audit
- Don't expose request_ids to end users, as they allow access to cost information
- Implement rate limiting based on queried costs
- Set up alerts for requests with higher than expected costs
Next Steps
- REST API - Complete API documentation
- Usage Dashboard - Aggregated cost visualization
- Credits and Billing - Credit management
