Issue agent certification
Issues the AgentCertificationV1 an assessment submission earned, as a ComponentAttestation signed with the platform key whose subject is the agent's registry UUID. The certification belongs to the agent, not to any of its DIDs, which are added, rotated and revoked independently. Whoever asks is recorded as authorizedBy. That is a program administrator (organization-level manage_programs, from the UI or an integration's API key) or a reviewer who made one of the reviews the certification rests on, never the assessment's starter or the submission's author: auditors certify, as an audit owner issues a project compliance credential, and the review is the sign-off. Governance asks on the caller's behalf, as the reviewer whose verdict completes a passing review or when an administrator or reviewer asks again, and vouches for the request with its own token. Only governance knows who started the assessment and who reviews the program, by the user rather than a DID they may replace, so anyone asking Integrity directly is refused, a program administrator too. A reviewer is known only once the reviews named are read, so anyone else in the organization is refused after them. The platform signature goes through Auth Service on the caller's own token, so the caller's role must be one that may sign (audit owner, organization owner or project owner), and an API key needs scopes that let its owner's role sign; when Auth Service refuses, the request fails with 500. The request names the submission and the review it rests on for each control. Program reviewer assignments live in governance, which chooses each control's newest review by a current reviewer; Integrity checks what it can know: the submission is its assessment's newest, by when Integrity registered it; each review is a registered review of it, one per control, its control's newest by when Integrity registered it, finding the control satisfied, or not applicable where the program permits that, and not made by the submission's author. That is checked again under the lock the certification is recorded under, which a review of the submission is claimed under too, so a verdict registered while it was signed is never passed over; once it is recorded, the submission takes no more reviews. The certification's evidence is the statements of the submission, its declarations and those reviews, each signed by whoever made it, and its term runs from when Integrity registered the last review. The submission identifies the issuance. A repeated request, including one retried after a timeout, returns the credential the first one issued, unless the submission's author asks, and a revoked certification is never replaced from the same submission.
https://<host>/gov/v1/certification/agentcurl --request POST \
--url 'https://<host>/gov/v1/certification/agent' \
--header 'Authorization: Bearer <token>' \
--header 'X-Governance-Authorization: <X-Governance-Authorization>' \
--header 'Content-Type: application/json' \
--data '{"reviews":["urn:cid:review_one","urn:cid:review_two"],"submission":"urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"}'const response = await fetch("https://<host>/gov/v1/certification/agent", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"X-Governance-Authorization": "<X-Governance-Authorization>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
})
});
const data = await response.json();interface AgentCertificationIssueResponse {
credential: Record<string, unknown>;
credentialId: string;
sourceCid: string;
}
const response = await fetch("https://<host>/gov/v1/certification/agent", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"X-Governance-Authorization": "<X-Governance-Authorization>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
})
});
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
const data: AgentCertificationIssueResponse = await response.json();import requests
response = requests.post(
"https://<host>/gov/v1/certification/agent",
headers={
"Authorization": "Bearer <token>",
"X-Governance-Authorization": "<X-Governance-Authorization>",
"Content-Type": "application/json",
},
json={
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two",
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto",
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}`)
req, err := http.NewRequest(http.MethodPost, "https://<host>/gov/v1/certification/agent", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("X-Governance-Authorization", "<X-Governance-Authorization>")
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>/gov/v1/certification/agent"))
.header("Authorization", "Bearer <token>")
.header("X-Governance-Authorization", "<X-Governance-Authorization>")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}
"""))
.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>/gov/v1/certification/agent");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "<token>");
request.Headers.Add("X-Governance-Authorization", "<X-Governance-Authorization>");
request.Content = new StringContent("""
{
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}
""", 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>/gov/v1/certification/agent');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer <token>',
'X-Governance-Authorization: <X-Governance-Authorization>',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}
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>/gov/v1/certification/agent")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer <token>"
request["X-Governance-Authorization"] = "<X-Governance-Authorization>"
request["Content-Type"] = "application/json"
request.body = <<~'JSON'
{
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}
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>/gov/v1/certification/agent")
.header("Authorization", "Bearer <token>")
.header("X-Governance-Authorization", "<X-Governance-Authorization>")
.json(&json!({
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}))
.send()
.await?;
println!("{} {}", response.status(), response.text().await?);
Ok(())
}Authorizations
BearerAuth· HTTP bearer (JWT)
Parameters
Header parameters
| Name | Type | Description |
|---|---|---|
X-Governance-Authorizationrequired | string | Governance's own bearer token, |
Request bodyrequired
reviewsstring[]requiredThe urn:cid of the review the certification rests on for each control of the program, as
/gov/v1/agent/reviewregistered it: one per control. Governance chooses them, since it knows who reviews the program; Integrity checks each is a passing review of this submissionsubmissionstringrequiredThe urn:cid of the assessment submission to certify, as
/gov/v1/agent/submissionregistered it. It identifies the issuance, so repeating a request returns the credential it issued
{
"reviews": [
"urn:cid:review_one",
"urn:cid:review_two"
],
"submission": "urn:cid:baga6yaq6ecyxdwey6hdjb2nf7kptcgz3sspjb765rzbcpvhm5srp7ofoczsto"
}Responses
200The agent certification the submission earned, signed by the platform; repeating the request returns the same credential to anyone but the submission's author
credentialobjectrequiredcredentialIdstringrequiredsourceCidstringrequired
{
"credential": {},
"credentialId": "urn:uuid:0f8e2d4c-7b6a-4c59-8e3d-2a1b0c9d8e7f",
"sourceCid": "bafybeibwzifp5oc5mirxgcq"
}400The submission is not a urn:cid
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}401Missing or invalid bearer or X-Governance-Authorization token
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}403The request carries no X-Governance-Authorization header whose token holds organization-level certify_agents; or the caller made the submission, or holds no organization-level manage_programs and made none of the reviews the certification rests on
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}404No assessment submission of the caller's organization is registered under the CID
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}409The certification this submission earned has been revoked, so certifying the agent again takes a new submission; or the one recorded rests on reviews since superseded, so it is not returned; or a review registered after one named is still being recorded
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}422The submission cannot certify: a later submission superseded it; the reviews named are not one passing review of it per program control, each its control's newest; a review finds a control not applicable where the program does not permit that, or one was made by the submission's author; a document behind it is missing or not what its CID names; or its term would already have run out
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}500Internal error, including Auth Service refusing to sign on the caller's token
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}503Auth Service or blob storage unavailable
callstackobjectShow properties
object | nullerrorstringrequired
{
"callstack": {},
"error": "string"
}