API Reference
API Reference
Queue Monitor exposes a REST API for integrating with external dashboards or
monitoring tools. This page documents every endpoint the package registers in
routes/api.php.
Base URL
By default, the API is served under:
http://your-app.test/api/queue-monitor
The prefix is configurable via api.prefix in config/queue-monitor.php. All
paths below are relative to this prefix.
Enabling the API
The API is registered only when api.enabled is true. It defaults to on in the
local environment and off everywhere else. Enable it explicitly in other
environments with QUEUE_MONITOR_API_ENABLED=true — but only after adding
authentication (see below).
Authentication and authorization
The API exposes sensitive data (job payloads, stack traces). Secure it before enabling it outside local development. Two independent layers apply:
- Framework middleware — add auth middleware to
api.middleware, e.g.['api', 'auth:sanctum']. A request that fails it returns401. - Authorization gate — every request passes through the
EnsureQueueMonitorEnabledmiddleware, which calls yourLaravelQueueMonitor::auth(...)callback. If the callback denies the request, the API returns403. Outsidelocal, access is denied until you register a callback.
See Authentication & Access Control for setup.
Rate limiting
Every route is wrapped in throttle:60,1 (60 requests per minute per client) by
default. The limit comes from api.rate_limit. Exceeding it returns 429 Too Many Requests with a Retry-After header.
Payload redaction
Job payloads and tags returned by the API are redacted before serialization.
Keys listed in api.sensitive_keys (default: password, token, secret,
key, authorization, api_key, credit_card, cvv, ssn, private_key,
command) are masked. Set api.sensitive_keys to an empty array to disable
redaction. Raw payloads remain stored unredacted for replay — only responses are
masked.
Pagination and limits
List endpoints accept a limit parameter. The default is 50
(api.default_limit). Any requested limit is clamped to api.max_limit
(default 1000); a larger value is silently reduced to the maximum rather than
rejected.
Jobs
List jobs
Retrieve a filtered, paginated list of jobs.
Endpoint: GET /jobs
Query parameters:
| Parameter | Type | Description |
|---|---|---|
limit |
int | Records per page (default: 50, max: 1000) |
offset |
int | Records to skip (default: 0) |
statuses[] |
array | Filter by status: queued, processing, completed, failed, timeout, cancelled |
queues[] |
array | Filter by queue name |
connection |
string | Filter by queue connection |
job_classes[] |
array | Filter by job class (FQCN) |
server_names[] |
array | Filter by server name |
worker_id |
string | Filter by worker id |
worker_type |
string | Filter by worker type: queue_work or horizon |
tags[] |
array | Filter by tag |
queued_after / queued_before |
date | Filter by queued timestamp |
started_after / started_before |
date | Filter by started timestamp |
completed_after / completed_before |
date | Filter by completed timestamp |
min_duration_ms / max_duration_ms |
int | Filter by duration |
min_attempts |
int | Minimum attempt count |
search |
string | Search UUID, job class, or exception message (max 255) |
sort_by |
string | One of queued_at, started_at, completed_at, duration_ms, created_at (default: queued_at) |
sort_direction |
string | asc or desc (default: desc) |
Response:
{
"data": [
{
"id": 1,
"uuid": "e0e4a3f2-...",
"job_id": "97",
"job_class": "App\\Jobs\\ProcessPayment",
"display_name": "App\\Jobs\\ProcessPayment",
"connection": "redis",
"queue": "payments",
"payload": { "amount": 500, "credit_card": "[REDACTED]" },
"status": { "value": "failed", "label": "Failed", "color": "danger" },
"attempt": 2,
"max_attempts": 3,
"retried_from_id": null,
"server_name": "web-01",
"worker_id": "worker-1",
"worker_type": { "value": "horizon", "label": "Horizon" },
"metrics": {
"cpu_time_ms": 240,
"memory_peak_mb": 12.5,
"file_descriptors": null,
"duration_ms": 1500,
"duration_seconds": 1.5
},
"exception": {
"class": "RuntimeException",
"short_class": "RuntimeException",
"message": "Payment gateway timeout"
},
"tags": ["payments", "high-priority"],
"timestamps": {
"queued_at": "2026-08-26T10:00:00+00:00",
"started_at": "2026-08-26T10:00:01+00:00",
"completed_at": "2026-08-26T10:00:02+00:00",
"created_at": "2026-08-26T10:00:00+00:00",
"updated_at": "2026-08-26T10:00:02+00:00"
},
"flags": {
"is_finished": true,
"is_successful": false,
"is_failed": true,
"is_retryable": true,
"is_retry": false
}
}
],
"meta": {
"total": 128,
"limit": 50,
"offset": 0,
"has_filters": true
}
}
List failed jobs
Endpoint: GET /jobs/failed
Query parameters: limit (default: 100, clamped to api.max_limit).
Response: a job collection ({ "data": [ ... ], "meta": { "total": N } })
of failed jobs, each shaped like the job resource above.
List recent jobs
Endpoint: GET /jobs/recent
Query parameters: limit (default: 100, clamped to api.max_limit).
Response: a job collection of the most recently queued jobs.
Get job details
Endpoint: GET /jobs/{uuid}
Returns the full job resource (same shape as an item in the list response) with
the redacted payload and, when failed, the exception summary. Returns 404 if
the UUID is unknown.
{
"data": {
"uuid": "e0e4a3f2-...",
"job_class": "App\\Jobs\\ProcessPayment",
"payload": { "amount": 500, "credit_card": "[REDACTED]" },
"exception": {
"class": "RuntimeException",
"short_class": "RuntimeException",
"message": "Payment gateway timeout"
}
}
}
Delete a job
Endpoint: DELETE /jobs/{uuid}
Permanently removes a single job record. Returns 404 if the UUID is unknown.
{ "message": "Job deleted successfully" }
Get retry chain
Endpoint: GET /jobs/{uuid}/retry-chain
Returns the job and every job in its retry chain as a collection, ordered by attempt.
{ "data": [ /* job resources */ ], "meta": { "total": 3 } }
Replay a job
Endpoint: POST /jobs/{uuid}/replay
Re-dispatches a job from its stored payload. Requires payload storage to have been enabled when the job ran.
Success (200):
{
"message": "Job replayed successfully",
"data": {
"original_uuid": "e0e4a3f2-...",
"new_uuid": "b1c2d3e4-...",
"new_job_id": "412",
"queue": "payments",
"connection": "redis",
"replayed_at": "2026-08-26T10:05:00+00:00"
}
}
Failure (422) — e.g. no stored payload:
{ "message": "Failed to replay job", "error": "No payload stored for this job" }
Statistics
All statistics endpoints wrap their result in a data envelope.
Global statistics
Endpoint: GET /statistics
{
"data": {
"total": 10000,
"completed": 9600,
"failed": 350,
"timeout": 50,
"processing": 12,
"queue_backlog": 38,
"success_rate": 96.48,
"failure_rate": 3.52,
"avg_duration_ms": 842.11,
"max_duration_ms": 58210,
"avg_memory_mb": 24.6,
"max_memory_mb": 187.4
}
}
Aggregates are limited to the window in metrics_window_hours (default 24h).
Per-server statistics
Endpoint: GET /statistics/servers
Query parameters: server (optional) — limit to one server.
{
"data": [
{
"server_name": "web-01",
"total_jobs": 5000,
"completed": 4820,
"failed": 180,
"success_rate": 96.4,
"avg_duration_ms": 780.5
}
]
}
Per-queue statistics
Endpoint: GET /statistics/queues
Query parameters: queue (optional) — limit to one queue.
{
"data": [
{
"queue": "payments",
"connection": "redis",
"total_jobs": 3200,
"completed": 3100,
"failed": 100,
"processing": 4,
"success_rate": 96.88,
"avg_duration_ms": 910.2
}
]
}
Per-job-class statistics
Endpoint: GET /statistics/job-classes
Query parameters: job_class (optional) — limit to one class.
{
"data": [
{
"job_class": "App\\Jobs\\ProcessPayment",
"total_jobs": 3200,
"completed": 3100,
"failed": 100,
"success_rate": 96.88,
"avg_duration_ms": 910.2,
"max_duration_ms": 58210
}
]
}
Queue health
Endpoint: GET /statistics/queue-health
Per-queue health scored over the last hour.
{
"data": [
{
"queue": "payments",
"total_last_hour": 420,
"processing": 4,
"failed": 6,
"avg_duration_ms": 910.2,
"health_score": 98.57,
"status": "healthy"
}
]
}
status is healthy (score ≥ 95), degraded (≥ 75), or unhealthy.
Failure patterns
Endpoint: GET /statistics/failure-patterns
The top exception classes (up to 10) and how many job classes each affects.
{
"data": {
"top_exceptions": [
{
"exception_class": "RuntimeException",
"count": 240,
"affected_job_classes": 6
}
]
}
}
Tag analytics
Endpoint: GET /statistics/tags
{
"data": [
{
"tag": "high-priority",
"count": 1800,
"successful_count": 1750,
"success_rate": 97.22
}
]
}
Maintenance
Prune jobs
Endpoint: POST /prune
Deletes old job records. With no body, the configured retention settings apply.
Body parameters:
| Parameter | Type | Description |
|---|---|---|
days |
int | Retain records newer than this many days (min 1) |
max_rows |
int | Keep at most this many rows, deleting oldest first (min 1) |
statuses[] |
array | Only prune these statuses (validated against the status enum) |
{ "message": "Jobs pruned successfully", "deleted": 1240 }
Batch operations
Both batch endpoints accept either an explicit uuids[] list or a filters
object. filters uses the same keys as the job filter
(statuses, queues, job_classes, server_names, search, queued_after,
queued_before, min_attempts, min_duration_ms). Unknown filter values are
rejected with 422 rather than silently dropped, so a batch never widens to
match every job. max_jobs is capped at batch.max_jobs (default 1000).
Batch replay
Endpoint: POST /batch/replay
{
"message": "Batch replay completed",
"success": 42,
"failed": 3,
"errors": ["b1c2...: No payload stored"]
}
Batch delete
Endpoint: POST /batch/delete
{ "message": "Batch delete completed", "deleted": 45, "failed": 0 }
Stuck jobs
A stuck job is one processing longer than health.stuck_job_minutes
(default 30).
Resolve specific stuck jobs
Endpoint: POST /stuck-jobs/resolve
Body parameters:
| Parameter | Type | Description |
|---|---|---|
action |
string | Required: delete or retry |
uuids[] |
array | Required, at least one UUID |
{ "message": "3 stuck job(s) resolved, 3 retried", "resolved": 3, "replayed": 3, "errors": [] }
Resolve all stuck jobs
Endpoint: POST /stuck-jobs/resolve-all
Body parameters: action — delete or retry. Operates on every currently
stuck job (up to batch.max_jobs).
{ "message": "No stuck jobs found", "resolved": 0, "replayed": 0, "errors": [] }
Health and monitoring
System health
Endpoint: GET /health
Runs the database, recent-activity, stuck-job, error-rate, queue-backlog, and
storage checks. Returns HTTP 200 when the overall status is healthy, and
503 when it is degraded.
{
"status": "healthy",
"checks": {
"database": { "healthy": true, "message": "Database reachable" },
"stuck_jobs": {
"healthy": true,
"message": "No stuck jobs detected",
"details": { "stuck_count": 0, "threshold_minutes": 30, "stuck_jobs": [] }
}
},
"timestamp": "2026-08-26T10:00:00+00:00"
}
Health score
Endpoint: GET /health/score
{ "score": 92, "status": "healthy" }
status is healthy (score ≥ 80), degraded (≥ 60), or unhealthy.
Active alerts
Endpoint: GET /health/alerts
{
"alerts": {
"stuck_jobs": { "severity": "warning", "message": "5 jobs stuck in processing for > 30 minutes", "count": 5 }
},
"has_critical": false,
"count": 1
}
Export
Export endpoints are bounded by export.default_limit (default 1000) and
export.max_rows (default 5000). The CSV/JSON exports accept the same filter
parameters as GET /jobs.
CSV export
Endpoint: GET /export/csv
Returns text/csv as a file download
(Content-Disposition: attachment; filename="queue-monitor-jobs-YYYY-MM-DD.csv").
JSON export
Endpoint: GET /export/json
{
"data": [ /* flattened job rows */ ],
"meta": { "count": 1000, "exported_at": "2026-08-26T10:00:00+00:00" }
}
Statistics report
Endpoint: GET /export/statistics
{
"generated_at": "2026-08-26T10:00:00+00:00",
"global": { /* global statistics */ },
"servers": [ /* per-server statistics */ ],
"queue_health": [ /* per-queue health */ ]
}
Failed-jobs report
Endpoint: GET /export/failed-jobs
{
"generated_at": "2026-08-26T10:00:00+00:00",
"total_failed": 350,
"by_exception": { "RuntimeException": { "count": 240, "jobs": ["e0e4..."] } },
"by_queue": { "payments": 100 },
"recent_failures": [
{
"uuid": "e0e4...",
"job_class": "App\\Jobs\\ProcessPayment",
"exception": "RuntimeException",
"failed_at": "2026-08-26T09:59:00+00:00"
}
]
}
Dashboard data endpoints
The built-in web dashboard is backed by its own JSON endpoints under the UI
route prefix (queue-monitor by default), not the API prefix. They are gated by
the same access-control callback but the web middleware group. The
throughput/timeline endpoints accept a minutes parameter restricted to a fixed
whitelist — 15, 60, 360, or 1440. Any other value falls back to 60.
| Endpoint | minutes |
Notes |
|---|---|---|
GET /queue-monitor/metrics |
15 / 60 / 360 / 1440 | Overview: stats, queues, alerts, recent jobs, throughput chart. Recent-jobs count follows ui.per_page. |
GET /queue-monitor/autoscale/timeline?queue={name} |
15 / 60 / 360 / 1440 | Per-queue scaling timeline (job volume, duration, memory, worker decisions). |
Errors
The API uses standard HTTP status codes:
| Code | Meaning |
|---|---|
200 |
Success |
401 |
Unauthenticated — the framework auth middleware you added (e.g. auth:sanctum) rejected the request |
403 |
Forbidden — the LaravelQueueMonitor::auth(...) gate denied the request |
404 |
Job not found |
422 |
Validation error (invalid filter, replay with no payload, etc.) |
429 |
Too many requests — the throttle:60,1 rate limit was exceeded |
503 |
Queue Monitor or its API is disabled, or GET /health reports degraded |