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


`AdminService` is the one write surface that changes what config is
running, rather than what an object's state is. It carries four RPCs,
`ValidateConfig`, `ApplyConfig`, `GetConfig` and `ListAuditRecords`, and
it exists only when both `-auth-config` is set and the process was not
started with `-read-only`.

All three RPCs act on the `-config` document only. Identities live in
the separate document `-auth-config` loads (see
[Security](https://jaque.sh/docs/deployment/security.md)); `GetConfig` never returns them
and `ApplyConfig` never accepts them, because they are not part of the
document either RPC reads or writes.

## 1. Admin-gated, and absent without auth

`AdminService` requires the `admin` role: `viewer` and `operator`
identities are refused with a permission error, the same way
`CommandService` refuses an unauthenticated caller. With no
`-auth-config` set at all, the route is not mounted: there is no
identity to require, so a config write over the API is never offered to
an unauthenticated caller. That is the same posture the config file and
the external command FIFO already have for a local operator. See
[Security](https://jaque.sh/docs/deployment/security.md) for what `-auth-config` looks
like and what the three roles mean.

`-read-only` unmounts `AdminService` for the same reason it unmounts
`CommandService`: a process serving queries and the dashboard only has
no write surface to gate, `admin` role or not.

## 2. `ValidateConfig`: check without touching anything

`ValidateConfig(cue)` runs a whole CUE document past the same loader a
SIGHUP reload uses, without writing anything to disk and without
touching the running config. Success returns an empty response.
Rejection is a transport error (`CodeInvalidArgument`) carrying a
`ConfigDiagnostics` detail: one `ConfigDiagnostic` per problem the
loader found, each naming the file, line, column and message, exactly
as a rejected SIGHUP logs them.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.AdminService/ValidateConfig \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <admin identity token>' \
  -d '{"cue":"hosts: gw: {address: \"192.168.1.1\", check: {type: \"icmp\", host: address}}"}'
```

## 3. `ApplyConfig`: persist, then hot-swap

`ApplyConfig(cue)` runs the identical check and, on success, rewrites
the served `-config` file with `cue`'s bytes and swaps the running
config through the same fanout a SIGHUP reload uses. Every consumer
that reacts to a reload today, the scheduler, the sinks, the notifier's
macro map, sees an apply exactly the same way. The file is rewritten
before the swap: a process that stops between the two steps still
converges on restart to the config it would have been running anyway.

Success and rejection follow the same shape as `ValidateConfig`: an
empty response, or a `CodeInvalidArgument` error carrying a
`ConfigDiagnostics` detail. A rejected apply changes nothing: the
served file is left untouched and the running config keeps serving what
it was serving before the call.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.AdminService/ApplyConfig \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <admin identity token>' \
  -d '{"cue":"hosts: gw: {address: \"192.168.1.1\", check: {type: \"icmp\", host: address}}"}'
```

A missing `cue` field is also a `CodeInvalidArgument` transport error,
but carries no `ConfigDiagnostics` detail: that is a malformed
request rather than a rejected document. Any other applier failure,
something other than a rejected CUE document, comes back as
`CodeInternal`, also with no detail.

## 4. `GetConfig`: read the served document

`GetConfig()` takes no fields and returns the whole CUE document
currently served from the `-config` file, the same text `ValidateConfig`
and `ApplyConfig` would each start from. It requires the `admin` role,
the same as the other two RPCs.

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

```json
{"cue": "hosts: gw: {address: \"192.168.1.1\", check: {type: \"icmp\", host: address}}\n"}
```

A read failure comes back as `CodeInternal`, the same code an
`ApplyConfig` failure other than a rejected document returns.

## 5. `ListAuditRecords`: read the audit trail

`ListAuditRecords(since, limit)` returns audit records newest first.
`since` is optional: an unset value means everything still retained.
`limit` of `0` means `100`; values above `1000` are clamped to `1000`.
It requires the `admin` role, the same as the rest of `AdminService`.

```sh
curl -s -X POST http://127.0.0.1:8080/jaque.v1.AdminService/ListAuditRecords \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <admin identity token>' \
  -d '{"limit":2}'
```

```json
{
  "records": [
    {
      "at": "2026-09-07T10:32:11Z",
      "source": "AUDIT_SOURCE_API",
      "principal": {"name": "jdoe", "role": "ROLE_OPERATOR"},
      "peer": "10.0.4.12:53214",
      "rpc": "/jaque.v1.CommandService/AcknowledgeProblem",
      "commandJson": "{\"Target\":{\"object\":\"web-01/http\"},\"Sticky\":false,\"Author\":\"jdoe\",\"Comment\":\"investigating\"}",
      "outcome": "AUDIT_OUTCOME_ACCEPTED"
    },
    {
      "at": "2026-09-07T10:31:58Z",
      "source": "AUDIT_SOURCE_EXTCMD",
      "rpc": "SCHEDULE_SVC_DOWNTIME",
      "commandJson": "{\"Target\":{\"object\":\"web-01/http\"},\"Start\":\"2026-09-07T10:31:58Z\",\"End\":\"2026-09-07T11:31:58Z\",\"Author\":\"nagios\",\"Comment\":\"maintenance\"}",
      "outcome": "AUDIT_OUTCOME_ACCEPTED"
    }
  ]
}
```

A record with no `principal` is one nothing authenticated the sender
for, the same as the command file's own audit lines. A read failure
comes back as `CodeInternal`, the same code a `GetConfig` failure
returns.

## 6. Profile demo narrows what `ValidateConfig`/`ApplyConfig` accept

Under `-config-profile demo`, both RPCs validate the submitted document
against the narrowed schema instead of the full one: command checks,
command contacts, exec and file sinks, and any non-empty `commands`
block are rejected the same way an unsupported check type is:
`CodeInvalidArgument` carrying a `ConfigDiagnostics` detail, same shape
as the example in Errors below. See [CLI flags](https://jaque.sh/docs/reference/cli-flags.md)
for the full list of what the profile refuses.

## 7. Errors

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

- `invalid_argument`: a missing `cue` field, or a rejected CUE document
  (`ValidateConfig`/`ApplyConfig` only; `GetConfig` takes no fields and
  never returns this code, and `ListAuditRecords` never returns it either).
  A rejected document additionally carries a `ConfigDiagnostics` detail,
  one `ConfigDiagnostic` per problem the loader found.
- `unauthenticated`: no session cookie or bearer token resolves to an
  identity.
- `permission_denied`: the resolved identity's role is below `admin`.
- `internal`: an applier failure other than a rejected CUE document, a
  `GetConfig` read failure, or a `ListAuditRecords` read failure.

A rejected `ValidateConfig` response body looks like this:

```json
{
  "code": "invalid_argument",
  "message": "config: invalid:\nhosts.gw.check: unknown check type \"bogus\", must be one of [command dns http icmp legacy passive snmp tcp tls wasm]:\n    config.cue:3:2\n",
  "details": [
    {
      "type": "jaque.v1.ConfigDiagnostics",
      "value": "<base64-encoded protobuf>",
      "debug": {
        "diagnostics": [
          {
            "message": "hosts.gw.check: unknown check type \"bogus\", must be one of [command dns http icmp legacy passive snmp tcp tls wasm]",
            "file": "config.cue",
            "line": 3,
            "column": 2
          }
        ]
      }
    }
  ]
}
```

`ApplyConfig` rejects the same way, and leaves the served file and running
config untouched.

## 8. Config stays code

Every RPC takes or returns a whole document, never a partial shape for
one host or one check. Pipelines that render CUE and commit it remain
the recommended way to manage a fleet's config in a real estate. This
service is the admin convenience and the playground's upload path,
alongside that workflow rather than in place of it. A caller that wants
to add one object resolves the current config with `GetConfig`, edits
its own copy, and calls `ApplyConfig` with the whole result.

## 9. Security considerations

An admin identity that can call `ApplyConfig` can rewrite the entire
served config, a strictly larger blast radius than anything an
`operator` identity can do through `CommandService`. Treat an admin
session or token with the same care as shell access to the host: rotate
`-session-secret` on any suspicion a session leaked, the same guidance
[Security](https://jaque.sh/docs/deployment/security.md) gives for every other role.
