Skip to main content
Version: v0.1.0-rc.1

🔌 gRPC API

CryptOS exposes two gRPC services. The protobuf definitions are the source of truth: the node API in proto/cryptos/node/v1 in the cryptos-node repo, and the FleetOS Manager API in proto/cryptos/fleet/v1 in the cryptos-manager repo; this page explains the parts that need more than the field comments.

ServicePackageServed byWhat it covers
NodeServicecryptos.node.v1each CryptOS node, over mTLS on port 443 and the local UNIX socketone node: config, status, ceremony, issuance, revocation, key backup and rotation, image upgrades, the audit log
FleetServicecryptos.fleet.v1the FleetOS Manager, as a Connect endpointmany nodes: inventory, the profile catalog, enrollment, the audit log, operator credentials, MCP agent keys, step-up approvals

The rest of the RPCs are not written up here yet. Until they are, read the comments in proto/cryptos/node/v1/node.proto (cryptos-node) and proto/cryptos/fleet/v1/fleet.proto (cryptos-manager). 🚧

Issuance warnings​

Three NodeService responses carry a repeated string warnings field. A warning never means the call failed: the node did the work and is telling the operator something they should read.

RPCFieldWhen it is set
IssueLeafIssueLeafResponse.warnings (2)the leaf's notAfter was capped to the issuing CA's own notAfter
SignSubordinateCSRSignSubordinateCSRResponse.warnings (3)the child CA certificate's notAfter was capped to the parent's notAfter
ApplyConfigApplyConfigResponse.warnings (4)a profile's validity_days, counted from now, already runs past this CA's notAfter

The ApplyConfig warnings are advisory. A CA's remaining lifetime shrinks every day, so every profile eventually outlives it; the config is still applied. Each warning names the profile and the CA's notAfter date, and says what will happen: under cap its certificates will be capped to that date, and under reject issuance from it will be refused.

The warnings are plain text for a person to read. Don't parse them.

The FleetOS Manager's own FleetService.IssueLeaf response carries only cert_der today; it doesn't pass the node's warnings through.

Validity policy​

CertificateProfile.validity_policy (field 11) decides what a node does when a profile's validity_days would run past the issuing CA's own notAfter. A certificate can never outlive its issuer, so the node has to either shorten it or refuse it.

ValueBehaviour
cap (or empty)The default. The node shortens the certificate so it ends when the issuer ends, issues it, and reports the cap in the response warnings.
rejectThe node refuses to issue with FAILED_PRECONDITION, before it loads the CA key. The error names the profile, the requested end date and the issuer's notAfter.

Any other value fails config validation. The policy applies to both IssueLeaf and SignSubordinateCSR.

Profiles live in pki.profiles[] in the node's machine config; see Machine config schema. The FleetOS Manager's catalog (ListProfiles, CreateProfile, UpdateProfile, ApplyProfileToNode) uses the same cryptos.v1.CertificateProfile message, so validity_policy travels with a profile when the manager pushes it to a node.

A catalog profile also carries a manager-only requestable flag, off by default, set with SetProfileRequestable. It isn't part of cryptos.v1.CertificateProfile and never reaches a node; it only decides whether certificate requests can name the profile. ListRequestableProfiles lists the profiles currently marked requestable.

MCP agent keys​

The FleetOS Manager serves an MCP endpoint so AI agents can work with the fleet. An agent authenticates with an MCP key: a bearer key bound to one operator's client certificate. MCP clients that can run the OAuth login get a key from that flow; CreateMcpKey mints one for clients that can't. Both produce the same kind of key.

A key is identity only. On every request its effective level is the lower of the bound certificate's live level and the key's level_ceiling, and the key stops working as soon as that certificate is revoked or expires.

Access rules​

  • Operator certificate only. All three RPCs need an operator client certificate. A call that arrives with an MCP key is refused with PERMISSION_DENIED: a key can never mint, list or revoke keys.
  • ListMcpKeys returns the keys bound to the caller's certificate. Setting all lists every operator's keys and is admin-only; a non-admin who sets it is refused.
  • RevokeMcpKey lets an operator revoke the keys bound to their own certificate, and an admin revoke any key. It is idempotent: revoking a key that is already revoked returns it unchanged and writes no second audit entry. The revocation takes effect on the key's next request.
  • CreateMcpKey binds the new key to the caller's certificate serial. level_ceiling may not exceed the caller's own level.
  • CreateMcpKey and RevokeMcpKey are audited. The create entry names the key id, label and ceiling, never the key.

ListMcpKeys​

Request fieldTypeMeaning
all (1)boollist every operator's keys instead of only the caller's; admin only
Response fieldTypeMeaning
items (1)repeated McpKeythe keys, newest first, revoked ones included

The listing never carries a key or its hash.

RevokeMcpKey​

Request fieldTypeMeaning
id (1)stringthe McpKey.id to revoke
Response fieldTypeMeaning
mcp_key (1)McpKeythe key after revocation, with revoked_at set

An unknown id returns NOT_FOUND.

CreateMcpKey​

Request fieldTypeMeaning
label (1)stringfree-text name for the key, shown in listings
level_ceiling (2)stringviewer, operator or admin; empty means no ceiling below the operator's own level
Response fieldTypeMeaning
plaintext_key (1)stringthe bearer key: fos_mcp_ followed by base64url of 32 random bytes
mcp_key (2)McpKeythe stored metadata for the new key
🔑 Shown once

plaintext_key is returned in this response only. The manager stores just its hash, so it can't show the key again. A lost key is revoked and a new one minted; it is never recovered. Don't log it.

A ceiling that isn't a known level, or that is above the caller's level, returns INVALID_ARGUMENT. When the manager's MCP endpoint is disabled, CreateMcpKey returns FAILED_PRECONDITION.

McpKey​

The metadata of one key. It never carries the key or its hash. Timestamps are RFC 3339 strings; an unset one is empty.

FieldTypeMeaning
id (1)stringstable identifier, used by RevokeMcpKey and the audit key_id
label (2)stringthe operator's name for the key
client_name (3)stringthe name the MCP client registered with during the OAuth login; empty for a key from CreateMcpKey
operator_cn (4)stringsubject CN of the operator certificate the key is bound to
operator_serial (5)stringhex serial of that certificate
level_ceiling (6)stringviewer, operator or admin; empty means no ceiling below the operator's own level
created_at (7)stringwhen the key was minted
last_used_at (8)stringwhen the key last authenticated a request; empty if never
revoked_at (9)stringwhen the key was revoked; empty while it is active

Step-up approvals​

Some MCP tool calls are too risky for an agent to run on its own: revocation, profile and adapter changes, and issuance from a CA profile or the root node. When an agent makes one of these calls, the manager doesn't run it. It raises an approval instead and returns its id to the agent. A person then decides the approval with their operator certificate, and the agent calls the tool again, with the same arguments plus the approval_id, to run it.

An approval covers one exact request: the tool, the request_digest of its arguments and the MCP key that raised it. It runs at most once, and a pending or approved approval that isn't used lapses 15 minutes after it was raised.

Access rules​

  • Operator certificate only. Both RPCs need an operator client certificate. A call that arrives with an MCP key is refused with PERMISSION_DENIED: an agent can never list or decide approvals.
  • ListApprovals is open to any operator certificate.
  • DecideApproval needs the deciding certificate's level to be at least the approval's required_level; a lower level is refused with PERMISSION_DENIED and the refusal is audited. The operator whose key raised the request may decide it themselves, because the agent holds only the key.
  • When the manager's MCP endpoint is disabled, both RPCs return FAILED_PRECONDITION.

ListApprovals​

Request fieldTypeMeaning
status (1)stringkeep only approvals in this state: pending, approved, denied, expired or used; all or empty lists all
Response fieldTypeMeaning
items (1)repeated Approvalthe approvals, newest first

Any other status value returns INVALID_ARGUMENT.

DecideApproval​

✋ You are authorizing the agent

Approving lets the agent run that exact call once, with your approval on its audit entry. Revoking a certificate can't be undone, and issuing from a CA profile or the root node creates a CA. Read the approval's tool and summary, and check requested_by_cn and key_id name a key you expect, before you approve. If anything is unexpected, deny it and revoke the key with RevokeMcpKey.

Request fieldTypeMeaning
id (1)stringthe Approval.id to decide
approve (2)booltrue approves the request, false denies it
Response fieldTypeMeaning
approval (1)Approvalthe approval after the decision, with status and the decided_by fields set

An unknown id returns NOT_FOUND. An approval that is no longer pending (already decided, expired or used) returns FAILED_PRECONDITION.

Approval​

Timestamps are RFC 3339 strings; an unset one is empty.

FieldTypeMeaning
id (1)stringstable identifier, used by DecideApproval and the audit approval_id
tool (2)stringthe MCP tool the request would run
summary (3)stringone line for a person to read, describing the action
request_digest (4)stringlowercase hex SHA-256 of the canonical request, so the approval covers exactly the request that raised it
requested_by_cn (5)stringsubject CN of the operator certificate behind the requesting MCP key
requested_by_serial (6)stringhex serial of that certificate
key_id (7)stringthe McpKey.id that made the request
required_level (8)stringthe lowest level that may decide it: viewer, operator or admin
created_at (9)stringwhen the approval was raised
expires_at (10)stringwhen a pending or approved approval lapses to expired
status (11)stringpending, approved, denied, expired or used
decided_by_cn (12)stringsubject CN of the deciding operator certificate; empty until decided
decided_by_serial (13)stringhex serial of that certificate; empty until decided
decided_at (14)stringwhen it was decided; empty until decided
kind (15)stringstep_up or certificate_request; empty is treated as step_up, the only kind before this field existed

Certificate requests​

No web page yet

CreateCertificateRequest, ListCertificateRequests, GetCertificateRequestByID and CancelCertificateRequest are reachable over the API today. The FleetOS Manager web UI doesn't have a Request a certificate or My requests page yet; that ships in a later wave, on the same screens as Make a credential request.

Any signed-in user, not only an operator or admin, can ask for a certificate under a profile an admin has marked requestable. The flow reuses the step-up approvals queue: filing a request opens an Approval of kind certificate_request, and an operator or admin other than the requester has to decide it before anything is signed.

pending --(approved)--> approved --(the node signs)--> issued
| \--(the node refuses)--> failed
|--(denied)--> denied
|--(cancelled)--> cancelled
\--(30 days pass, still pending)--> expired

Access rules​

  • Viewer level and above may call CreateCertificateRequest. There is no operator-or-admin floor on filing a request, only on deciding it.
  • ListCertificateRequests returns only the caller's own requests below operator level; mine_only is forced on. An operator or admin sees every request, and may still set mine_only to see just their own.
  • GetCertificateRequestByID is readable by the request's own requester, or by an operator and above.
  • CancelCertificateRequest ends a pending request. The requester may cancel their own; anyone else needs admin level.
  • Approving or denying a certificate-request approval goes through the same DecideApproval as a step-up approval, with one more rule: the requester can never decide their own request, even as an operator or admin. DecideApproval refuses a certificate-request approval whose requested_by_cn matches the decider with PERMISSION_DENIED.

CreateCertificateRequest​

Request fieldTypeMeaning
profile_name (1)stringa catalog profile marked requestable
csr_der (2)bytesa PKCS#10 CSR, generated in the browser or pasted
note (3)stringfree text for the approver, for example what the certificate is for
Response fieldTypeMeaning
request (1)CertificateRequestthe new request, pending

The manager checks the CSR against the profile before storing anything: an unknown or non-requestable profile returns NOT_FOUND or FAILED_PRECONDITION; a CSR that doesn't parse, doesn't verify or carries a key the node won't sign (anything but ECDSA P-384 or RSA of 3072 bits or more) returns INVALID_ARGUMENT with CSR_REJECTED; a CSR whose subject or SANs don't fit the profile (for example no common name when the profile fixes none, or SANs the profile doesn't allow a request to supply) returns INVALID_ARGUMENT naming the mismatched field.

ListCertificateRequests​

Request fieldTypeMeaning
state (1)stringkeep only requests in this state; empty lists all
mine_only (2)boolkeep only the caller's own requests
Response fieldTypeMeaning
items (1)repeated CertificateRequestthe requests, newest first

GetCertificateRequestByID​

Request fieldTypeMeaning
id (1)stringthe CertificateRequest.id
Response fieldTypeMeaning
request (1)CertificateRequestthe request

An unknown id returns NOT_FOUND.

CancelCertificateRequest​

Request fieldTypeMeaning
id (1)stringthe CertificateRequest.id
Response fieldTypeMeaning
request (1)CertificateRequestthe request, cancelled

A request that is no longer pending returns FAILED_PRECONDITION.

CertificateRequest​

Timestamps are RFC 3339 strings; an unset one is empty.

FieldTypeMeaning
id (1)stringstable identifier
profile_name (2)stringthe requested catalog profile
csr_pem (3)stringthe submitted CSR, PEM encoded
note (4)stringthe requester's note
requested_by_cn (5)stringsubject CN of the requester's operator certificate
requested_by_serial (6)stringhex serial of that certificate
state (7)stringpending, approved, issued, denied, cancelled, expired or failed
approval_id (8)stringthe Approval deciding this request
certificate_pem (9)stringthe issued certificate, PEM encoded; empty until state is issued
failure_reason (10)stringthe issuing node's refusal reason; empty unless state is failed
created_at (11)stringwhen the request was filed
expires_at (12)stringwhen a pending request lapses to expired, 30 days after created_at
decided_at (13)stringwhen the request's approval was decided; empty until decided
issued_at (14)stringwhen the certificate was issued; empty until issued

Node audit log​

Two NodeService calls read the node's hash-chained audit log (see Audit log format). They are authorized like ListIssued: the local socket, or the bootstrap admin certificate over mTLS; any other certificate gets PERMISSION_DENIED. A node in maintenance mode answers FAILED_PRECONDITION. Both calls are recorded in the log like any other.

ListAuditEvents​

Returns entries oldest first (ascending seq), a page at a time. Every filter is optional, and they combine.

Request fieldTypeMeaning
page_size (1)int32most entries to return; 0 means 100, larger values are capped at 1000, negative is INVALID_ARGUMENT
page_token (2)stringthe previous response's next_page_token, sent with the same filters; empty starts at the oldest match
from_time (3)stringRFC 3339; keeps entries whose ts is at or after it
to_time (4)stringRFC 3339; keeps entries whose ts is before it
event_type (5)stringthe full rpc_method (/cryptos.node.v1.NodeService/RevokeCertificate) or its method name alone (RevokeCertificate)
actor (6)stringkeeps entries whose actor_subject contains it; case-sensitive

A time that isn't RFC 3339, a to_time earlier than from_time, and a page_token the node didn't issue or that was issued for other filters are INVALID_ARGUMENT.

The response holds entries and a next_page_token that is empty on the last page. Each AuditLogEntry carries:

FieldTypeMeaning
event (1)AuditEventthe entry exactly as stored, signed and chained
entry_sha256 (2)bytesSHA-256 of the entry's bytes on disk: the value the next entry's prev_entry_sha256 holds. Re-encoding event doesn't reproduce those bytes, so use this value
target (3)stringwhat the call acted on when the entry records it, such as the serial on RevokeCertificate or the asserted names on IssueLeaf; empty otherwise
summary (4)stringone line for a person to read, such as revoked a certificate: 4f1a09c2

target and summary are worked out when the entry is read and aren't part of the chain. Don't parse summary.

VerifyAuditChain​

Takes no fields and walks the whole stored log. A broken chain is a result, not an error; the call fails only when the log can't be read.

FieldTypeMeaning
entry_count (1)uint64entries in the log, counted past a break
intact (2)boolevery entry verified
first_broken_sequence (3)uint64the seq the first failing entry holds, or the one expected at its place when the line can't be read; 0 when intact
reason (4)stringthe file, line and failure (a signature mismatch, a sequence gap, a prev_entry_sha256 mismatch, or a line that is malformed or does not parse); empty when intact

FleetOS Manager audit events​

FleetService.ListAudit returns cryptos.fleet.v1.AuditEvent entries. This is the manager's log, separate from the hash-chained cryptos.node.v1.AuditEvent log each node keeps (see Audit log format).

FieldTypeMeaning
id (1)stringentry id
at (2)stringwhen it happened
kind (3)stringwhat happened, e.g. issued, revoked, config-applied, profile-updated, node-renamed, mcp-key-created, mcp-key-first-used, mcp-key-rejected, mcp-key-revoked, approval-requested, approval-approved, approval-denied, approval-decide-refused, approval-used
summary (4)stringone line for a person to read
target_kind (5)stringapproval, cert, enrollment, mcp-key, node, profile or protocol
target_path (6)stringthe object acted on
node_id (17)stringthe stable id (NodeSummary.id) of the node the entry concerns; empty when it concerns no node. For entries recorded before node ids, the manager fills it from the name the node held at the time

Actor fields​

Fields 7 onward say who acted and through which surface. They are empty on entries recorded before the manager captured an actor.

FieldTypeMeaning
actor_kind (7)stringhow the actor authenticated: cert for an operator client certificate, mcp_key for an MCP key
actor_cn (8)stringsubject CN of the operator certificate that acted, or that the MCP key is bound to
actor_serial (9)stringhex serial of that operator certificate
key_id (10)stringthe McpKey.id when actor_kind is mcp_key; empty otherwise
via (11)stringthe surface the action came through: web, mcp or api
tool (12)stringthe MCP tool name when via is mcp; empty otherwise
request_digest (13)stringlowercase hex SHA-256 of the canonical request, so an entry can be matched to the exact request without storing its body
outcome (14)stringok, denied, pending or error
approval_id (15)stringthe Approval.id on entries in an approval's lifecycle and on the action an approval authorized; empty otherwise
approver_serial (16)stringhex serial of the operator certificate that decided the approval; set on approval-approved, approval-denied and approval-used entries and on the approved action's own entry; empty otherwise
🧭 Following an approved action

An agent's approved action leaves a trail you can join on approval_id: approval-requested (outcome pending) when the agent asked, approval-approved or approval-denied when a person decided, then approval-used and the action's own entry when the agent ran it. See Step-up approvals.

Because an MCP key is always bound to a certificate, an action an agent takes is still attributed to a person: actor_cn and actor_serial name the operator, and key_id names the key they gave the agent.