Axon Docs
GitHub Quickstart

Docs / API Reference

REST API

The JSON API Axon.Server maps under /axon when you call .AddAxonApiEndpoints() or .AddAxonDashboard() (which includes it). The dashboard UI is a client of this exact API — nothing here is dashboard-only.

If .AddAuthentication(...) is configured, every endpoint below requires a signed-in session (the axon_dashboard_auth cookie) except POST /axon/login and GET /axon/me. Endpoints marked Admin additionally require the Admin role — a ReadOnly user gets 403 Forbidden. With no authentication configured, every endpoint is open to anyone who can reach the server.

Jobs

GET
/axon/jobs?skip=0&take=20&state=Processing

Lists jobs, newest first. All query parameters are optional.

  • skip int — default 0.
  • take int — default 20 (a 0 or omitted value also resolves to 20, not unlimited).
  • state JobState? — filters to a single state, e.g. Enqueued, Processing, Failed. Omit for all states.

Returns 200 with an array of Job objects.

GET
/axon/jobs/{jobId}

Returns 200 with a single Job, or 404 if it doesn't exist.

GET
/axon/jobs/{jobId}/history

Returns 200 with an array of state transitions for the job, oldest first:

JobHistoryEntry[]
[
  { "jobId": "a1b2...", "state": "Enqueued", "timestamp": 638..., "note": null },
  { "jobId": "a1b2...", "state": "Processing", "timestamp": 638..., "note": null },
  { "jobId": "a1b2...", "state": "Failed", "timestamp": 638..., "note": "System.Exception: ..." }
]

timestamp is UTC ticks (DateTime.Ticks), not Unix milliseconds. note carries the exception text on a Failed entry, otherwise null.

POSTAdmin
/axon/jobs/{jobId}/retry

Requeues a job for immediate redispatch, regardless of its current state or remaining attempts. Returns 204, or 404 if the job doesn't exist.

DELETEAdmin
/axon/jobs/{jobId}

Deletes a job permanently, regardless of its current state. Returns 204 whether or not the job existed — this endpoint doesn't check first.

Recurring jobs

GET
/axon/recurring-jobs?skip=0&take=20

Lists recurring jobs. Same skip/take defaults as /axon/jobs. Returns 200 with an array of RecurringJob objects.

POSTAdmin
/axon/recurring-jobs/{recurringJobId}/trigger

Enqueues a one-off job right now, using the recurring job's stored method/arguments/device, without touching its schedule. Returns 200 with the new job's id, or 404 if the recurring job doesn't exist:

200 OK
{ "jobId": "c4d5..." }
POSTAdmin
/axon/recurring-jobs/{recurringJobId}/pause

Sets IsPaused = true. The poll loop skips a paused recurring job entirely — NextRunAt is left untouched, so resuming picks up the existing schedule where it would have been rather than recomputing it. Returns 204, or 404 if it doesn't exist.

POSTAdmin
/axon/recurring-jobs/{recurringJobId}/resume

Sets IsPaused = false. Returns 204, or 404 if it doesn't exist.

POSTAdmin
/axon/recurring-jobs/{recurringJobId}/skip-next

Advances NextRunAt to the occurrence after the next one, without running the skipped occurrence. Returns 204, or 404 if it doesn't exist.

DELETEAdmin
/axon/recurring-jobs/{recurringJobId}

Deletes a recurring job's schedule (does not affect jobs it already triggered). Returns 204 whether or not it existed.

Servers and clients

GET
/axon/servers

Lists every known Axon.Server instance, most recently seen first:

200 OK
[
  {
    "instanceId": "...",
    "machineName": "api-7b9c",
    "startedAt": 638...,
    "lastSeenAt": 638...,
    "servedQueues": "default,billing",
    "isOnline": true
  }
]

isOnline is computed at request time from how long ago lastSeenAt was — instances heartbeat periodically, and one that's stopped is not removed, just eventually shown offline.

GET
/axon/clients

Lists every connected device:

200 OK
[
  {
    "deviceName": "WORKER-01_3f2a...",
    "connectionId": "...",
    "connectedAt": 638...,
    "isProcessing": false
  }
]

deviceName defaults to {Environment.MachineName}_{Guid} (see Axon.Client's connection setup) unless the client overrides it. isProcessing reflects whether this device currently has a job in the Processing state — a snapshot at request time, not push-updated.

Session

Only mapped when .AddAuthentication(...) is configured. Without it, GET /axon/me is still mapped but always returns { "username": null, "role": null }.

POST
/axon/login

Body: { "username": string, "password": string }. On success, sets the axon_dashboard_auth cookie and returns 204. Returns 401 on bad credentials, or 429 if the username is currently locked out (5 failures within 5 minutes) or the calling IP has hit its own rate limit (5 attempts per 5 minutes). Every attempt — success or failure — is logged to Axon.Server.Services.AxonAuditLog.

POST
/axon/logout

Clears the session cookie. Returns 204.

GET
/axon/me

Returns the current session's identity, or nulls if not signed in — useful for a frontend to check auth state without triggering a login redirect:

200 OK
{ "username": "admin", "role": "Admin" }

Health checks

Always mapped, independent of AddAxonApiEndpoints()/AddAxonDashboard() and never behind auth — orchestrator probes typically can't authenticate anyway.

GET
/axon/health/live

Always 200 once the process is up. Never touches the job store.

GET
/axon/health/ready

Reports the job store's health check result — 200 if Axon.Store.* can reach its backing database (or Redis, if AddRedisBackplane is configured), 503 otherwise.

Job shape

Returned by /axon/jobs and /axon/jobs/{jobId}:

Job
{
  "jobId": "a1b2...",
  "deviceName": "WORKER-01_3f2a...",
  "state": "Processing",
  "methodName": "ChargeInvoice",
  "declaringType": "MyApp.BillingJobs",
  "assembly": "MyApp, Version=1.0.0.0, ...",
  "arguments": ["inv_confirm_492"],
  "priority": "Medium",
  "queueName": "billing",
  "attempts": 1,
  "maxAttempts": 3,
  "scheduledFor": null,
  "enqueuedAt": 638...,
  "processingDeadline": 638...,
  "concurrencyKey": null,
  "maxConcurrent": null,
  "retryPolicy": null,
  "parentJobId": null,
  "continueOnParentFailure": false
}

All timestamp fields are UTC ticks. See JobState for the full set of state values.

RecurringJob shape

Returned by /axon/recurring-jobs:

RecurringJob
{
  "recurringJobId": "nightly-report",
  "deviceName": "WORKER-01_3f2a...",
  "cronExpression": "0 2 * * *",
  "methodName": "SendNightlyReport",
  "declaringType": "MyApp.ReportJobs",
  "assembly": "MyApp, Version=1.0.0.0, ...",
  "arguments": [],
  "nextRunAt": 638...,
  "lastRunAt": 638...,
  "isPaused": false
}

Note that a recurring job's stored JobInfo fields don't currently include priority/concurrencyKey/queueName — a job triggered from a schedule always runs at default priority on the default queue, regardless of what was set when the recurring job was created.