Skip to content

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 ​

RouteMethodResponse TypeDescription
/audio/speechPOSTStreamingText-to-Speech (OpenAI compatible)
/tts/generatePOSTJSONText-to-Speech
/asr/transcribePOSTJSONSpeech-to-Text
/asr/transcribe/batchPOSTJSONSpeech-to-Text (async batch). Also accepts x-request-id as input (must be a valid UUID).
/audio/transcriptionsPOSTJSONSpeech-to-Text (OpenAI compatible)
/llms/completionPOSTStreamingLLM text generation
/chat/completionsPOSTStreamingLLM Chat (OpenAI compatible)
/text-inteligencePOSTJSONText analysis
/anonymizePOSTJSONText anonymization

How to Extract x-request-id ​

javascript
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();
python
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()
bash
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 response

Querying 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:

javascript
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);
python
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 ​

bash
curl -X GET https://api.sippulse.ai/usage-requests/req_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 \
  -H "Authorization: Bearer your_token"

Response Example ​

json
{
  "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 ​

FieldTypeDescription
idstringUnique request ID
organization_idstringOrganization ID
user_idstringID of the user who made the request
agent_idstringAgent ID (if applicable)
thread_idstringConversation thread ID (if applicable)
auth_modestringAuthentication type: jwt, api_key, guest_token, oauth
execution_time_msnumberExecution time in milliseconds
processing_time_msnumberProcessing/setup time in milliseconds
total_price_localnumberTotal cost in local currency
speednumberThroughput (tokens/s, characters/s, etc.)

Item Fields ​

Each item represents a billed component of the request:

FieldTypeDescription
charge_subtypestringUsage type, such as text-to-speech, speech-to-text, text-generation or agent
pricing_rulestringApplied pricing rule, such as tts_char or voice
identifierstringIdentifier of the model, resource or agent used. Voice items carry the agent ID
quantitynumberMetered amount, in unit
unitstringUnit of quantity: token, character, second, etc.
billed_quantitynumber or nullAmount the price was multiplied by, in billed_unit. Null for usage recorded before billed quantities existed
billed_unitstring or nullUnit of billed_quantity, such as minute for voice agent sessions. Null under the same conditions as billed_quantity
price_amountnumberSelling price of the item, in currency
currencystringCurrency of price_amount
event_timestampstringISO-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:

FieldTypeDescription
input_tokens_detailsobjectInput cache, present only when the provider reports it. May carry cached_tokens and/or cache_write_tokens
output_tokens_detailsobjectReasoning 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 ​

ParameterRequiredDescription
fromYesFirst local day, YYYY-MM-DD, inclusive, in tz
toYesLast local day, YYYY-MM-DD, inclusive, in tz. Must be on or after from
tzNoIANA timezone the days are read in. Default UTC
agent_idNoOnly sessions of this agent
cursorNonext_cursor of the previous page
limitNoPage 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 ​

bash
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 ​

json
{
  "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 ​

FieldTypeDescription
request_idstringSession ID, prefixed vce_
thread_idstring or nullConversation thread
room_namestring or nullVoice room name
agent_idstring or nullAgent that handled the session
project_idstring or nullProject of the session
ended_atstringISO-8601 end of the session
duration_secondsnumberMetered session length, in seconds
billed_quantitynumber or nullBilled quantity, in billed_unit. Null for sessions billed before billed quantities were recorded
billed_unitstring or nullUnit of billed_quantity: minute for voice sessions. Null under the same conditions as billed_quantity
pricenumberSelling price of the session voice time, in currency
currencystringCurrency 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:

javascript
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:

javascript
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 ​

  1. Always store the request_id for important requests you may need to audit
  2. Don't expose request_ids to end users, as they allow access to cost information
  3. Implement rate limiting based on queried costs
  4. Set up alerts for requests with higher than expected costs

Next Steps ​