EQTY LabDocs

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.

posthttps://<host>/api/v1/agents/{agentId}/certifications
bash
curl --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"}'
javascript
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();
typescript
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();
python
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())
go
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))
}
java
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());
    }
}
csharp
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
<?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;
ruby
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}"
rust
// 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

NameTypeDescription
agentId
required
string

Agent ID

Request bodyrequired

Where Integrity holds the certification, and optionally the certification itself

application/json
  • credentialobject

    The signed certification, whose content ID must equal source_cid

  • credential_idstring
  • refresh_statusboolean
  • source_cidstring
Examplejson
{
  "credential": {},
  "credential_id": "string",
  "refresh_status": true,
  "source_cid": "string"
}

Responses

200OK

application/json
  • correlation_idstring
  • iteminternal_controlhttp.agentCertificationView
    Show internal_controlhttp.agentCertificationView
    • agent_idstring
    • authorized_by_didstring
    • configuration_cidstring
    • configuration_standingobject

      ConfigurationStanding 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.KeyStanding

        • added_bystring

          Who added it; told only to an admin and the agent's operators

        • added_by_api_key_idstring

          The API key's public ID; told only to an admin and the agent's operators

        • added_by_emailstring

          Their 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 ofregistration_tokenapi_keycontrol_plane

        • configuration_cidstring

          ConfigurationCID is what its newest request's state gave, as a urn:cid; ConfigurationError why it gave none.

        • configuration_errorstring
        • didstring
        • last_seen_atstring
        • stateobject

          One ofcertifiedcordonedunknownidle

          Show properties

          object (enum) · one of certified, cordoned, unknown, idle

      • statusobject

        One ofcurrentpartly_drifteddriftedidle

        Show properties

        object (enum) · one of current, partly_drifted, drifted, idle

    • credential_idstring
    • credential_statusagentcertification.CredentialStatusMetadata[]
      Show properties

      Array of agentcertification.CredentialStatusMetadata

      • idstring
      • rawobject
        Show properties

        Map of any

      • status_list_credentialstring
      • status_list_indexstring
      • status_purposestring
      • typestring
    • credential_status_checked_atstring
    • currentboolean
    • issuer_didstring
    • observed_atstring
    • operating_profile_checkagentcertification.OperatingProfileCheck
      Show agentcertification.OperatingProfileCheck
      • attempted_atstring

        AttemptedAt is the latest check, decided or not, and ErrorCode why it could not decide; empty when it did.

      • checked_atstring

        CheckedAt is when Status was last decided. A check that cannot tell leaves it, and Status, as they were.

      • current_operating_profile_cidstring

        CurrentOperatingProfileCID is the profile the agent held when Status was last decided, as a urn:cid; empty when it had none.

      • error_codestring
      • statusagentcertification.OperatingProfileStatus

        One ofmatchmismatchunknown

    • operating_profile_cidstring
    • program_cidstring
    • source_refsagentcertification.SourceRefs
      Show agentcertification.SourceRefs
      • credential_idstring
      • source_cidstring
    • status_resolutionagentcertification.StatusResolution
      Show agentcertification.StatusResolution
      • checked_atstring
      • metadataobject
        Show properties

        Map of any

      • revokedboolean
    • valid_fromstring
    • valid_untilstring
    • validation_errorsagentcertification.ValidationError[]
      Show properties

      Array of agentcertification.ValidationError

      • codestring
      • fieldstring
      • messagestring
    • validation_statusagentcertification.ValidationStatus

      One ofvalidexpirednot_yet_validrevokedinvaliduntrusted_issuer

Examplejson
{
  "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)

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "code": "string",
  "correlation_id": "string",
  "message": "string"
}

401Unauthorized

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "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

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "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

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "code": "string",
  "correlation_id": "string",
  "message": "string"
}

409certification_agent_mismatch: the certification names another agent

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "code": "string",
  "correlation_id": "string",
  "message": "string"
}

413Request Entity Too Large

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "code": "string",
  "correlation_id": "string",
  "message": "string"
}

500Internal Server Error

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "code": "string",
  "correlation_id": "string",
  "message": "string"
}

502Integrity answered with something that is not a credential

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "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

application/json
  • codestring
  • correlation_idstring
  • messagestring
Examplejson
{
  "code": "string",
  "correlation_id": "string",
  "message": "string"
}