Skip to main content
Version: 0.15

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_approvalpending (approve writes this in one step; jobs do not linger at approved) → claimedrunning → 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.