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

> Troubleshoot failed requests in FreeLLMAPI. View the full attempt trail with the AttemptTrace system for detailed insights into routing failures.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: how-to-guide
- Published: 2026-08-26

---

**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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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.

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

```

**Sample response:**

```json
{
  "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:

```sql
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:

```typescript
// 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

- **Every attempt is traced**: FreeLLMAPI creates an `AttemptTrace` via `AsyncLocalStorage` at the start of each request and records every provider hop in [`server/src/lib/attempt-trace.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/attempt-trace.ts).
- **Failed attempts persist**: The `persistRequestAttempts()` function in [`server/src/lib/request-log.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/request-log.ts) writes all attempts—including failures—to the `request_attempts` table without blocking the response.
- **Schema supports forensics**: The migration in [`server/src/db/migrations/20260726_000002_request_attempts.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/db/migrations/20260726_000002_request_attempts.ts) stores platform, model, key usage, timing, and error summaries.
- **Multiple access patterns**: Retrieve trails via the `/v1/analytics/requests/:id` endpoint in [`server/src/routes/analytics.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/analytics.ts) or query the SQLite database directly.

## 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`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/fallback-loop.ts). Only after the response completes does `persistRequestAttempts()` flush the data to the SQLite database.