go-schedule

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.

MethodPathResult
GET/v1/manifestStable identity, display name, product version, protocol versions, operating mode, sorted capabilities, and safe platform facts
PATCH/v1/manifestRename from {"display_name":"Workshop scheduler"} and return the resulting manifest
POST/v1/manifest/resetCompare 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.

Bounded system summary

GET /v1/system-summary and its authenticated remote equivalent GET /api/v1/system-summary return one daemon-owned go-schedule.system-summary.v1 observation for the desktop All Systems view. Observe authority is sufficient. The response reports its observation time, active-task count, nearest upcoming occurrence in the next 24 hours, failed-run count in the previous 24 hours, current unacknowledged-alert count, and failed or retrying notification-delivery count in the previous 24 hours. Each category includes at most one safe representative identifier for drill-down.

The response never includes task commands, arguments, environment, stdin, working directories, run output, alert messages, notification destinations, authorization, payloads, certificates, or credentials. Counts are computed at the database boundary and representatives are limited independently of retained history. The request is read-only, creates no overview cache in the daemon, and does not imply shared state or coordination with another daemon.

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.

MethodPathMinimum capabilityResult
GET/v1/access/actorsEnrollList credential-independent actors
POST/v1/access/actorsEnrollCreate a non-built-in actor
PATCH/v1/access/actors/{id}EnrollUpdate name, capability, state, or expiration
POST/v1/access/actors/{id}/revokeEnrollIrreversibly revoke an actor
GET/v1/auditEnrollList filtered retained audit events
GET/v1/audit/exportEnrollExport 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.

MethodPathResult
GET/v1/triggers{"triggers": [...]} with redacted records
POST/v1/triggersCreate 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}/enableEnable the trigger
POST/v1/triggers/{id}/disableDisable the trigger
POST/v1/triggers/{id}/rotateAtomically replace and return the key
POST/v1/triggers/{id}/revealExplicitly return the current key
POST/v1/triggers/fireAccept {"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.

MethodPathBehavior
GET/v1/filesystem-watchersList definitions joined with current runtime health
POST/v1/filesystem-watchersCreate 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}/enableEnable and reload observation
POST/v1/filesystem-watchers/{id}/disableDisable, 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.

MethodPathPurpose
GET/v1/trigger-setsList redacted Trigger Sets and members
POST/v1/trigger-setsAtomically 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}/enableAtomically enable every member
POST/v1/trigger-sets/{id}/disableAtomically disable every member
POST/v1/trigger-sets/{id}/rotateAtomically rotate every key and return ordered replacement secrets
POST/v1/trigger-sets/{id}/revealExplicitly return current ordered secrets

GET /v1/search returns one versioned, secret-free search observation over tasks, groups, failed runs, upcoming scheduled tasks, and unacknowledged alerts. The required q parameter accepts one through 200 Unicode characters after trimming. Repeated kind parameters may select task, group, failure, schedule, or alert; omission searches every kind. limit defaults to 50 and accepts one through 50.

The response schema is go-schedule.daemon-search.v1. Each result contains its kind, stable object identity, safe display name, optional task identity, bounded context, optional occurrence time or enabled state, and advisory action hints. The response never includes task commands, arguments, working directories, environments, stdin, run output, alert messages, credentials, notification payloads, certificate bodies, or secret-bearing endpoints. Observe authority is sufficient, and the read creates no mutation audit record.

Upcoming schedule results are the nearest valid occurrence for matching active scheduled tasks within 30 days. Failure results include failed runs only, and alert results include unacknowledged alerts only. Results are deterministic and bounded; truncated reports when more safe matches existed than the requested limit.

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.

MethodPathResult
GET/v1/chains{"chains": [...]}
POST/v1/chainsCreate 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, health_interval_seconds, created_at, and updated_at. Assignment objects add failure_threshold, on_failure_to_start, duration_threshold_seconds, on_recovery, reminder_interval_seconds, and quiet_period_seconds to the existing outcome fields. See Webhook notifications for condition, receiver payload, precedence, retry, duplicate, heartbeat, and security contracts.

MethodPathResult
GET/v1/notification-channels{"notification_channels": [...]} with redacted metadata
POST/v1/notification-channelsCreate from name, endpoint, optional authorization, enabled, and health_interval_seconds; 201
GET/v1/notification-channels/{id}One redacted channel
PATCH/v1/notification-channels/{id}Update optional name, endpoint, enabled, or health_interval_seconds
DELETE/v1/notification-channels/{id}Remove assignments and unfinished work while preserving safe terminal history; 204
POST/v1/notification-channels/{id}/enableEnable new delivery creation
POST/v1/notification-channels/{id}/disableDisable new delivery creation
POST/v1/notification-channels/{id}/rotateReplace or clear the write-only authorization value
POST/v1/notification-channels/{id}/testQueue a transport-equivalent test delivery; 202
GET/v1/tasks/{id}/notificationsList direct task assignments
PUT/v1/tasks/{id}/notificationsAtomically replace direct task assignments
GET/v1/tasks/{id}/notifications/effectiveReturn selected source scope and complete effective assignments
GET/v1/groups/{id}/notificationsList direct group assignments
PUT/v1/groups/{id}/notificationsAtomically replace direct group assignments
GET/v1/notification-deliveriesList redacted evidence with optional channel, task, run, state, and limit filters

An assignment contains channel_id plus one or more outcome or problem conditions; one channel may occur only once per scope. Recovery and reminders require at least one problem condition. The nearest non-empty task or group scope replaces all more distant assignments. Delivery responses remain distinct from run responses and include safe condition metadata, 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.

Edit this page on GitHub

On this page