Attach a certification to an agent
Verifies an AgentCertificationV1 names this agent and indexes it for builtin.agent_certification. The agent holds one current certification per program version, configuration and operating profile: a new valid certification becomes the current one for the program version, configuration and profile it names, superseding the previous one for them, and one in any other state fills that slot only when it is empty. Certifications of other configurations stay current beside it, so a configuration can be certified before the agent is rolled out to it, and an agent rolled back is admitted under the certification of the configuration it runs again; revoking a certification is how to stop honoring it. The signed certification can be sent as credential, as governance's certification record carries it: its content ID (JCS, BLAKE3) must equal source_cid (400 certification_source_mismatch otherwise), and nothing is read from Integrity, so the caller needs no Integrity access. Without it, the certification is read from source_cid, where Integrity stored it at issuance, with the caller's bearer token, so Integrity must let the caller read it (403 integrity_forbidden otherwise). Either way its proof, issuer and subject are verified the same way. The agent's current operating profile is compared with the one the certification names; when it cannot be read yet, the certification is still attached and its operating-profile check reports unknown with an error code. The certification comes back with its configuration standing, as the list gives it. A new certification that names an operating profile must name one of the agent's profile versions, which the gateway reads it from (400 certification_profile_unknown otherwise); one already indexed, or revoked, is attached again whatever it names, so a revocation is always recorded. Attaching an indexed certification again re-checks it, and refresh_status re-resolves its revocation status at once rather than waiting for the cached status to age out, so a revocation takes effect straight away. Registry admins and users bound to the agent as operator may attach any certification of it. Anyone else may attach one they authorized: a certification of this agent from a trusted issuer whose authorizedBy is the caller's DID, as Auth Service holds it, whether or not it has since expired or been revoked. Until the certification shows that, such a caller is answered as anyone without access is, and the registry is not read. An API key acts as its holder here with full access, or scoped to manage_programs or review_agent_assessments, as Governance Service attaches a certification on a program administrator's or reviewer's key; either way it is bound to no project.
https://<host>/api/v1/agents/{agentId}/certificationscurl --request POST \
--url 'https://<host>/api/v1/agents/{agentId}/certifications' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"credential":{},"credential_id":"string","refresh_status":true,"source_cid":"string"}'const response = await fetch("https://<host>/api/v1/agents/{agentId}/certifications", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
})
});
const data = await response.json();interface AgentCertificationResponse {
correlation_id?: string;
item?: Record<string, unknown>;
}
const response = await fetch("https://<host>/api/v1/agents/{agentId}/certifications", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
})
});
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
const data: AgentCertificationResponse = await response.json();import requests
response = requests.post(
"https://<host>/api/v1/agents/{agentId}/certifications",
headers={
"Authorization": "Bearer <token>",
"Content-Type": "application/json",
},
json={
"credential": {},
"credential_id": "string",
"refresh_status": True,
"source_cid": "string",
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}`)
req, err := http.NewRequest(http.MethodPost, "https://<host>/api/v1/agents/{agentId}/certifications", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.Status, string(data))
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://<host>/api/v1/agents/{agentId}/certifications"))
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}
"""))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://<host>/api/v1/agents/{agentId}/certifications");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "<token>");
request.Content = new StringContent("""
{
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");<?php
$ch = curl_init('https://<host>/api/v1/agents/{agentId}/certifications');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer <token>',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}
JSON,
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status . " " . $response . PHP_EOL;require "net/http"
uri = URI("https://<host>/api/v1/agents/{agentId}/certifications")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer <token>"
request["Content-Type"] = "application/json"
request.body = <<~'JSON'
{
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}
JSON
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts "#{response.code} #{response.body}"// Cargo.toml dependencies: reqwest (features "json"), serde_json, tokio (features "full")
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::Client::new()
.post("https://<host>/api/v1/agents/{agentId}/certifications")
.header("Authorization", "Bearer <token>")
.json(&json!({
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}))
.send()
.await?;
println!("{} {}", response.status(), response.text().await?);
Ok(())
}Authorizations
BearerAuth· API key in header "Authorization" — Type "Bearer" followed by a space and a sign-in token: a Guardian Platform JWT, or an OIDC ID token in a standalone deployment.ApiKeyAuth· API key in header "Authorization" — Type "Bearer" followed by a space and a Guardian Platform API key. Project-scoped routes and registration tokens check the key's scopes and project binding. Per-agent routes accept a key bound to no project that has empty scopes or full access, which acts as its holder, or a scope for what the route does: submit_agent_assessments reads an agent (its detail, configurations, state history and the files a state references, definitions, tools and operating profile, as its assessments rest on), view_agent_certifications reads its certifications, and manage_programs or review_agent_assessments attach a certification. A state's raw snapshot and findings, its audit exchanges and plugin events, and saving its operating profile take full access. A scope counts only while the key's holder still holds that permission in the key's organization, which auth-service reports as keyHeldScopes. A key never acts as an admin, so its holder's binding to the agent still decides.
Parameters
Path parameters
| Name | Type | Description |
|---|---|---|
agentIdrequired | string | Agent ID |
Request bodyrequired
Where Integrity holds the certification, and optionally the certification itself
credentialobjectThe signed certification, whose content ID must equal source_cid
credential_idstringrefresh_statusbooleansource_cidstring
{
"credential": {},
"credential_id": "string",
"refresh_status": true,
"source_cid": "string"
}Responses
200OK
correlation_idstringiteminternal_controlhttp.agentCertificationViewShow internal_controlhttp.agentCertificationView
agent_idstringauthorized_by_didstringconfiguration_cidstringconfiguration_standingobjectConfigurationStanding is how the agent's keys stand against what a certification the gateway can honor names: absent for one that is revoked, expired, not yet valid, invalid or untrusted, which no key stands against
Show properties
keysagentcertification.KeyStanding[]Show properties
Array of
agentcertification.KeyStandingadded_bystringWho added it; told only to an admin and the agent's operators
added_by_api_key_idstringThe API key's public ID; told only to an admin and the agent's operators
added_by_emailstringTheir email; told only to an admin and the agent's operators
added_viastring (enum)AddedVia is how the key joined the agent (registration_token, api_key or control_plane), AddedBy and AddedByEmail who added it, and AddedByAPIKeyID the API key's public ID.
One of
registration_tokenapi_keycontrol_planeconfiguration_cidstringConfigurationCID is what its newest request's state gave, as a urn:cid; ConfigurationError why it gave none.
configuration_errorstringdidstringlast_seen_atstringstateobjectOne of
certifiedcordonedunknownidleShow properties
object (enum)· one of certified, cordoned, unknown, idle
statusobjectOne of
currentpartly_drifteddriftedidleShow properties
object (enum)· one of current, partly_drifted, drifted, idle
credential_idstringcredential_statusagentcertification.CredentialStatusMetadata[]Show properties
Array of
agentcertification.CredentialStatusMetadataidstringrawobjectShow properties
Map of
anystatus_list_credentialstringstatus_list_indexstringstatus_purposestringtypestring
credential_status_checked_atstringcurrentbooleanissuer_didstringobserved_atstringoperating_profile_checkagentcertification.OperatingProfileCheckShow agentcertification.OperatingProfileCheck
attempted_atstringAttemptedAt is the latest check, decided or not, and ErrorCode why it could not decide; empty when it did.
checked_atstringCheckedAt is when Status was last decided. A check that cannot tell leaves it, and Status, as they were.
current_operating_profile_cidstringCurrentOperatingProfileCID is the profile the agent held when Status was last decided, as a urn:cid; empty when it had none.
error_codestringstatusagentcertification.OperatingProfileStatusOne of
matchmismatchunknown
operating_profile_cidstringprogram_cidstringsource_refsagentcertification.SourceRefsShow agentcertification.SourceRefs
credential_idstringsource_cidstring
status_resolutionagentcertification.StatusResolutionShow agentcertification.StatusResolution
checked_atstringmetadataobjectShow properties
Map of
anyrevokedboolean
valid_fromstringvalid_untilstringvalidation_errorsagentcertification.ValidationError[]Show properties
Array of
agentcertification.ValidationErrorcodestringfieldstringmessagestring
validation_statusagentcertification.ValidationStatusOne of
validexpirednot_yet_validrevokedinvaliduntrusted_issuer
{
"correlation_id": "string",
"item": {
"agent_id": "string",
"authorized_by_did": "string",
"configuration_cid": "string",
"configuration_standing": {
"keys": [
{
"added_by": "string",
"added_by_api_key_id": "string",
"added_by_email": "string",
"added_via": "registration_token",
"configuration_cid": "string",
"configuration_error": "string",
"did": "string",
"last_seen_at": "string",
"state": "certified"
}
],
"status": "current"
},
"credential_id": "string",
"credential_status": [
{
"id": "string",
"raw": {
"key": null
},
"status_list_credential": "string",
"status_list_index": "string",
"status_purpose": "string",
"type": "string"
}
],
"credential_status_checked_at": "string",
"current": true,
"issuer_did": "string",
"observed_at": "string",
"operating_profile_check": {
"attempted_at": "string",
"checked_at": "string",
"current_operating_profile_cid": "string",
"error_code": "string",
"status": "match"
},
"operating_profile_cid": "string",
"program_cid": "string",
"source_refs": {
"credential_id": "string",
"source_cid": "string"
},
"status_resolution": {
"checked_at": "string",
"metadata": {
"key": null
},
"revoked": true
},
"valid_from": "string",
"valid_until": "string",
"validation_errors": [
{
"code": "string",
"field": "string",
"message": "string"
}
],
"validation_status": "valid"
}
}400missing_certification_source, certification_source_mismatch (credential is not the document at source_cid), agent_not_certifiable, credential_verification_failed, malformed_credential, credential_empty, certification_invalid, or certification_profile_unknown (the operating profile it names is not one of the agent's profile versions, which the gateway reads it from)
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}401Unauthorized
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}403A viewer binding attaching a certification they did not authorize, an API key bound to a project or narrowed by scopes that leave out manage_programs and review_agent_assessments, or integrity_forbidden: Integrity would not let the caller read the certification
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}404agent_not_found (also to a caller without access attaching a certification they did not authorize), or certification_not_found in Integrity
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}409certification_agent_mismatch: the certification names another agent
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}413Request Entity Too Large
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}500Internal Server Error
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}502Integrity answered with something that is not a credential
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}503integrity_service_unavailable, auth_service_unavailable (the caller's DID could not be read), or the certification store is not configured
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}