> Section: [10. Reference](https://jaque.sh/docs/reference/cli-flags.md)
> Next: api/admin-service
> Index: https://jaque.sh/llms.txt


`CommandService` and `QueryService` are the ConnectRPC API on `-listen`:
one port, three wire formats, a write service and a read service kept
strictly apart. This is the API the dashboard itself calls, so nothing
reachable through it is a second-class integration.

## 1. One port, three protocols

jaque speaks ConnectRPC: plain JSON over HTTP/1.1 (curl-friendly, no
generated client needed), gRPC, and gRPC-Web, all on the same port as the
dashboard and `/metrics` (`-listen`).

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.QueryService/ListStatus \
  -H 'Content-Type: application/json' -d '{}'
```

## 2. `CommandService`: writes

Fifteen RPCs, one per command variant: `ProcessCheckResult`,
`ProcessCheckResults` (batch, all-or-nothing validation, in-order
enqueue), `AcknowledgeProblem`, `RemoveAcknowledgement`,
`ScheduleDowntime`, `DeleteDowntime`, `SendCustomNotification`,
`SetProgramNotifications`, `SetObjectNotifications`,
`SetContactNotifications`, `SetProgramToggle`, `SetObjectToggle`,
`ScheduleCheck`, `AddComment` and `DeleteComment`.
`AcknowledgeProblem`, `RemoveAcknowledgement`, `ScheduleDowntime`,
`SendCustomNotification`, `SetObjectNotifications`, `SetObjectToggle` and
`ScheduleCheck` all take a `Target`, which is either one object or every
object a label selector matches, the
same mechanism the dashboard's bulk actions use. An object target is a
oneof of `host`, `service` or `process`, each its own message
(`HostRef`, `ServiceRef`, `ProcessRef`). The variant picked is the
kind; there is no inference from shape: `{"object":{"host":{"name":"gw"}}}`
addresses a host, `{"object":{"service":{"host":"nas","name":"disk"}}}`
addresses a service, and
`{"object":{"process":{"name":"checkout"}}}` addresses a business
process.

A success response means the command was **enqueued**. The engine
resolves commands asynchronously at its own pace, so a success response
does not mean the command has been applied yet. A submitted check
result must carry a `status` other than `STATUS_PENDING`: an unset
status is refused with `InvalidArgument` rather than recorded as
`UNKNOWN`.

Command requests carry no `realm` field. Addressing an aggregator (a
process started with `-realms-config`) uses the aggregator's own
command service instead: its requests wrap the realm API's requests
together with the realm that owns the target, and the aggregator
forwards the inner request to the realm that owns it, which records
the aggregator's service identity as the author.

`-api-token` (empty by default, meaning open) protects writes only:

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.CommandService/AcknowledgeProblem \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"target":{"object":{"host":{"name":"gw"}}},"sticky":true,"notify":true,"author":"ops","comment":"known issue"}'
```

`notify` sends an `ACKNOWLEDGEMENT` notification to the object's current
escalation level; the dashboard always sets it.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.CommandService/ScheduleDowntime \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"target":{"object":{"host":{"name":"gw"}}},"start":"2026-09-20T02:00:00Z","end":"2026-09-20T04:00:00Z","author":"ops","comment":"maintenance","flexible":{"duration":"1800s"}}'
```

`ScheduleDowntime`'s request carries a `kind` oneof: unset schedules a
fixed downtime, `flexible` (a `duration`) schedules a flexible one, and
`triggered` (a `triggered_by` downtime ID) schedules a triggered one. See
[Downtimes](https://jaque.sh/docs/config/downtimes.md) for what each kind does once
scheduled.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.CommandService/SendCustomNotification \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"target":{"object":{"host":{"name":"gw"}}},"author":"ops","comment":"rolling restart in progress"}'
```

`SendCustomNotification` sends a `CUSTOM` notification to the object's
current escalation level with the given author and comment, in any object
state; a downtime still silences it.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.CommandService/SetObjectNotifications \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"target":{"object":{"host":{"name":"gw"}}},"enabled":false,"author":"ops"}'
```

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.CommandService/ScheduleCheck \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"target":{"object":{"host":{"name":"gw"}}}}'
```

The next check runs now and the regular cadence is kept.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.CommandService/AddComment \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"target":{"object":{"host":{"name":"gw"}}},"author":"ops","text":"network is looking at this, ticket 4521"}'
```

The response carries the `commentId` the server minted, and
`DeleteComment` takes that id. `AddComment` accepts a single object
target; a selector is rejected.

## 3. `QueryService`: reads, no `-api-token` required

`ListStatus` (current state of one host, one service, or the whole
fleet, optionally narrowed by a label selector), `ListHistory` (recent
transitions for one object or the global feed), `ListObjects` (every
configured object plus its labels), `ListContacts`, `ListViews`,
`ListSinks` (the configured perfdata sinks: name, type, input and
selector), and `GetServerInfo` (the running binary's version, the
release-stamped `vX.Y.Z`, or `"dev"` for anything else, plus
`auth_enabled`, whether the process was started with `-auth-config`,
and `read_only`, whether the process was started with `-read-only`).
Every read comes from the projection, never from the engine or the
event log directly, so reads never block on write throughput. Every
object row says what it is: `kind` is `OBJECT_KIND_HOST`,
`OBJECT_KIND_SERVICE` or `OBJECT_KIND_PROCESS` on `ListStatus` and
`ListObjects` answers (and the `-snapshot-out` artifact carries the same
field as `"kind": "host" | "service" | "process"`). Every object row also
carries its acknowledgement and downtime detail, the object's comments,
and the names of its check and notification periods. Each `Downtime` in
that detail carries a `kind` oneof naming which of the four
[downtime kinds](https://jaque.sh/docs/config/downtimes.md) it is: `fixed`, `flexible`,
`triggered` or `recurring`, the last for a downtime declared in config
rather than scheduled. `start` and `end` are always the window as
scheduled, or the current or next occurrence for a recurring downtime;
`flexible` and `triggered` also carry `active_from`/`active_until`,
unset while the downtime is still pending. Unlike `CommandService`,
reads never require `-api-token`: the bearer token protects writes
only. `-auth-config`, described below, is the one thing that does gate
reads.

Status and object rows also carry a `realm` field, and `ListStatus`
answers carry a `realms` list (`name`, `reachable`, `last_seen` per
realm). Both are set only when the answering process is an aggregator
(started with `-realms-config`) and empty on a single deployment: the
aggregator tags every row with the realm it came from, and the `realms`
list says which realms answered this request and when each was last
reached. An unreachable realm keeps its last-seen rows and reports
`reachable: false` instead of vanishing from the answer.

With `-read-only` set, `CommandService` is not mounted at all: every
request to it, whatever the RPC, answers 404. `QueryService` and
`AuthService` are unaffected. See
[Security](https://jaque.sh/docs/deployment/security.md).

## 4. `AuthService`: login, opt-in

`AuthService` exists only when the process was started with
`-auth-config`; without it the route is not mounted at all, and
`CommandService` and `QueryService` stay exactly as sections 2 and 3
describe. With one configured, both services additionally require a
resolved identity: `CommandService` needs at least the `operator` role,
`QueryService` needs at least `viewer`. See
[Security](https://jaque.sh/docs/deployment/security.md), section 3, for what
`-auth-config`'s document looks like and what the roles mean.

Three RPCs:

- `Login(user, password)` verifies a password against the auth
  document's identities and, on success, sets a `jaque_session` cookie
  carrying a signed session token; the cookie is `HttpOnly`, so no
  script on the page can read it. The response body carries the
  caller's name and role, never the session token itself.
- `Logout()` clears the `jaque_session` cookie and always succeeds.
- `WhoAmI()` resolves the caller's own credentials and returns its name
  and role, or fails with an unauthenticated error if none resolve.

A browser carries the `jaque_session` cookie automatically once
`Login` sets it; every subsequent `CommandService` and `QueryService`
call is resolved through it. A service identity skips `Login`
entirely and sends `Authorization: Bearer <token>` on every call
instead, the same header `-api-token` uses, but checked against the
identity's own token rather than a single shared one:

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.QueryService/ListStatus \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <the service identity token>' \
  -d '{}'
```

A request carrying neither a valid cookie nor a valid bearer token is
turned away with an unauthenticated error before it reaches the RPC.

## 5. This is the dashboard's own API

Every action in [Dashboard](https://jaque.sh/docs/ui/dashboard.md), acknowledge, downtime,
the status table, history, is a call to one of these RPCs. There is no
separate, richer internal API the UI gets and scripts don't.

## 6. Errors

Every failure, across `CommandService`, `QueryService` and `AuthService`,
comes back as a Connect transport error, never as an `ok`/`error` field
inside the response body. The codes actually returned:

- `invalid_argument`: the request itself is malformed: a batch with no
  results, a downtime missing `start` or `end`, a target whose service set
  names no host, an unparseable field.
- `not_found`: the named panel or object doesn't exist (`QueryPanel`,
  `ListObjectMetrics`, `QueryObjectMetric`).
- `failed_precondition`: the request is well-formed but the config it
  needs isn't there: a panel's datasource isn't declared, or no default
  datasource is configured for an object-metric query.
- `unauthenticated`: `-api-token` is set and the request carries no
  bearer token, or the token doesn't match; or, with `-auth-config` set,
  no session cookie or bearer token resolves to an identity.
- `permission_denied`: with `-auth-config` set, the resolved
  identity's role doesn't meet the RPC's requirement (`operator` for
  `CommandService`, `viewer` for `QueryService`).
- `unavailable`: a panel or object-metric query reached a configured
  datasource, but the datasource itself failed to answer.
- `internal`: a failure on jaque's own side unrelated to the caller's
  input, such as resolving a datasource that already passed every other
  check.

## 7. Security considerations

`-api-token`, when set, is checked with a constant-time comparison so a
response-time difference cannot leak how much of the token matched; when
unset every `CommandService` write is open to anyone who can reach
`-listen`. `QueryService` is never gated by `-api-token` (see
section 3), though it is gated by role when `-auth-config` is set,
see section 4. Absent `-auth-config`, it should not carry anything
an unauthenticated caller should not see: `ListSinks` and `ListContacts`
omit sink URLs, headers and contact target payloads (webhook URLs,
command lines) from their responses, because those can carry
credentials. `AcknowledgeProblem` and `ScheduleDowntime`'s
`author`/`comment` fields are operator-supplied free text with no
server-side validation beyond the RPC's type, the same posture the
event log carries them into, see [Event log
schema](../reference/event-log-schema.md), section 4. There is no
transport encryption at this layer: run behind a reverse proxy or a
network boundary you control if `-listen` is reachable beyond a trusted
network.
