How to View the Full Attempt Trail for Failed Requests in FreeLLMAPI

Yes, FreeLLMAPI captures every routing attempt—including failures—via the AttemptTrace system and persists them to the database for later inspection.

FreeLLMAPI is an open-source LLM routing engine that automatically fails over between multiple providers when rate limits or errors occur. Understanding exactly which providers were tried and why each attempt failed is critical for debugging production issues. This article explains how the platform maintains a full attempt trail for failed requests using the AttemptTrace context system and persistent storage.

How FreeLLMAPI Records Failed Request Attempts

When a request enters the system, the framework creates an AttemptTrace instance stored in AsyncLocalStorage. This context survives across the entire failover ladder, accumulating a record for every provider attempt whether it succeeds or fails.

The AttemptTrace Context System

The AttemptTrace class defined in server/src/lib/attempt-trace.ts serves as the backbone for request tracing. As the fallback loop dispatches each provider call, the system appends an AttemptTraceRecord containing:

  • Platform (e.g., "openrouter", "groq")
  • Model ID and key ordinal/label
  • Outcome status (ok, rate_limited, error, timeout)
  • Timing metrics (start offset and duration in milliseconds)
  • Redacted error summary for diagnostics

Because the trace lives in AsyncLocalStorage, it maintains state across asynchronous boundaries throughout the request lifecycle.

Logging and Linking Request IDs

In server/src/lib/request-log.ts, the logRequest() function writes the terminal request row to the requests table. If an active AttemptTrace exists, the code calls noteRequestRowId() to capture the database ID of the request. This link ensures that subsequent attempt records can be associated with the correct parent request, even when the final outcome is an error.

Persisting the Complete Failover Chain

Once the response finishes (success or failure), the persistRequestAttempts() function writes every AttemptTraceRecord to the request_attempts table. This operation happens off the client-latency path, guaranteeing that every attempt in the failover chain is stored, including those that encountered rate limits, timeouts, or provider errors.

Database Schema for Request Attempts

The attempt trail schema is defined in server/src/db/migrations/20260726_000002_request_attempts.ts. The table structure includes:

  • request_id: Foreign key linking to requests.id
  • ordinal: The zero-based position in the failover sequence
  • platform and model_id: Target provider and model
  • key_ordinal and key_label: Which API key was used
  • outcome: Final status of the specific attempt
  • start_offset_ms and duration_ms: Performance timing
  • error_summary: Sanitized error message (if applicable)

This schema ensures comprehensive auditability for every hop a request makes through the provider ladder.

Retrieving the Full Attempt Trail

Via the Analytics API

The primary method for accessing attempt data is the /v1/analytics/requests/:id endpoint defined in server/src/routes/analytics.ts. This route performs a SQL join between the requests and request_attempts tables, returning a JSON payload that includes the full attempts array.

GET https://api.free-llmapi.com/v1/analytics/requests/42?includeAttempts=true
Accept: application/json
Authorization: Bearer <YOUR_API_KEY>

Sample response:

{
  "id": 42,
  "status": "failed",
  "platform": "openrouter",
  "model_id": "meta-llama/Meta-Llama-3.1-70B-Instruct",
  "attempts": [
    {
      "ordinal": 0,
      "platform": "openrouter",
      "model_id": "meta-llama/Meta-Llama-3.1-70B-Instruct",
      "key_ordinal": 1,
      "key_label": "OpenRouter-Key-A",
      "outcome": "rate_limited",
      "start_offset_ms": 0,
      "duration_ms": 312,
      "error_summary": "Rate limit exceeded"
    },
    {
      "ordinal": 1,
      "platform": "groq",
      "model_id": "mixtral-8x7b-32768",
      "key_ordinal": 2,
      "key_label": "Groq-Key-B",
      "outcome": "ok",
      "start_offset_ms": 312,
      "duration_ms": 874,
      "error_summary": null
    }
  ],
  "error": {
    "message": "Provider error (openrouter): rate limit exceeded",
    "type": "rate_limit_error"
  }
}

The dashboard UI consumes this same endpoint to render the "Fallback detail" panel, visualizing the exact path a request took through the provider ladder.

Direct Database Queries

For internal debugging or custom analytics, query the request_attempts table directly using SQLite:

SELECT
  ra.ordinal,
  ra.platform,
  ra.model_id,
  ra.key_ordinal,
  ra.key_label,
  ra.outcome,
  ra.start_offset_ms,
  ra.duration_ms,
  ra.error_summary
FROM request_attempts ra
WHERE ra.request_id = 42
ORDER BY ra.ordinal;

Building Custom Endpoints

If you require a different view of the attempt data, you can extend the Express router. Create a new route file and query the database using the prepared statement pattern:

// In server/src/routes/custom.ts
import { Router } from 'express';
import { getDb } from '../db/index.js';

const router = Router();

router.get('/request/:id/attempts', async (req, res) => {
  const db = getDb();
  const rows = db.prepare(`
    SELECT * FROM request_attempts
    WHERE request_id = ?
    ORDER BY ordinal
  `).all(req.params.id);
  res.json(rows);
});

export default router;

Register this router in your main server configuration to expose the custom endpoint.

Summary

Frequently Asked Questions

Does FreeLLMAPI log attempts that fail due to rate limits?

Yes. The AttemptTraceRecord captures attempts with outcome values like rate_limited, timeout, or error. These records are persisted to request_attempts exactly like successful attempts, allowing you to see which keys hit limits and when the failover succeeded.

How does the system handle sensitive error details in the attempt trail?

The system stores a redacted error summary rather than full stack traces or raw provider responses. This balances diagnostic utility with security, ensuring that API keys or sensitive user data do not leak into the logs while still indicating the failure mode (e.g., "Rate limit exceeded" or "Invalid model").

Can I query the attempt trail for requests that timed out completely?

Yes. Even if the final request results in a timeout error returned to the client, all prior attempts (and any partial progress timing data) are preserved in the database. Query the request_attempts table filtered by request_id to reconstruct the full timeline.

Where is the attempt data stored before the request finishes?

During request processing, attempt data lives in an AsyncLocalStorage context managed by the AttemptTrace class. This in-memory storage survives across asynchronous provider calls orchestrated by server/src/lib/fallback-loop.ts. Only after the response completes does persistRequestAttempts() flush the data to the SQLite database.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →