Pin an assessment's configuration
Pin the agent's configuration to an assessment's draft: control-plane derives it, with the caller's token, from one observed state in the agent's history, or the latest when stateCid is omitted. With configurationCid instead, it is derived from the newest state, from any of the agent's active keys, that gave that configuration, so the pin is of the configuration rather than of one of the many states Viper signs for it: browse them with control-plane's configurations and preview each with configuration-snapshot?configuration_cid=. At submission the configuration is read from whichever state witnesses it then, so the agent re-signing its state or a key being rotated never asks for a new pin; a configuration pinned before the gateway recorded configurations keeps the state it was pinned from. The snapshot must capture every field the program's scope requires. The configuration may be pinned again at any time; a submission keeps the configuration it froze, and the next draft differs from it. Only the member who started the assessment may pin; an integration's API key bound to no project acts as its owner with full access or the submit_agent_assessments scope
https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configurationcurl --request PUT \
--url 'https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"configurationCid":"string","stateCid":"string"}'const response = await fetch("https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration", {
method: "PUT",
headers: {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"configurationCid": "string",
"stateCid": "string"
})
});
const data = await response.json();interface Assessment {
agentDid?: string;
agentName?: string;
agentUrn?: string;
canCertify?: boolean;
canEdit?: boolean;
canReview?: boolean;
configuration?: Record<string, unknown>;
configurationPinnedAt?: string;
configurationPinnedByUserId?: string;
createdAt?: string;
createdBy?: string;
createdByDid?: string;
createdByUserId?: string;
draftVersion?: number;
hasUnsubmittedChanges?: boolean;
latestSubmission?: Record<string, unknown>;
observation?: Record<string, unknown>;
organizationId?: number;
profile?: Record<string, unknown>;
profilePinnedAt?: string;
profilePinnedByUserId?: string;
programManifestCid?: string;
programSlug?: string;
programUuid?: string;
status?: "draft" | "in_review" | "passed" | "failed";
uuid?: string;
}
const response = await fetch("https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration", {
method: "PUT",
headers: {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"configurationCid": "string",
"stateCid": "string"
})
});
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
const data: Assessment = await response.json();import requests
response = requests.put(
"https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration",
headers={
"Authorization": "Bearer <token>",
"Content-Type": "application/json",
},
json={
"configurationCid": "string",
"stateCid": "string",
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"configurationCid": "string",
"stateCid": "string"
}`)
req, err := http.NewRequest(http.MethodPut, "https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration", 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://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration"))
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.method("PUT", HttpRequest.BodyPublishers.ofString("""
{
"configurationCid": "string",
"stateCid": "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.Put, "https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "<token>");
request.Content = new StringContent("""
{
"configurationCid": "string",
"stateCid": "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://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer <token>',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"configurationCid": "string",
"stateCid": "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://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration")
request = Net::HTTP::Put.new(uri)
request["Authorization"] = "Bearer <token>"
request["Content-Type"] = "application/json"
request.body = <<~'JSON'
{
"configurationCid": "string",
"stateCid": "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()
.put("https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/configuration")
.header("Authorization", "Bearer <token>")
.json(&json!({
"configurationCid": "string",
"stateCid": "string"
}))
.send()
.await?;
println!("{} {}", response.status(), response.text().await?);
Ok(())
}Authorizations
BearerAuth· API key in header "Authorization" — Type "Bearer" followed by a space and JWT token.
Parameters
Path parameters
| Name | Type | Description |
|---|---|---|
orgIdrequired | string | Organization ID or UUID (urn:uuid optional) |
assessmentIdrequired | string | Assessment UUID (urn:uuid optional) |
Request body
The observed state to pin
configurationCidstringA configuration one of the agent's states gave, bare or urn:cid, as the registry's configurations list them; pins it from the newest state any active key sent that gave it. Not with stateCid
stateCidstringA state from the agent's history; the latest when omitted
{
"configurationCid": "string",
"stateCid": "string"
}Responses
200Assessment with its pinned configuration
agentDidstringread-onlyThe agent key that signed the pinned state; absent until a configuration is pinned
agentNamestringread-onlyThe name the agent registry gave the agent, read when the assessment was started and on every submission; a label for those who cannot see the agent, never evidence. Absent when the registry gave none
agentUrnstringread-onlyThe agent's control-plane registry ID, whose UUID is the root of the assessment's integrity graph
canCertifybooleanread-onlyWhether the caller may ask for the certification its passed newest submission earned: nothing is recorded for it yet, certified or revoked, they neither started the assessment nor made the submission, and they administer programs, or still review its program and made one of the reviews it rests on. False until the assessment passes. Only a single assessment's response works it out; lists leave it false
canEditbooleanread-onlyWhether the caller may change and submit the draft: they started the assessment
canReviewbooleanread-onlyWhether the caller may review its submissions: they review its program and did not start it
configurationobjectread-onlyPinned to the draft; a submission keeps its own copy, which a later pin does not change
Show properties
cidstringurn:cid of Document, registered with the Integrity Service
documentobjectThe AgentConfigurationV1 document, including its coverage. Returned as stored, so its bytes are not canonical: cid is over its JCS (RFC 8785) form, which a verifier recomputes before hashing
schemaVersionstring
configurationPinnedAtstring<date-time>read-onlyconfigurationPinnedByUserIdstringread-onlycreatedAtstring<date-time>read-onlycreatedBystringread-onlycreatedByDidstringread-onlycreatedByUserIdstringread-onlyWho started it, the only member who may change its draft; absent on assessments started before it was recorded
draftVersionintegerread-onlyIncreases with every change to the draft; submitting with it freezes exactly the draft the caller saw
hasUnsubmittedChangesbooleanread-onlyThe draft changed since its newest submission, or was never submitted
latestSubmissionobjectread-onlyAbsent until the first submission
Show properties
controlCountintegerThe program's controls, each of which is reviewed
draftVersionintegerThe draft version it froze
lastReviewedAtstring<date-time>numberintegerreviewedCountintegerControls with at least one review
statusobjectOne of
in_reviewpassedfailedShow properties
object (enum)· one of in_review, passed, failedsubmittedAtstring<date-time>uuidstringurn:uuid form
observationobjectread-onlyShow properties
correlationIdstringThe exchange's correlation ID
observedAtstring<date-time>When the gateway verified the exchange carrying it
requestobjectPer-request detail, evidence rather than configuration
Show properties
modelstringproviderstringsystemPromptCidstringtoolsstring[]
stateCidstringThe observed state in the gateway's store
subAgentstringThe custom agent's sub-agent that made the request, /-separated when nested; it shares the agent's configuration
viperVersionstringThe Viper version that captured it
organizationIdintegerread-onlyprofileobjectread-onlyPinned to the draft, optionally; a submission keeps its own copy, which a later pin does not change
Show properties
cidstringurn:cid of Document, registered with the Integrity Service
documentobjectThe AgentOperatingProfileV1 document, as the agent registry stores it. cid is over its JCS (RFC 8785) form
versionintegerThe agent's version number of it, as the agent registry numbers them
profilePinnedAtstring<date-time>read-onlyprofilePinnedByUserIdstringread-onlyprogramManifestCidstringread-onlyThe requirements the agent is judged against
programSlugstringread-onlyThe program the version belongs to, which its reviewers are assigned to
programUuidstringread-onlyurn:uuid form
statusobjectread-onlyOne of
draftin_reviewpassedfailedShow properties
object (enum)· one of draft, in_review, passed, faileduuidstringread-onlyurn:uuid form
{
"agentDid": "string",
"agentName": "string",
"agentUrn": "string",
"canCertify": true,
"canEdit": true,
"canReview": true,
"configuration": {
"cid": "string",
"document": {},
"schemaVersion": "string"
},
"configurationPinnedAt": "2025-01-01T00:00:00Z",
"configurationPinnedByUserId": "string",
"createdAt": "2025-01-01T00:00:00Z",
"createdBy": "string",
"createdByDid": "string",
"createdByUserId": "string",
"draftVersion": 0,
"hasUnsubmittedChanges": true,
"latestSubmission": {
"controlCount": 0,
"draftVersion": 0,
"lastReviewedAt": "2025-01-01T00:00:00Z",
"number": 0,
"reviewedCount": 0,
"status": "in_review",
"submittedAt": "2025-01-01T00:00:00Z",
"uuid": "string"
},
"observation": {
"correlationId": "string",
"observedAt": "2025-01-01T00:00:00Z",
"request": {
"model": "string",
"provider": "string",
"systemPromptCid": "string",
"tools": [
"string"
]
},
"stateCid": "string",
"subAgent": "string",
"viperVersion": "string"
},
"organizationId": 0,
"profile": {
"cid": "string",
"document": {},
"version": 0
},
"profilePinnedAt": "2025-01-01T00:00:00Z",
"profilePinnedByUserId": "string",
"programManifestCid": "string",
"programSlug": "string",
"programUuid": "string",
"status": "draft",
"uuid": "string"
}400Bad request - Invalid request, or the agent's state cannot be pinned
errorstring
{
"error": "Invalid request body"
}401Unauthorized - Authentication required
errorstring
{
"error": "Invalid request body"
}403Forbidden - Not the assessment's starter, an API key with neither full access nor submit_agent_assessments, a project-bound key, or the agent registry refuses the caller
errorstring
{
"error": "Invalid request body"
}404Assessment or agent not found
errorstring
{
"error": "Invalid request body"
}500Internal server error
errorstring
{
"error": "Invalid request body"
}503The agent registry is unavailable
errorstring
{
"error": "Invalid request body"
}