Skyline

Reference

HTTP API

Every endpoint Skyline adds to the Horizon API, with parameters and responses.

The Skyline dashboard is a single-page app talking to a JSON API. Every operation it performs is available to you — for a deploy script, an incident runbook, or a bot that drains a queue when a downstream service goes down.

This page documents the endpoints Skyline adds, and the extra parameters it adds to inherited ones. The upstream Horizon endpoints (/stats, /workload, /masters, /monitoring, /metrics/*, /batches/*) are unchanged.

Base path and authentication

Endpoints are mounted under the dashboard path, which is horizon.path in your config and defaults to horizon. So the full base URL is /horizon/api.

There is no separate API authentication. Every endpoint sits behind the same horizon.middleware (['web'] by default) and the same viewHorizon gate as the dashboard itself. In practice that means a session cookie: whoever can view the dashboard can call the API, and nobody else can. Calls that change state are ordinary POST and DELETE requests through the web middleware group, so they need a CSRF token.

The curl snippets below show the shape of each request. They are not copy-pasteable as-is: without a session cookie you get a redirect, and without a CSRF token a state-changing call gets a 419.

These endpoints are destructive

DELETE /api/queues/… discards every job in a queue. If you expose the dashboard beyond your operators, review your viewHorizon gate before you rely on the UI's confirmation modals as the safety net — the API has no modal.

Skyline endpoints

Method Path Purpose
GET/api/trendsWorkload, wait and failure time-series.
POST/api/queues/{connection}/{queue}/pausePause one queue.
DELETE/api/queues/{connection}/{queue}/pauseResume one queue.
GET/api/jobs/queues/{queue}Jobs waiting inside a queue.
GET/api/jobs/delayedScheduled and retrying jobs.
GET/api/jobs/reservedJobs currently executing.
POST/api/jobs/perform/{id}Run a delayed job immediately.
DELETE/api/jobs/{id}Delete a pending or delayed job.
DELETE/api/queues/{connection}/{queue}Empty a queue.

Job list responses

Every job-listing endpoint returns the same envelope, with the job payload already decoded:

{
  "jobs": [ { "id": "8813", "name": "App\\Jobs\\ProcessPodcast", "queue": "default", "payload": { }, "status": "pending" } ],
  "total": 412
}

They paginate with a cursor, not a page number. Pass the index after which to read as starting_at; it defaults to -1, meaning the beginning.

total may be null

When a listing is scoped with ?queue=, total comes back as null. Counting the matches for one queue means scanning the whole state, so Skyline skips it and paginates by cursor alone. Treat null as "unknown", not zero.

Returns the time-series behind the dashboard's trend charts — workload, wait and failures, bucketed and retained according to your trends config. Takes no parameters.

POST / DELETE /api/queues/{connection}/{queue}/pause

Pause and resume a single queue. Both return 204 No Content on success.

curl -X POST   https://example.com/horizon/api/queues/redis/exports/pause
curl -X DELETE https://example.com/horizon/api/queues/redis/exports/pause

Both respond 409 Conflict with {"message": "Queue pausing is not supported in this environment."} when the Laravel version or the cache store cannot support per-queue pausing. See Pausing & resuming queues.

GET /api/jobs/queues/{queue}

The jobs waiting inside a single queue, in order.

ParameterDefaultMeaning
starting_at-1Cursor index to read after.
nameCase-insensitive substring match on the job class name.

GET /api/jobs/delayed

Delayed jobs: those scheduled for the future and those waiting out a retry backoff.

ParameterDefaultMeaning
starting_at-1Cursor index to read after.
nameCase-insensitive substring match on the job class name.
filterscheduled (never attempted) or retry (released after a failure). Any other value is ignored and returns both.
queueRestrict to one queue. Forces total to null.

Each job carries an absolute available_at timestamp, so you can show a real next-run time rather than a backoff duration.

GET /api/jobs/reserved

Jobs a worker has reserved and is executing right now — what the dashboard's In Progress tab shows.

ParameterDefaultMeaning
starting_at-1Cursor index to read after.
nameCase-insensitive substring match on the job class name.
queueRestrict to one queue. Forces total to null.

POST /api/jobs/perform/{id}

Runs a delayed or retrying job immediately instead of waiting for its available-at time. This is the Perform Now button. The request returns as soon as the trigger is queued; the job itself runs on a worker.

curl -X POST https://example.com/horizon/api/jobs/perform/8813

The job id is not validated at the HTTP layer — an unknown id is accepted here and resolved when the trigger runs.

DELETE /api/jobs/{id}

Removes a single job from its queue. Returns 204 No Content on success.

StatusWhen
204The job was removed and any ShouldBeUnique lock released.
404No such job.
422The job is not pending or delayed — a reserved, completed or failed job cannot be deleted.
422The job's queue connection is unknown, or is not a Redis connection.
409A worker reserved the job between the lookup and the delete, so it is no longer removable.

DELETE /api/queues/{connection}/{queue}

Empties a queue: discards every pending and delayed job in it. Returns 200 with the number removed.

curl -X DELETE https://example.com/horizon/api/queues/redis/exports

{"deleted": 1284}

Responds 422 when the connection is unknown, or when its driver does not support clearing. The unique lock of every removed job is released — see Unique job locks.

Extended Horizon endpoints

These endpoints exist in Horizon; Skyline adds parameters to them. All accept starting_at as upstream does.

EndpointAdded parameters
GET /api/jobs/pendingname, queue
GET /api/jobs/completedname, queue
GET /api/jobs/silencedname
GET /api/jobs/failedname

On /api/jobs/failed, the upstream tag parameter still works and takes precedence over name; the two are mutually exclusive.

From PHP

If you are scripting against Skyline in-process rather than over HTTP, the same data is on the job repository, which you can resolve from the container:

use Laravel\Horizon\Contracts\JobRepository;

$jobs = app(JobRepository::class);

$jobs->getReserved();                              // executing now
$jobs->getDelayed(null, null, 'retry');            // backing off after a failure
$jobs->getPendingForQueue('exports');              // waiting in one queue
$jobs->countPending('ProcessPodcast');             // by class-name substring

And to prepend a raw payload onto a queue, bypassing the job class entirely:

Queue::connection('redis')->pushRawOnFront($payload, 'media');
Version note

GET /api/jobs/reserved, the filter parameter on /api/jobs/delayed, and the queue parameter on the job listings arrived with the job-state tabs. If your installed Skyline version predates them, the reserved endpoint 404s and the unknown parameters are ignored. Upgrade with composer update boring-o11y/laravel-skyline.

Queue control, not just queue monitoring.

Skyline is a drop-in replacement for Laravel Horizon that lets you act on what you see — pause a queue, jump a job to the front, drain a backlog.

Sign up for early access