Developer Dashboard

Response Headers

All AvalAI API responses include standard HTTP headers plus custom headers that provide important information about your requests, rate limits, and cost tracking.

Table of Contents


Request Tracking Headers

avalai-request-id

The most important header for cost tracking and debugging.

Every API response includes a unique avalai-request-id header that contains a UUID identifying that specific request. This ID is essential for:

  • Precise Cost Tracking: Use it with /user/v1/transactions/lookup to get exact cost details
  • Debugging: Reference this ID when reporting issues to support
  • Request Correlation: Track requests across your systems
  • Audit Trails: Maintain records of API calls

Format: UUID v7 (e.g., 01a009d5-ec91-74c2-8ffa-9eba731dfc9e)

Example:

avalai-request-id: 01a009d5-ec91-74c2-8ffa-9eba731dfc9e

Header change notice: avalai-request-id replaces the legacy x-request-id header. During a 60-day transition window both headers are returned with the same value. See the Migration Timeline and the release announcement for details.

x-request-id (legacy, deprecated)

The x-request-id header is the legacy name for the AvalAI request ID. Because some CDNs also use x-request-id for their own tracing and can overwrite the header, it can no longer be treated as an unambiguous AvalAI identifier.

  • Until 2026-10-15: AvalAI returns x-request-id alongside avalai-request-id, with the same UUID value.
  • After 2026-10-15: AvalAI stops returning x-request-id. Any x-request-id you observe after that date was added by an intermediary such as a CDN and does not identify your AvalAI request.

Read avalai-request-id for cost lookup, support traces, and logging. If you need backward-compatible code during the transition window, fall back to x-request-id only while avalai-request-id is absent:

python
request_id = response.headers.get("avalai-request-id") or response.headers.get(
    "x-request-id"
)

Migration Timeline

Why the header changed

Some CDNs that sit in front of API origins set their own x-request-id header and can overwrite or reuse that header name for their internal tracing. When a request passes through such a CDN, the observed value may identify the CDN hop instead of the AvalAI request. To keep cost lookup and support traces unambiguous, AvalAI returns its request ID in the dedicated avalai-request-id header.

Transition window

PhaseDatesResponse headers
Dual-header window opens2026-08-16Both avalai-request-id and x-request-id are returned with the same UUID value
Dual-header window closes2026-10-15Last day AvalAI returns x-request-id
Legacy header retiredAfter 2026-10-15Only avalai-request-id is returned by AvalAI

Representative response headers during the transition window:

x-ratelimit-limit-requests: 1500
x-ratelimit-remaining-requests: 1499
x-ratelimit-limit-tokens: 30000000
x-ratelimit-remaining-tokens: 29999827
x-ratelimit-reset-requests: 50s
x-ratelimit-reset-tokens: 50s
x-request-id: 01a009d5-ec91-74c2-8ffa-9eba731dfc9e
avalai-request-id: 01a009d5-ec91-74c2-8ffa-9eba731dfc9e

Required action

  1. Update header readers to use avalai-request-id before 2026-10-15. During the window, either header resolves to the same request.
  2. Stop parsing, logging, or billing against x-request-id; after the window it may belong to a CDN.
  3. Keep the request_id fields returned in response bodies (for example, the Videos API); they carry the same UUID value as avalai-request-id and are unaffected by this change.
  4. No change is required for the X-Client-Request-Id request header described below.

API Metadata Headers

Some OpenAI-compatible routes can return additional metadata headers. Treat them as useful diagnostics, not as required fields on every AvalAI provider route:

HeaderMeaningHow to use it
openai-processing-msUpstream model-processing time in milliseconds.Separate provider/model latency from your app, network, and queue time.
openai-versionREST API version used by the upstream-compatible route.Log during SDK or API migrations so behavior changes are easier to trace.
openai-organizationUpstream organization associated with the request, when exposed.Use only for debugging; do not depend on it for AvalAI account authorization.
service_tier or tier metadataProcessing tier actually used for the request when returned by the route.Compare requested vs. served processing mode in latency investigations.

Do not build billing logic from these metadata headers. For AvalAI billing and reseller reconciliation, avalai-request-id plus the User API transaction lookup remains the authoritative path.

Client-Supplied Request IDs

OpenAI-compatible routes can also accept a request header named X-Client-Request-Id. Use it as your own trace ID: generate a unique value per API attempt, send it with the request, and log it beside the returned avalai-request-id.

This is useful when a timeout or network error prevents your application from receiving response headers. If the selected AvalAI route preserves upstream OpenAI-compatible metadata, support can use your client request ID as a second lookup key. Keep it ASCII-only, no longer than 512 characters, and unique per request. Do not replace avalai-request-id; the AvalAI response ID remains the authoritative ID for cost lookup and support.

bash
curl https://api.avalai.ir/v1/responses \
  -H "Authorization: Bearer $AVALAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Client-Request-Id: 123e4567-e89b-12d3-a456-426614174000" \
  -d '{
    "model": "gpt-5.4-mini",
    "input": "Return a one-line health check."
  }'

Accessing avalai-request-id via SDKs

When using official SDKs like the OpenAI Python SDK, capture the header through the raw-response API so your code reads avalai-request-id directly:

Python (OpenAI SDK)

python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["AVALAI_API_KEY"],
    base_url="https://api.avalai.ir/v1",
)

raw_response = client.chat.completions.with_raw_response.create(
    model="gpt-5.4-mini",
    messages=[{"role": "user", "content": "Hello!"}],
)
completion = raw_response.parse()

# Access the request ID from the response headers
request_id = raw_response.headers.get("avalai-request-id")
print(f"Request ID: {request_id}")
# Output: Request ID: 01a009d5-ec91-74c2-8ffa-9eba731dfc9e

Note

During the transition window you can also read the legacy value with raw_response.headers.get("x-request-id"); both resolve to the same UUID. After 2026-10-15 only avalai-request-id is returned, so migrate all readers before that date. SDK attributes derived from the legacy header name should not be relied upon after the window closes.

Responses API version

Use this version when the selected model supports /v1/responses. messages moves to input, and the final text is read from response.output_text.

python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["AVALAI_API_KEY"],
    base_url="https://api.avalai.ir/v1",
)

response = client.responses.create(
    model="gpt-5.4-mini",
    instructions="You are a helpful assistant.",
    input="Hello!",
)

print(response.output_text)
  • messagesinput
  • system message → instructions or a developer item
  • choices[0].message.contentresponse.output_text
  • for tools and multimodal output, inspect response.output by item type.

Accessing avalai-request-id with LangChain

LangChain does not directly expose raw HTTP response headers from the underlying API calls. However, you can capture the avalai-request-id header by using a custom HTTP client that intercepts the response headers.

Python LangChain v0.3 (Synchronous)

python
import httpx
from langchain_openai import ChatOpenAI
from langchain_core.callbacks import BaseCallbackHandler
from contextvars import ContextVar
import os

# Store headers in context var for thread safety
request_headers: ContextVar[dict] = ContextVar("request_headers", default={})


class HeaderCapturingClient(httpx.Client):
    def send(self, request, **kwargs):
        response = super().send(request, **kwargs)
        request_headers.set(dict(response.headers))
        return response


class HeaderAccessCallback(BaseCallbackHandler):
    def __init__(self):
        self.request_id = None

    def on_llm_end(self, response, **kwargs):
        headers = request_headers.get()
        self.request_id = headers.get("avalai-request-id")
        print(f"Captured avalai-request-id: {self.request_id}")


# Setup
http_client = HeaderCapturingClient()
chat_generator = ChatOpenAI(
    base_url="https://api.avalai.ir/v1",
    api_key=os.getenv("AVALAI_API_KEY"),
    model="gpt-5.4-mini",
    http_client=http_client,
)

callback = HeaderAccessCallback()
response = chat_generator.invoke("say hi", config={"callbacks": [callback]})

print(f"Request ID from callback: {callback.request_id}")

Python LangChain v0.3 (Asynchronous)

python
import httpx
from contextvars import ContextVar
from langchain_openai import ChatOpenAI
from langchain_core.callbacks import AsyncCallbackHandler
import os

request_headers: ContextVar[dict] = ContextVar("request_headers", default={})


class AsyncHeaderCapturingClient(httpx.AsyncClient):
    async def send(self, request, **kwargs):
        response = await super().send(request, **kwargs)
        request_headers.set(dict(response.headers))
        return response


class AsyncHeaderAccessCallback(AsyncCallbackHandler):
    def __init__(self):
        self.request_id = None

    async def on_llm_end(self, response, **kwargs):
        headers = request_headers.get()
        self.request_id = headers.get("avalai-request-id")
        print(f"Captured avalai-request-id: {self.request_id}")


# Setup
http_client = AsyncHeaderCapturingClient()
chat_generator = ChatOpenAI(
    base_url="https://api.avalai.ir/v1",
    api_key=os.getenv("AVALAI_API_KEY"),
    model="gpt-5.4-mini",
    http_async_client=http_client,
)

# Usage (run in async context)
callback = AsyncHeaderAccessCallback()
response = await chat_generator.ainvoke("say hi", config={"callbacks": [callback]})

How it works: This approach uses a custom httpx client that captures the response headers into a context variable. The LangChain callback then accesses these headers after the LLM call completes. The ContextVar ensures thread safety when making concurrent requests. During the transition window you can fall back to headers.get("x-request-id") if the new header is missing.

Accessing via HTTP Headers (Other SDKs)

For other SDKs or direct HTTP requests, access the header directly from the response:

SDK/MethodHow to Access
Python (OpenAI)client.chat.completions.with_raw_response.create(...).headers.get("avalai-request-id")
Python (requests)response.headers.get("avalai-request-id")
JavaScript (OpenAI)(await ...create(...).withResponse()).response.headers.get("avalai-request-id")
JavaScript (fetch)response.headers.get("avalai-request-id")
Go (net/http)resp.Header.Get("avalai-request-id")
PHP (curl)Extract with preg_match('/avalai-request-id:\s*([^\r\n]+)/i', ...)

For Resellers: This header is crucial for your business. Always capture and store it to track the exact cost of each API call. The /user/v1/transactions/lookup endpoint returns 100% accurate cost data within 30 seconds using this ID. See our Reseller Cost Tracking Guide for a complete workflow.


Rate Limit Headers

AvalAI implements rate limiting to ensure fair usage and system stability. Every API response includes headers that inform you about your current rate limit status.

Request-Based Rate Limits

HeaderDescriptionExample
x-ratelimit-limit-requestsMaximum requests allowed in the time window30000
x-ratelimit-remaining-requestsRemaining requests in current window29999
x-ratelimit-reset-requestsTime until the request limit resets45s

Token-Based Rate Limits

HeaderDescriptionExample
x-ratelimit-limit-tokensMaximum tokens allowed in the time window150000000
x-ratelimit-remaining-tokensRemaining tokens in current window149999982
x-ratelimit-reset-tokensTime until the token limit resets45s

Project-Scoped Token Headers

Some upstream-compatible routes may include project-token headers when a project-level token bucket applies:

HeaderDescriptionExample
x-ratelimit-limit-project-tokensMaximum project-scoped tokens allowed in the current window60000
x-ratelimit-remaining-project-tokensRemaining project-scoped tokens before throttling57000
x-ratelimit-reset-project-tokensTime until the project-scoped token bucket resets3s

If these headers are present, monitor them in addition to request and token headers. A request can be throttled because the project bucket is exhausted even when the route-level token bucket still has capacity.

Rate Limit Tiers

Your rate limits depend on your account tier (0-5). Higher tiers have higher limits. See Rate Limits Guide for detailed tier information.

429 Too Many Requests

If you exceed your rate limits, you'll receive a 429 status code with a Retry-After header indicating when you can retry:

HTTP/2 429
Retry-After: 45
x-ratelimit-limit-requests: 30000
x-ratelimit-remaining-requests: 0
x-ratelimit-reset-requests: 45s

Header-Driven Retry Workflow

Use response headers to make retries predictable instead of retrying blindly:

  1. Capture avalai-request-id first. Log it before parsing the body so support, reseller cost lookup, and internal traces all point to the same request.
  2. Respect Retry-After on 429. Sleep for that value before retrying; if it is missing, use exponential backoff with jitter and a maximum retry count.
  3. Check every bucket. A request can be blocked by request limits, token limits, or project-scoped token limits, so monitor x-ratelimit-remaining-requests, x-ratelimit-remaining-tokens, and any x-ratelimit-remaining-project-tokens header.
  4. Reduce token pressure. If token headers are the bottleneck, lower max_tokens, shorten prompts, summarize prior turns, or move non-urgent bulk work to a batch-style workflow.
  5. Fail gracefully. Return a clear "try again later" message to users when retries are exhausted and keep the captured avalai-request-id in logs.

Standard HTTP Headers

Content-Type

Indicates the media type of the response body:

Content-Type: application/json

Content-Length

Size of the response body in bytes:

Content-Length: 970

Date

Server timestamp when the response was generated:

Date: Thu, 27 Nov 2025 09:24:15 GMT

Examples

Complete Response Headers Example

Here's a complete example showing common headers from a typical API request. Some upstream-compatible metadata headers are route-dependent and may be absent. During the transition window both request ID headers are present:

bash
# Use -i flag to show headers
curl -i "https://api.avalai.ir/v1/chat/completions" \
  -H "Authorization: Bearer $AVALAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.4-mini",
    "messages": [{"role": "user", "content": "hi"}]
  }'

# Response Headers:
# HTTP/2 200
# date: Thu, 27 Nov 2025 09:24:15 GMT
# content-type: application/json
# content-length: 970
# openai-processing-ms: 842
# openai-version: 2020-10-01
# x-ratelimit-limit-requests: 30000
# x-ratelimit-remaining-requests: 29999
# x-ratelimit-limit-tokens: 150000000
# x-ratelimit-remaining-tokens: 149999982
# x-ratelimit-reset-requests: 45s
# x-ratelimit-reset-tokens: 45s
# x-ratelimit-remaining-project-tokens: 57000
# x-request-id: 01a009d5-ec91-74c2-8ffa-9eba731dfc9e
# avalai-request-id: 01a009d5-ec91-74c2-8ffa-9eba731dfc9e
python
# Python example - accessing response headers
import requests

response = requests.post(
    "https://api.avalai.ir/v1/chat/completions",
    headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
    json={"model": "gpt-5.4-mini", "messages": [{"role": "user", "content": "hi"}]},
)

# Access headers
request_id = response.headers.get("avalai-request-id")
remaining_requests = response.headers.get("x-ratelimit-remaining-requests")
remaining_tokens = response.headers.get("x-ratelimit-remaining-tokens")
reset_time = response.headers.get("x-ratelimit-reset-requests")

print(f"Request ID: {request_id}")
print(f"Remaining Requests: {remaining_requests}")
print(f"Remaining Tokens: {remaining_tokens}")
print(f"Reset Time: {reset_time}")
javascript
// JavaScript example - accessing response headers
const response = await fetch("https://api.avalai.ir/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.AVALAI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "gpt-5.4-mini",
    messages: [{ role: "user", content: "hi" }],
  }),
});

// Access headers
const requestId = response.headers.get("avalai-request-id");
const remainingRequests = response.headers.get("x-ratelimit-remaining-requests");
const remainingTokens = response.headers.get("x-ratelimit-remaining-tokens");
const resetTime = response.headers.get("x-ratelimit-reset-requests");

console.log(`Request ID: ${requestId}`);
console.log(`Remaining Requests: ${remainingRequests}`);
console.log(`Remaining Tokens: ${remainingTokens}`);
console.log(`Reset Time: ${resetTime}`);
go
// Go example - accessing response headers
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	body, _ := json.Marshal(map[string]interface{}{
		"model":    "gpt-5.4-mini",
		"messages": []map[string]string{{"role": "user", "content": "hi"}},
	})

	req, _ := http.NewRequest("POST", "https://api.avalai.ir/v1/chat/completions", bytes.NewBuffer(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AVALAI_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, _ := (&http.Client{}).Do(req)
	defer resp.Body.Close()

	// Access headers
	requestID := resp.Header.Get("avalai-request-id")
	remainingRequests := resp.Header.Get("x-ratelimit-remaining-requests")
	remainingTokens := resp.Header.Get("x-ratelimit-remaining-tokens")
	resetTime := resp.Header.Get("x-ratelimit-reset-requests")

	fmt.Printf("Request ID: %s\n", requestID)
	fmt.Printf("Remaining Requests: %s\n", remainingRequests)
	fmt.Printf("Remaining Tokens: %s\n", remainingTokens)
	fmt.Printf("Reset Time: %s\n", resetTime)
}
php
<?php
// PHP example - accessing response headers
$ch = curl_init('https://api.avalai.ir/v1/chat/completions');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . getenv('AVALAI_API_KEY'),
    'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'model' => 'gpt-5.4-mini',
    'messages' => [['role' => 'user', 'content' => 'hi']]
]));

$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
curl_close($ch);

// Parse headers
preg_match('/avalai-request-id:\s*([^\r\n]+)/i', $headers, $requestId);
preg_match('/x-ratelimit-remaining-requests:\s*([^\r\n]+)/i', $headers, $remainingRequests);
preg_match('/x-ratelimit-remaining-tokens:\s*([^\r\n]+)/i', $headers, $remainingTokens);
preg_match('/x-ratelimit-reset-requests:\s*([^\r\n]+)/i', $headers, $resetTime);

echo "Request ID: " . trim($requestId[1] ?? '') . "\n";
echo "Remaining Requests: " . trim($remainingRequests[1] ?? '') . "\n";
echo "Remaining Tokens: " . trim($remainingTokens[1] ?? '') . "\n";
echo "Reset Time: " . trim($resetTime[1] ?? '') . "\n";
?>
Responses API version

Use this version when the selected model supports /v1/responses. messages moves to input, and the final text is read from response.output_text.

python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["AVALAI_API_KEY"],
    base_url="https://api.avalai.ir/v1",
)

response = client.responses.create(
    model="gpt-5.4-mini",
    instructions="You are a helpful assistant.",
    input="Write a one-sentence summary of AvalAI.",
)

print(response.output_text)
javascript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.AVALAI_API_KEY,
  baseURL: "https://api.avalai.ir/v1",
});

const response = await client.responses.create({
  model: "gpt-5.4-mini",
  instructions: "You are a helpful assistant.",
  input: "Write a one-sentence summary of AvalAI.",
});

console.log(response.output_text);
bash
curl https://api.avalai.ir/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AVALAI_API_KEY" \
  -d '
  {
    "model": "gpt-5.4-mini",
    "input": "Write a one-sentence summary of AvalAI.",
    "instructions": "You are a helpful assistant."
  }'
  • messagesinput
  • system message → instructions or a developer item
  • choices[0].message.contentresponse.output_text
  • for tools and multimodal output, inspect response.output by item type.

Monitoring Rate Limits

Example of monitoring your rate limits in real-time:

python
# Python - Rate limit monitoring
import requests
import time


def make_api_call():
    response = requests.post(
        "https://api.avalai.ir/v1/chat/completions",
        headers={"Authorization": f"Bearer {api_key}"},
        json={"model": "gpt-5.4-mini", "messages": [{"role": "user", "content": "hi"}]},
    )

    # Monitor rate limits
    remaining = int(response.headers.get("x-ratelimit-remaining-requests", 0))
    limit = int(response.headers.get("x-ratelimit-limit-requests", 0))
    reset = response.headers.get("x-ratelimit-reset-requests", "")

    usage_percent = ((limit - remaining) / limit * 100) if limit > 0 else 0

    print(f"Rate Limit Usage: {usage_percent:.2f}%")
    print(f"Remaining: {remaining}/{limit}")
    print(f"Resets in: {reset}")

    # Warn if approaching limit
    if usage_percent > 90:
        print("⚠️  WARNING: Approaching rate limit!")
        time.sleep(5)  # Back off

    return response


# Use in your application
for i in range(100):
    response = make_api_call()
    # Process response...
javascript
// JavaScript - Rate limit monitoring with backoff
async function makeApiCallWithRateLimit() {
  const response = await fetch("https://api.avalai.ir/v1/chat/completions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AVALAI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-5.4-mini",
      messages: [{ role: "user", content: "hi" }],
    }),
  });

  // Monitor rate limits
  const remaining = parseInt(response.headers.get("x-ratelimit-remaining-requests") || "0");
  const limit = parseInt(response.headers.get("x-ratelimit-limit-requests") || "0");
  const reset = response.headers.get("x-ratelimit-reset-requests") || "";

  const usagePercent = limit > 0 ? ((limit - remaining) / limit) * 100 : 0;

  console.log(`Rate Limit Usage: ${usagePercent.toFixed(2)}%`);
  console.log(`Remaining: ${remaining}/${limit}`);
  console.log(`Resets in: ${reset}`);

  // Handle 429 responses
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get("retry-after") || "60");
    console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
    await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
    return makeApiCallWithRateLimit(); // Retry
  }

  // Warn if approaching limit
  if (usagePercent > 90) {
    console.log("⚠️  WARNING: Approaching rate limit!");
    await new Promise((resolve) => setTimeout(resolve, 5000)); // Back off
  }

  return response;
}
go
// Go - Rate limit monitoring
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"
	"time"
)

func makeAPICallWithRateLimit() (*http.Response, error) {
	body, _ := json.Marshal(map[string]interface{}{
		"model":    "gpt-5.4-mini",
		"messages": []map[string]string{{"role": "user", "content": "hi"}},
	})

	req, _ := http.NewRequest("POST", "https://api.avalai.ir/v1/chat/completions", bytes.NewBuffer(body))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AVALAI_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return nil, err
	}

	// Monitor rate limits
	remaining, _ := strconv.Atoi(resp.Header.Get("x-ratelimit-remaining-requests"))
	limit, _ := strconv.Atoi(resp.Header.Get("x-ratelimit-limit-requests"))
	reset := resp.Header.Get("x-ratelimit-reset-requests")

	usagePercent := float64(0)
	if limit > 0 {
		usagePercent = float64(limit-remaining) / float64(limit) * 100
	}

	fmt.Printf("Rate Limit Usage: %.2f%%\n", usagePercent)
	fmt.Printf("Remaining: %d/%d\n", remaining, limit)
	fmt.Printf("Resets in: %s\n", reset)

	// Handle 429 responses
	if resp.StatusCode == 429 {
		retryAfter, _ := strconv.Atoi(resp.Header.Get("retry-after"))
		if retryAfter == 0 {
			retryAfter = 60
		}
		fmt.Printf("Rate limited. Retrying after %d seconds...\n", retryAfter)
		time.Sleep(time.Duration(retryAfter) * time.Second)
		resp.Body.Close()
		return makeAPICallWithRateLimit() // Retry
	}

	// Warn if approaching limit
	if usagePercent > 90 {
		fmt.Println("⚠️  WARNING: Approaching rate limit!")
		time.Sleep(5 * time.Second) // Back off
	}

	return resp, nil
}
php
<?php
// PHP - Rate limit monitoring
function makeApiCallWithRateLimit() {
    $ch = curl_init('https://api.avalai.ir/v1/chat/completions');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . getenv('AVALAI_API_KEY'),
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'model' => 'gpt-5.4-mini',
        'messages' => [['role' => 'user', 'content' => 'hi']]
    ]));

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $headers = substr($response, 0, $headerSize);
    $body = substr($response, $headerSize);
    curl_close($ch);

    // Parse headers
    preg_match('/x-ratelimit-remaining-requests:\s*(\d+)/i', $headers, $remaining);
    preg_match('/x-ratelimit-limit-requests:\s*(\d+)/i', $headers, $limit);
    preg_match('/x-ratelimit-reset-requests:\s*([^\r\n]+)/i', $headers, $reset);

    $remaining = isset($remaining[1]) ? (int)$remaining[1] : 0;
    $limit = isset($limit[1]) ? (int)$limit[1] : 0;
    $reset = isset($reset[1]) ? trim($reset[1]) : '';

    $usagePercent = $limit > 0 ? (($limit - $remaining) / $limit * 100) : 0;

    echo "Rate Limit Usage: " . number_format($usagePercent, 2) . "%\n";
    echo "Remaining: $remaining/$limit\n";
    echo "Resets in: $reset\n";

    // Handle 429 responses
    if ($httpCode == 429) {
        preg_match('/retry-after:\s*(\d+)/i', $headers, $retryAfter);
        $retryAfter = isset($retryAfter[1]) ? (int)$retryAfter[1] : 60;
        echo "Rate limited. Retrying after $retryAfter seconds...\n";
        sleep($retryAfter);
        return makeApiCallWithRateLimit(); // Retry
    }

    // Warn if approaching limit
    if ($usagePercent > 90) {
        echo "⚠️  WARNING: Approaching rate limit!\n";
        sleep(5); // Back off
    }

    return json_decode($body, true);
}
?>
Responses API version

Use this version when the selected model supports /v1/responses. messages moves to input, and the final text is read from response.output_text.

python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["AVALAI_API_KEY"],
    base_url="https://api.avalai.ir/v1",
)

response = client.responses.create(
    model="gpt-5.4-mini",
    instructions="You are a helpful assistant.",
    input="Write a one-sentence summary of AvalAI.",
)

print(response.output_text)
javascript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.AVALAI_API_KEY,
  baseURL: "https://api.avalai.ir/v1",
});

const response = await client.responses.create({
  model: "gpt-5.4-mini",
  instructions: "You are a helpful assistant.",
  input: "Write a one-sentence summary of AvalAI.",
});

console.log(response.output_text);
bash
curl https://api.avalai.ir/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AVALAI_API_KEY" \
  -d '
  {
    "model": "gpt-5.4-mini",
    "input": "Write a one-sentence summary of AvalAI.",
    "instructions": "You are a helpful assistant."
  }'
  • messagesinput
  • system message → instructions or a developer item
  • choices[0].message.contentresponse.output_text
  • for tools and multimodal output, inspect response.output by item type.

Best Practices

1. Always Capture avalai-request-id

Store the avalai-request-id from every API call for:

  • Cost tracking and billing (especially for resellers)
  • Debugging and support requests
  • Audit trails and compliance

During the transition window (until 2026-10-15) you may fall back to x-request-id when the new header is missing, but remove that fallback before the window closes. If you also send X-Client-Request-Id, log both IDs together so timeout reports and successful responses can be correlated.

python
# Good practice
request_id = response.headers.get("avalai-request-id")
db.store_request_log(user_id=user.id, request_id=request_id, timestamp=now())

2. Monitor Rate Limits Proactively

Don't wait for 429 errors. Monitor your rate limit headers and implement backoff strategies:

python
remaining = int(response.headers.get("x-ratelimit-remaining-requests", 0))
if remaining < 100:  # Less than 100 requests remaining
    time.sleep(1)  # Back off

3. Handle 429 Responses Gracefully

Implement exponential backoff when you receive rate limit errors:

python
import time


def make_request_with_retry(max_retries=3):
    for attempt in range(max_retries):
        response = requests.post(...)

        if response.status_code == 429:
            retry_after = int(response.headers.get("retry-after", 60))
            time.sleep(retry_after)
            continue

        return response

    raise Exception("Max retries exceeded")

4. Use Request IDs for Cost Lookups

For precise cost tracking, wait up to 30 seconds after the request, then query the User API:

python
# Step 1: Make API call and capture avalai-request-id
response = requests.post(...)
request_id = response.headers.get("avalai-request-id")

# Step 2: Wait for processing
time.sleep(5)  # Usually available much sooner

# Step 3: Get exact cost
cost_data = requests.post(
    "https://api.avalai.ir/user/v1/transactions/lookup",
    json={"transaction_ids": [request_id]},
)

See Reseller Cost Tracking Guide for complete workflow.

5. Log Headers for Debugging

When reporting issues to support, include relevant headers:

python
import logging

logging.info(f"Request ID: {response.headers.get('avalai-request-id')}")
logging.info(f"Status Code: {response.status_code}")
logging.info(f"Rate Limit: {response.headers.get('x-ratelimit-remaining-requests')}")