Skip to main content
Version: 0.16

Executor API

Connecting external executors​

Anything that can POST HTTPS can report certificate work against a job. Agents execute TokenTimer-planned jobs; your own tooling reports the outcome of work it did itself.

  1. Have a job. Scheduled renewals appear on the Jobs tab. A workspace manager can also Create manual job or POST /api/v1/workspaces/{id}/certops/jobs. An issue job takes no subject; see Issue a certificate. Copy the job ID.
  2. Create a machine API token with certops:events:write (and certops:evidence:write if you upload evidence). The ttx_ value is shown once.
  3. Run the work locally and POST lifecycle events against that jobId.
  4. Review timeline and evidence on the Jobs tab.

A manual renew payload accepts only an optional reason. Other fields fail with CERTOPS_RENEWAL_OVERRIDE_INVALID. Manual deploy/reload need a full payload; they do not inherit a linked profile. Only the scheduler and bulk renew fill payload from a profile.

Machine API tokens​

Scoped keys for non-human callers, bound to one workspace.

  • Format ttx_<id>_<secret>. Only a SHA-256 hash is stored.
  • Shown once at creation. Optional expiry. Last-used is listed.
  • Create and revoke on Settings (/certops/settings). Revocation is immediate.
Authorization: Bearer ttx_abc123_...

Token scopes​

  • certops:read - inventory
  • certops:events:write - report events (cannot create jobs)
  • certops:jobs:read - jobs and timelines
  • certops:evidence:write - attach evidence
  • certops:observations:write, certops:provision:execute - cert-manager controller only; requires a bound controllerClusterId. See cert-manager.

Grant one token per integration. Do not commit tokens. Prefer expiry. If a token may have leaked, revoke it.

Executor jobs​

A job is one unit of work: issue, renew, deploy, reload, revoke, or no-op.

Where jobs come from​

sourceWho creates it
automationRenewal scheduler
apiWorkspace manager (dashboard or session API)
systemControl plane outside the renewal sweep

Machine tokens only report against an existing jobId. Unknown ids return 404 CERTOPS_JOB_NOT_FOUND.

Statuses move forward only: pending_approval → pending (approve writes this in one step; jobs do not linger at approved) → claimed → running → a terminal state (succeeded, failed, rejected, blocked, cancelled, orphaned_unknown_effect). orphaned_unknown_effect means the agent went silent after it may already have changed a host. See Reconciling interrupted jobs. The Jobs tab lists jobs with a copyable job ID; opening a row shows the timeline and evidence.

How a job is bound to one agent​

Claiming is exclusive: two agents never receive the same job. For renew/deploy/reload/revoke against a certificate an agent discovered on disk, TokenTimer pins the job to that agent. If that agent is offline, the job waits at pending. Pass assignedAgentId to hand the certificate to a replacement agent. Observed-only certificates are refused at creation (CERTOPS_CERTIFICATE_NOT_AGENT_DEPLOYABLE); see Certificates.

Reporting events​

POST an event with a caller-chosen eventId, jobId, workspace ID, status, event type, and timestamp.

  • Types: job.accepted, job.started, job.progress, job.completed, job.failed, job.rejected, evidence.attached.
  • Same eventId + same payload is a no-op. Same eventId + different payload is a conflict.
  • Unknown top-level fields are rejected.
  • Up to 16 inline evidence items. Secret-looking metadata names are refused.

Needs certops:events:write. Evidence-bearing requests also need certops:evidence:write.

Evidence and redaction​

Evidence is scanned before storage. Generic secrets are replaced with a placeholder and marked redactionApplied. Output is capped at 64 KiB (CERTOPS_EVIDENCE_OUTPUT_TOO_LARGE). Private key material is rejected (HTTP 422 PRIVATE_KEY_MATERIAL_REJECTED), never stored. Do not pipe raw certbot or openssl key output into evidence.

Executor example​

#!/bin/sh
JOB_ID="${TOKENTIMER_JOB_ID:?set TOKENTIMER_JOB_ID to an existing job id}"
EVENT_ID="$JOB_ID-completed"

curl -s -X POST \
'https://your-tokentimer.example/api/v1/certops/executor/events' \
-H "Authorization: Bearer $TOKENTIMER_TOKEN" \
-H 'Content-Type: application/json' \
-d "{
\"schemaVersion\": 1,
\"eventId\": \"$EVENT_ID\",
\"jobId\": \"$JOB_ID\",
\"workspaceId\": \"WORKSPACE_ID\",
\"executorId\": \"certbot-$(hostname)\",
\"status\": \"succeeded\",
\"eventType\": \"job.completed\",
\"occurredAt\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
\"message\": \"certbot renewed $RENEWED_DOMAINS\"
}"

Step-by-step: Connect an external executor.

API reference​

Executor routes (bearer ttx_...):

  • POST /api/v1/certops/executor/events
  • POST /api/v1/certops/jobs/{jobId}/events
  • POST /api/v1/certops/jobs/{jobId}/evidence

Workspace session routes under /api/v1/workspaces/{id}/certops: GET/POST /jobs, GET /jobs/{jobId}, GET /jobs/{jobId}/log, GET /jobs/{jobId}/evidence, GET/POST /tokens, POST /tokens/{tokenId}/revoke.

Each token shares one rate-limit bucket (120 requests / 60s by default) across all executor routes. Exceeding it returns 429 CERTOPS_MACHINE_RATE_LIMITED with a Retry-After header.

Common errors: 401 CERTOPS_API_TOKEN_UNAUTHORIZED, 403 CERTOPS_API_TOKEN_SCOPE_DENIED, 404 CERTOPS_JOB_NOT_FOUND, 409 CERTOPS_EXECUTOR_EVENT_CONFLICT, 422 PRIVATE_KEY_MATERIAL_REJECTED, 413 CERTOPS_EVIDENCE_OUTPUT_TOO_LARGE.

See Create a manual CertOps job.