Skip to main content Link Menu Expand (external link) Document Search Copy Copied

Local and remote JSON API

Audience: client and integration authors
Applies to: the v1.4.0 local and remote API contract and later
Transport: protected local IPC by default; optional authenticated TLS 1.3 HTTPS on the reviewed /api/v1 allowlist

The CLI and desktop app use the same versioned JSON handlers hosted by goschedd. Local IPC paths begin at /v1; the opt-in remote adapter exposes only reviewed operations below /api/v1. Errors use {"error":{"code":"...","field":"...","message":"..."}}.

The remote access guide defines listener setup, deployment modes, pairing, profiles, revocation, and trust ownership. Installing or upgrading never enables a listener.

Direct JSON client workflow

A direct client must retain the operator-provided certificate, expected daemon installation ID, credential ID, and bearer value in protected storage. First exchange a one-time phrase without following redirects, then compare both the response daemon_id and GET /api/v1/manifest installation_id to the expected value before retaining or using the credential. Never disable TLS verification or accept an identity change automatically.

Read the phrase and bearer from protected input rather than a URL, source file, or command argument. This shell sketch intentionally keeps both values out of command history:

read -rs PAIRING_PHRASE
printf '{"daemon_id":"%s","pairing_id":"%s","phrase":"%s","display_name":"JSON client","kind":"json","capability":"observe"}' "$DAEMON_ID" "$PAIRING_ID" "$PAIRING_PHRASE" | curl --fail --proto =https --tlsv1.3 --max-redirs 0 --cacert daemon.pem --json @- "$ENDPOINT/api/v1/enroll"
read -rs BEARER
printf 'header = "Authorization: Bearer %s"\n' "$BEARER" | curl --fail --proto =https --tlsv1.3 --max-redirs 0 --cacert daemon.pem --config - "$ENDPOINT/api/v1/tasks?limit=100"
unset PAIRING_PHRASE BEARER

Collection responses use bounded limits and continuation parameters where documented by the generated OpenAPI contract in api/openapi/remote-v1.yaml. Classify stable error envelope codes before considering HTTP text. Treat 401 unauthorized as missing or invalid authentication, 401 credential_revoked as a profile that requires new enrollment material, 403 as insufficient actor authority, 409 as identity or compatibility conflict, 429 as rate limiting, and 5xx as server failure. Use bounded timeouts for every request.

GET and other retry-safe operations may be retried deliberately. The desktop connection owner retries transient health and subscription failures with jittered delays that begin below one second and cap at thirty seconds. It stops for authentication, revocation, authorization, compatibility, certificate-trust, and daemon-identity failures. If a mutation response is lost or otherwise ambiguous, do not replay it automatically: report the outcome as uncertain, refresh the authoritative resource, determine whether the first mutation committed, then require a deliberate retry if needed. Server-Sent Events are invalidation hints; reconnecting clients refresh authoritative state rather than depending on replay.

Daemon identity and capability manifest

The daemon identity lifecycle defines stable installation identity, editable names, restore and clone behavior, and reset safety. The manifest is deliberately separate from health so existing liveness clients remain compatible.

Method Path Result
GET /v1/manifest Stable identity, display name, product version, protocol versions, operating mode, sorted capabilities, and safe platform facts
PATCH /v1/manifest Rename from {"display_name":"Workshop scheduler"} and return the resulting manifest
POST /v1/manifest/reset Compare and replace identity from {"confirm_installation_id":"<current-id>"} and return the resulting manifest

The current manifest reports local API v1, no remote API version, and local_only operating mode. It excludes hostnames, network addresses, storage paths, accounts, environment values, commands, credentials, trigger keys, scheduler records, and lifecycle timestamps. A stale reset confirmation returns 409 conflict; invalid names and malformed requests return 400 validation_failed; neither failure mutates state. GET /v1/health remains unchanged.

Actor permissions and management audit

The actor permissions and management audit contract applies one Observe, Operate, Manage, and Enroll hierarchy to every registered management operation. Protected local IPC resolves to the built-in local actor without new authentication input.

Method Path Minimum capability Result
GET /v1/access/actors Enroll List credential-independent actors
POST /v1/access/actors Enroll Create a non-built-in actor
PATCH /v1/access/actors/{id} Enroll Update name, capability, state, or expiration
POST /v1/access/actors/{id}/revoke Enroll Irreversibly revoke an actor
GET /v1/audit Enroll List filtered retained audit events
GET /v1/audit/export Enroll Export filtered events as newline-delimited JSON

Audit filters are actor_id, operation, result, since, until, and limit. The default limit is 100 and the maximum is 1,000. Invalid filters return 400 validation_failed. Authorization denial returns 403 forbidden; failure to persist mandatory audit evidence returns 503 audit_unavailable.

External triggers

An ordinary trigger representation contains id, name, optional set_id, optional set_name, optional set_position, target_task_id, target_task_name, enabled, readiness, reason, created_at, and updated_at. It never contains the raw key. A set member cannot be retargeted individually; use the set endpoint so every member retains the shared target invariant.

Method Path Result
GET /v1/triggers {"triggers": [...]} with redacted records
POST /v1/triggers Create from name, target task, and optional enabled state; 201 with the key
GET /v1/triggers/{id} One redacted trigger
PATCH /v1/triggers/{id} Update name or target task
DELETE /v1/triggers/{id} Delete; 204
POST /v1/triggers/{id}/enable Enable the trigger
POST /v1/triggers/{id}/disable Disable the trigger
POST /v1/triggers/{id}/rotate Atomically replace and return the key
POST /v1/triggers/{id}/reveal Explicitly return the current key
POST /v1/triggers/fire Accept {"key":"gst_..."} and submit one run request; 202

Fire failures use stable codes: trigger_unknown, trigger_disabled, trigger_target_missing, trigger_command_incomplete, trigger_task_inactive, trigger_task_disabled, trigger_group_blocked, or trigger_dispatch_unavailable. Error responses never echo the submitted key.

Filesystem watchers

A filesystem watcher representation contains id, name, kind, path, optional pattern, recursive, string durations debounce and stability, target identity, enabled state, runtime health, readiness, reason, and timestamps. kind is file for one exact path or directory for basename-glob selection. A directory pattern defaults to *; recursive selection excludes linked directories. Durations accept Go duration syntax such as 250ms, 2s, or 1m and must be from 25 milliseconds through one hour.

Method Path Behavior
GET /v1/filesystem-watchers List definitions joined with current runtime health
POST /v1/filesystem-watchers Create a definition and reload observation; 201
GET /v1/filesystem-watchers/{id} Show one definition and current health
PATCH /v1/filesystem-watchers/{id} Atomically update selection, timing, target, enabled state, or name and reload observation
DELETE /v1/filesystem-watchers/{id} Delete, cancel pending candidates, and reload observation; 204
POST /v1/filesystem-watchers/{id}/enable Enable and reload observation
POST /v1/filesystem-watchers/{id}/disable Disable, cancel pending candidates, and reload observation

Watcher lifecycle and health events use kind: "filesystem_watcher" with identity, name, verb, and optional health only. They omit configured and matched paths. Filesystem-originated runs have trigger: "filesystem_watcher" and source_watcher_id; they never retain the matched path.

Trigger Sets

Ordinary Trigger Set representations include stable set identity, name, target, member and enabled counts, ordered redacted members, and timestamps. Create, reveal, and rotate responses additionally contain ordered member keys and complete commands.

Method Path Purpose
GET /v1/trigger-sets List redacted Trigger Sets and members
POST /v1/trigger-sets Atomically create 1 through 99 members and return ordered secrets
GET /v1/trigger-sets/{id} Show one redacted Trigger Set
PATCH /v1/trigger-sets/{id} Atomically retarget every member
DELETE /v1/trigger-sets/{id} Atomically delete the set and every member; 204
POST /v1/trigger-sets/{id}/enable Atomically enable every member
POST /v1/trigger-sets/{id}/disable Atomically disable every member
POST /v1/trigger-sets/{id}/rotate Atomically rotate every key and return ordered replacement secrets
POST /v1/trigger-sets/{id}/reveal Explicitly return current ordered secrets

Runtime storage information

GET /v1/runtime-info returns the absolute effective paths used by the running daemon: data_dir, database_path, optional config_path, log_path, and lock_path. Desktop clients use this endpoint for read-only storage disclosure, including when the daemon was launched with a custom configuration path.

Completion chains

The chain representation contains id, source_task_id, source_task_name, target_task_id, target_task_name, on_outcome, created_at, and updated_at.

Method Path Result
GET /v1/chains {"chains": [...]}
POST /v1/chains Create from source, target, and outcome; 201
GET /v1/chains/{id} One chain
PATCH /v1/chains/{id} Update any non-empty subset of mutable fields
DELETE /v1/chains/{id} Delete; 204

Create example:

{
  "source_task_id": "source-id",
  "target_task_id": "target-id",
  "on_outcome": "success"
}

on_outcome is success, failure, or any. Missing resources return not_found. Invalid outcomes, self-links, duplicates, and direct or indirect cycles return validation_failed without partial mutation.

Correlated history and events

Completion-triggered entries from GET /v1/runs have trigger set to completion and include optional source_task_id and source_run_id. Externally triggered entries have trigger set to external_trigger and include source_trigger_id. Filesystem-triggered entries have trigger set to filesystem_watcher and include source_watcher_id. Raw keys and matched file paths are never stored in run history.

GET /v1/events emits kind: "chain" with a created, updated, or deleted verb. Create and update include the current chain; delete carries its stable ID.

Trigger lifecycle events use kind: "trigger" and contain only a redacted trigger or its stable deletion ID.

Trigger Set lifecycle events use kind: "trigger_set" and contain set identity, name, member count, and verb without member keys. One set-level mutation publishes one event after its transaction commits.

Webhook notifications

Notification channels are reusable write-only webhook destinations. Ordinary channel responses contain id, name, kind, endpoint_summary, has_authorization, enabled, created_at, and updated_at. See Webhook notifications for the receiver payload, precedence, retry, duplicate, and security contracts.

Method Path Result
GET /v1/notification-channels {"notification_channels": [...]} with redacted metadata
POST /v1/notification-channels Create from name, endpoint, optional authorization, and optional enabled; 201
GET /v1/notification-channels/{id} One redacted channel
PATCH /v1/notification-channels/{id} Update optional name, endpoint, or enabled
DELETE /v1/notification-channels/{id} Remove assignments and unfinished work while preserving safe terminal history; 204
POST /v1/notification-channels/{id}/enable Enable new delivery creation
POST /v1/notification-channels/{id}/disable Disable new delivery creation
POST /v1/notification-channels/{id}/rotate Replace or clear the write-only authorization value
POST /v1/notification-channels/{id}/test Queue a transport-equivalent test delivery; 202
GET /v1/tasks/{id}/notifications List direct task assignments
PUT /v1/tasks/{id}/notifications Atomically replace direct task assignments
GET /v1/tasks/{id}/notifications/effective Return selected source scope and complete effective assignments
GET /v1/groups/{id}/notifications List direct group assignments
PUT /v1/groups/{id}/notifications Atomically replace direct group assignments
GET /v1/notification-deliveries List redacted evidence with optional channel, task, run, state, and limit filters

An assignment contains channel_id, on_success, and on_failure; at least one outcome must be true and one channel may occur only once per scope. The nearest non-empty task or group scope replaces all more distant assignments. Delivery responses remain distinct from run responses and include a safe parsed event, attempts, timestamps, last status, and bounded diagnostic without protected endpoint or authorization fields.

Remote HTTPS API

The optional network API is a separate allowlisted adapter under /api/v1; it does not expose the complete local /v1 mux. Its canonical OpenAPI 3.1 description is api/openapi/remote-v1.yaml. Health, manifest discovery, and one-time enrollment are public within the trusted TLS boundary. Every other listed operation requires one strict Authorization: Bearer header and is authorized against the credential’s current actor capability before the existing local handler runs.

GET /api/v1/access/current returns the authenticated actor’s current server-owned capability without requiring Enroll authority. Clients use this protected read during connection negotiation, compare it with the selected profile’s expected grant, and fail closed when an administrator has reduced that grant.

The enrollment request is POST /api/v1/enroll with JSON fields pairing_id, phrase, and daemon_id. Successful exchange returns the bearer token exactly once. Authentication failures use 401 unauthorized, browser-origin requests use 403 origin_rejected, unknown or deliberately excluded routes use 404 not_found, and rate limits use 429 rate_limited with Retry-After: 1.