Create a service enrollment request
const url = 'https://example.com/api/certs/service/enroll';const options = {method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/api/certs/service/enroll \ --header 'Content-Type: application/json' \ --data '{}'Approving one yields an enrollment code rather than a certificate; the
certificate comes later from /api/certs/service/retrieve. This is the
one path where an approval does not queue a signing job.
Request Bodyrequired
Section titled “Request Bodyrequired”The service public key and the options being asked for
object
The service public key and the options being asked for
object
object
Extensions are the SSH certificate extensions requested (e.g. “permit-pty”, “permit-agent-forwarding”).
ForceCommand is the SSH “force-command” critical option request.
NoTouchRequired requests the OpenSSH “no-touch-required” extension. Only meaningful for service enrollment of a hardware-backed sk- key.
SourceAddresses are the caller’s own interface addresses, unioned server-side with the address the request was observed from.
Example generated
{}Responses
Section titled “Responses”Request created
object
object
ExpiresAt is when this request stops being approvable, from its own
type’s budget (cert_options.
A client bounds its wait by this rather than by a local guess, and a console displays the time remaining from it. Without it, the client’s timeout and the server’s budget are two numbers an operator keeps in agreement by hand, and disagreement surfaces as a client still waiting on a request the server already killed. RFC 8628 carries expires_in for the same reason.
UserCode is the short code a human types into the web UI, grouped for display (“K7M4-QP2X”). Console requests only; empty for every other type, so no existing consumer changes.
This is the only response it ever appears in. It is not in the SSE stream, not in the request detail, and not in any audit record.
VerificationURL is the page that accepts UserCode; VerificationURLComplete embeds the code so a device with a camera or a keyboard can skip the code box entirely. Relative, like the two URLs above, and console requests only.
Complete is kept short (/c/, not /approve/
Example generated
{ "data": { "approval_url": "example", "events_url": "example", "expires_at": "example", "request_id": "example", "user_code": "example", "verification_url": "example", "verification_url_complete": "example" }, "error": "example"}Malformed body, or a public key that will not parse
object
Data is always null on an error response.
Error is a human-readable message. Branch on the HTTP status rather than this string; it is for humans and logs.
ErrorCode is a machine-readable error classifier. One of the error_code constants from internal/apitypes; use this to decide whether to retry or branch on the failure mode.
Example
{ "error": "certificate request \"9f1c…\" not found", "error_code": "not_found"}