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.
- 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. Anissuejob takes no subject; see Issue a certificate. Copy the job ID. - Create a machine API token with
certops:events:write(andcertops:evidence:writeif you upload evidence). Thettx_value is shown once. - Run the work locally and POST lifecycle events against that
jobId. - 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 tokens and the executor API are Pro and Team. Free returns 402 PLAN_FEATURE_REQUIRED. A frozen workspace returns 403 WORKSPACE_FROZEN.
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- inventorycertops:events:write- report events (cannot create jobs)certops:jobs:read- jobs and timelinescertops:evidence:write- attach evidencecertops:observations:write,certops:provision:execute- cert-manager controller only; requires a boundcontrollerClusterId. 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
source | Who creates it |
|---|---|
automation | Renewal scheduler |
api | Workspace manager (dashboard or session API) |
system | Control 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. SameeventId+ 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://tokentimer.ch/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/eventsPOST /api/v1/certops/jobs/{jobId}/eventsPOST /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.