Troubleshooting
This page covers common issues you may encounter when using Spectra and how to resolve them.
No Requests Are Being Tracked
Symptoms: The dashboard shows no data and the spectra_requests table is empty.
Checklist:
Spectra is enabled — Verify the master switch is on:
bashSPECTRA_ENABLED=trueWatcher is enabled — Automatic tracking requires the watcher to be active:
bashSPECTRA_WATCHER_ENABLED=trueProvider is registered — The request hostname must match a configured provider in
spectra.providers. For example, requests toapi.openai.comare recognized automatically, but requests to a custom or self-hosted endpoint require a custom provider to be registered.Endpoint is trackable — The request path must match a trackable endpoint defined in the provider's handlers. For example, OpenAI tracks
/v1/chat/completions,/v1/embeddings, and/v1/images/generations, but does not track/v1/modelsor other non-AI endpoints.Migrations have run — Ensure the
spectra_requeststable exists:shellphp artisan migrateUsing a supported HTTP client — Spectra automatically tracks requests made via Laravel's
Httpfacade (HttpWatcher), theopenai-php/laravelSDK (OpenAiWatcher), and Guzzle with Spectra middleware (GuzzleMiddleware). Directcurlcalls or other HTTP clients are not intercepted automatically — use provider macros or manual tracking for those.
TIP
Use Http::openai() or other provider macros for guaranteed tracking. The macros ensure all metadata is attached correctly and prevent common configuration issues.
Requests Tracked But Cost Is Zero
Symptoms: Requests appear in the dashboard but total_cost_in_cents shows 0.000000.
Checklist:
Pricing data is imported — The pricing catalog must contain entries for the models you are using:
shellphp artisan spectra:pricing --forceModel name matches — The
internal_nameinspectra_modelsmust match exactly what the provider returns in the response. Check the model name stored in your request records against the pricing catalog.Pricing tier exists — If you are using a non-standard tier (such as
batchorflex), pricing entries must exist for that specific tier inspectra_pricing.Pricing unit matches metrics — The
pricing_unitinspectra_modelsmust correspond to the metrics available in the response. For example, a model withpricing_unit = 'tokens'requires non-nullprompt_tokensandcompletion_tokensvalues.Cost tracking is enabled — Verify in the configuration:
php'costs' => [ 'enabled' => true, ],
Dashboard Shows Empty Data
Symptoms: The dashboard loads but displays no requests or statistics.
Checklist:
Dashboard is enabled — Verify the dashboard is not disabled:
bashSPECTRA_DASHBOARD_ENABLED=trueRequest persistence is on — If
store_requestsisfalse, requests are tracked in memory but not persisted to the database:bashSPECTRA_STORE_REQUESTS=trueDatabase connection is correct — If using a separate database, ensure the dashboard queries the same connection configured in
storage.connection.Authorization gate passes — The
viewSpectragate must returntruefor your user. In local development this is automatic. In production, ensure the gate is defined:phpGate::define('viewSpectra', function ($user) { return $user->isAdmin(); });Middleware is correct — If you added
authto dashboard middleware, make sure you are logged in when accessing the dashboard.
Queue Persistence Not Working
Symptoms: Requests are tracked but never appear in the database when using queue mode.
Checklist:
Queue worker is running — Ensure a worker is processing the queue:
shellphp artisan queue:workQueue configuration is correct — Verify the connection and queue name match your worker:
php'queue' => [ 'enabled' => true, 'connection' => null, // or your queue connection 'queue' => null, // or your queue name ],Check failed jobs — The
PersistSpectraRequestJobmay be failing silently:shellphp artisan queue:failedQueue takes priority over after-response — These modes are mutually exclusive. When
queue.enabledistrue, theafter_responsesetting is ignored.
After-Response Mode Not Working
Symptoms: after_response is enabled but requests appear to persist synchronously.
This is expected behavior in two scenarios:
- Non-HTTP context — After-response only works during web requests. Console commands, queue jobs, and scheduled tasks always persist synchronously because there is no HTTP response lifecycle to defer to.
- Queue is enabled — When
queue.enabledistrue, it takes priority andafter_responseis ignored.
Budget Middleware Not Enforcing
Symptoms: Requests proceed even when the user's budget is exceeded.
Checklist:
Budgets are enabled in the configuration:
php'budget' => ['enabled' => true],A budget record exists and is active for the user:
php$user->aiBudgets()->where('is_active', true)->first();Middleware is applied correctly to the route:
phpRoute::middleware(['auth', 'spectra.budget:openai,gpt-4o']) ->post('/ai/chat', ChatController::class);Hard limit is enabled — With
hard_limit = false, requests are allowed to proceed and only events are fired. Sethard_limit = trueon the budget to block requests when exceeded.
OpenTelemetry Export Not Appearing
Symptoms: OTEL is enabled but no traces appear in your observability backend.
Checklist:
OTEL is enabled in the configuration:
bashSPECTRA_OTEL_ENABLED=trueEndpoint is reachable — Test connectivity to your OTLP endpoint:
shellcurl -v http://localhost:4318/v1/tracesAuthentication headers are configured — Most cloud backends require authentication. Verify your headers in the configuration.
Export timing — In after-response mode, traces export after the HTTP response. In console or queue contexts, they export synchronously. If traces appear from CLI commands but not web requests, verify that your server supports terminable middleware.
Timeout is sufficient — If the OTLP endpoint is remote or slow, increase the timeout:
bashSPECTRA_OTEL_TIMEOUT=30
Stats Look Wrong After Data Changes
Symptoms: Dashboard charts or cost totals don't match the raw request data.
Solution: Rebuild the daily statistics aggregation from the raw request records:
php artisan spectra:rebuild-stats
# Or rebuild a specific date range
php artisan spectra:rebuild-stats --from=2026-01-01 --to=2026-01-31Media Files Not Being Saved
Symptoms: Image or video generation requests are tracked but no media files are downloaded to disk.
Checklist:
Media persistence is enabled:
bashSPECTRA_MEDIA_ENABLED=trueDisk is writable — Ensure the configured filesystem disk and path exist and are writable by your application.
API key is available — Some providers require an API key to download media files. Ensure keys are configured in
spectra.api_keys.URLs haven't expired — Provider media URLs have limited lifetimes. If persistence is delayed (for example, via a queue with a long delay), the URLs may expire before download completes.
Macro Not Available
Symptoms: Http::openai() or other provider macros throw a "Method not found" error.
Checklist:
Service provider is registered — Spectra's service provider should be auto-discovered. If not, register it manually:
php// config/app.php 'providers' => [ Spectra\SpectraServiceProvider::class, ],Watcher is enabled — Macros are registered when the watcher boots:
bashSPECTRA_WATCHER_ENABLED=trueCheck for conflicts — Another package may have registered a macro with the same name. Laravel macros are global and the last registration wins.
High Memory Usage
Symptoms: Application memory spikes when making many AI requests in a single process.
Solutions:
Enable queue persistence to move database writes to background workers:
bashSPECTRA_QUEUE_ENABLED=trueDisable payload storage if you don't need request and response bodies:
bashSPECTRA_STORE_REQUESTS=false SPECTRA_STORE_RESPONSES=falseSet truncation limits to cap the size of stored content:
bashSPECTRA_MAX_PROMPT_LENGTH=5000 SPECTRA_MAX_RESPONSE_LENGTH=5000Schedule pruning to prevent unbounded table growth:
shellphp artisan spectra:prune --hours=168
Duplicate Requests Being Tracked
Symptoms: The same AI request appears multiple times in the dashboard.
Causes:
Multiple watchers matching — If both HttpWatcher and GuzzleWatcher intercept the same request, it may be tracked twice. The
response_idunique constraint prevents true duplicates, but different watchers may generate different response identifiers.Retry middleware — If your HTTP client retries failed requests, each attempt is tracked as a separate request. This is intentional, as each retry attempt consumes tokens and incurs costs.
Solution: Use only one tracking mechanism per request path. If you use the OpenAI SDK with GuzzleWatcher, consider disabling HttpWatcher for those hosts, or rely exclusively on one watcher type.