Submit an assessment for review
Freeze an assessment's draft into its next submission for the program's reviewers to review control by control: the pinned configuration and any pinned operating profile, the draft's declarations and its not-applicable claims, registered with the Integrity Service as a governance statement in the assessment's graph, signed by the caller. A pinned profile is first checked by the agent registry against what the agent was seen with in the 30 days before and the permissions in the newest state, from any of the agent's active keys, that gave the pinned configuration (the pinned state when the gateway recorded none); the submission keeps the check as profileCheck for reviewers, and that state as its witness. The submission records the pinned observation, the state and request the starter pinned, and the key that signed it, whichever key witnessed the configuration. No finding blocks submitting, but a check that cannot be made does: 503 while the registry is unavailable, 400 when it cannot use the pinned state. Every file the pinned configuration names, and the pinned request's system prompt, is then read through the pinned state from the agent registry with the caller's token, checked against its CID and frozen with the submission as its files, which whoever may read the assessment reads through the submission's file route; a file the registry says it does not hold, one larger than 5 MiB, or one it serves with other bytes than its CID names is not frozen. A pinned state the registry no longer holds fails the submission with 400, asking for the configuration to be pinned again; the registry refusing the caller (401 or 403) fails it with 403; any other file it cannot serve fails it as the profile check does, 503 while it is unavailable; file storage failing to read or store a file, or freezing taking longer than 30 seconds, fails it with 503 too. The Integrity Service then records, for every control, the evidence it is judged on (its covering declarations, its not-applicable claim and the values of the configuration fields and profile sections it judges), and the submission names each control's evidence CID. The draft must have a pinned configuration, a declaration or not-applicable claim for every control (a control that expects no evidence needs neither when it is judged only against configuration fields the pinned configuration captured and, with a profile pinned, profile sections), and changes since the newest submission, and the starter must still see the agent, which must be active. A newest submission made before controls carried evidence cannot be reviewed, so the draft it froze can be submitted again unchanged. Reviewers judge whether the evidence is enough. Submitting supersedes the newest submission, whose controls can then no longer be reviewed. Pass draftVersion to submit only the draft you reviewed. Only the member who started the assessment may submit; 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}/submissionscurl --request POST \
--url 'https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{"draftVersion":0}'const response = await fetch("https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"draftVersion": 0
})
});
const data = await response.json();interface AssessmentSubmission {
agentDid?: string;
configuration?: Record<string, unknown>;
controlCount?: number;
controls?: Record<string, unknown>[];
declarations?: Record<string, unknown>[];
document?: Record<string, unknown>;
documentCid?: string;
draftVersion?: number;
evidence?: Record<string, unknown>;
files?: Record<string, unknown>[];
notApplicable?: Record<string, unknown>[];
number?: number;
observation?: Record<string, unknown>;
pins?: Record<string, unknown>[];
profile?: Record<string, unknown>;
profileCheck?: Record<string, unknown>;
statementId?: string;
status?: "in_review" | "passed" | "failed" | "superseded";
submittedAt?: string;
submittedBy?: string;
submittedByDid?: string;
submittedByUserId?: string;
uuid?: string;
witness?: Record<string, unknown>;
}
const response = await fetch("https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions", {
method: "POST",
headers: {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
},
body: JSON.stringify({
"draftVersion": 0
})
});
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
const data: AssessmentSubmission = await response.json();import requests
response = requests.post(
"https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions",
headers={
"Authorization": "Bearer <token>",
"Content-Type": "application/json",
},
json={
"draftVersion": 0,
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{
"draftVersion": 0
}`)
req, err := http.NewRequest(http.MethodPost, "https://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions", 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}/submissions"))
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"draftVersion": 0
}
"""))
.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://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "<token>");
request.Content = new StringContent("""
{
"draftVersion": 0
}
""", 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}/submissions');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer <token>',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => <<<'JSON'
{
"draftVersion": 0
}
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}/submissions")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer <token>"
request["Content-Type"] = "application/json"
request.body = <<~'JSON'
{
"draftVersion": 0
}
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://localhost:10001/api/v1/organizations/{orgId}/assessments/{assessmentId}/submissions")
.header("Authorization", "Bearer <token>")
.json(&json!({
"draftVersion": 0
}))
.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 draft version being submitted
draftVersionintegerThe draft version the caller reviewed; any other refuses the submission with 409
{
"draftVersion": 0
}Responses
201Submission
agentDidstringThe agent key that signed the pinned state
configurationcore.AssessmentConfigurationShow core.AssessmentConfiguration
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
controlCountintegercontrolscore.SubmissionControl[]Every control reviewed, by control, with its reviews
Show properties
Array of
core.SubmissionControlcontrolCodestringoutcomeobjectThe newest review's
One of
satisfiednot_satisfiednot_applicableShow properties
object (enum)— The newest review's · one of satisfied, not_satisfied, not_applicablereviewscore.ControlReview[]Oldest first
Show properties
Array of
core.ControlReviewcontrolCodestringdocumentCidstringurn:cid of the review document the Integrity Service stored
evidenceCidstringurn:cid of the control's evidence the review judged; absent on reviews made before controls carried evidence
outcomeobjectOne of
satisfiednot_satisfiednot_applicableShow properties
object (enum)· one of satisfied, not_satisfied, not_applicablereviewedAtstring<date-time>reviewedBystringreviewedByDidstringreviewedByUserIdstringstatementstringstatementIdstringurn:cid of the governance statement it registered as
uuidstringread-onlyurn:uuid form
declarationscore.AssessmentDeclaration[]The declarations it stands on, oldest first
Show properties
Array of
core.AssessmentDeclarationattachmentscore.AssessmentAttachment[]Show properties
Array of
core.AssessmentAttachmentcidstringurn:cid of the bytes, registered with the Integrity Service
contentTypestringfileNamestringidintegerThe file, for its assessment's declaration file routes
sizeBytesinteger
controlsstring[]The control codes it covers, in the program's order
documentCidstringread-onlyurn:cid of the declaration document the Integrity Service stored
statementstringstatementIdstringread-onlyurn:cid of the governance statement it registered as
submittedAtstring<date-time>read-onlysubmittedBystringread-onlysubmittedByDidstringread-onlysubmittedByUserIdstringread-onlyuuidstringread-onlyurn:uuid form
documentobjectThe submission document, as registered
documentCidstringurn:cid of the document
draftVersionintegerThe draft version it froze
evidenceobjectControl code -> urn:cid of the evidence the Integrity Service recorded the control is judged on; empty for a submission made before controls carried evidence, which cannot be reviewed
Show properties
Map of
stringfilescore.SubmissionFile[]Every configuration file the configuration names and the observed system prompt, by path, as the agent registry served them to the submitter, sorted by path; each is read through the submission's file route by whoever may read the assessment. A file the registry did not hold, or larger than 5 MiB, was not frozen. Empty for a submission made before files were frozen
Show properties
Array of
core.SubmissionFilecidstringurn:cid of the BLAKE3 CID of its bytes, which the submission's file route reads it by
pathstringAs the configuration names it, host:/abs/path or project:relative/path, or request:systemPrompt for the observed system prompt
sizeBytesinteger
notApplicablecore.AssessmentNotApplicable[]The claims it froze, by control
Show properties
Array of
core.AssessmentNotApplicablecontrolCodestringjustificationstringsubmittedAtstring<date-time>submittedBystringsubmittedByDidstringsubmittedByUserIdstring
numberintegerNumbered from 1 within the assessment
observationobjectThe pinned observation: the state, and the request the starter pinned, as the starter saw them
Show 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
pinscore.AssessmentPin[]Each pin and unpin made after the submission before it, or since the start, up to this one, oldest first; empty where none was recorded, as before pins were kept
Show properties
Array of
core.AssessmentPincidstringurn:cid of what was pinned; absent when the profile was unpinned
documentobjectThe configuration or profile document pinned; absent when the profile was unpinned
draftVersionintegerThe draft version the pin made, which orders pins and places each in the submission that froze it
kindobjectOne of
configurationprofileShow properties
object (enum)· one of configuration, profilepinnedAtstring<date-time>pinnedBystringWho pinned it, as the assessment names its starter
pinnedByUserIdstringAbsent on a pin recorded before it was
profileVersionintegerThe agent's version number of a pinned profile
requestobjectThe request a pinned configuration was seen with, evidence its CID doesn't cover
Show properties
modelstringproviderstringsystemPromptCidstringtoolsstring[]
profileobjectAbsent when no operating profile was pinned
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
profileCheckobjectThe agent registry's check of the profile when it was submitted, as returned by its profile/check route: what the agent was seen with in the 30 days before that the profile does not declare or excludes, what it declares that went unseen, and what could not be checked. Absent with the profile
statementIdstringurn:cid of the governance statement it registered as
statusobjectOne of
in_reviewpassedfailedsupersededShow properties
object (enum)· one of in_review, passed, failed, supersededsubmittedAtstring<date-time>submittedBystringsubmittedByDidstringsubmittedByUserIdstringuuidstringread-onlyurn:uuid form
witnessobjectThe state the profile was checked against at submission; absent on a submission made before it was recorded
Show properties
observedAtstring<date-time>When the gateway verified the exchange carrying it
signerDidstringThe agent key that signed it
stateCidstringThe witnessing state in the gateway's store
{
"agentDid": "string",
"configuration": {
"cid": "string",
"document": {},
"schemaVersion": "string"
},
"controlCount": 0,
"controls": [
{
"controlCode": "string",
"outcome": "satisfied",
"reviews": [
{
"controlCode": "string",
"documentCid": "string",
"evidenceCid": "string",
"outcome": "satisfied",
"reviewedAt": "2025-01-01T00:00:00Z",
"reviewedBy": "string",
"reviewedByDid": "string",
"reviewedByUserId": "string",
"statement": "string",
"statementId": "string"
}
]
}
],
"declarations": [
{
"attachments": [
{
"cid": "string",
"contentType": "string",
"fileName": "string",
"id": 0,
"sizeBytes": 0
}
],
"controls": [
"string"
],
"statement": "string"
}
],
"document": {},
"documentCid": "string",
"draftVersion": 0,
"evidence": {
"key": "string"
},
"files": [
{
"cid": "urn:cid:bafkr4ihkgmcaa4xvn6xvtgdqi4h3hl7p5gqrlbe3rxyqwcq3t5mfaj6awi",
"path": "string",
"sizeBytes": 0
}
],
"notApplicable": [
{
"controlCode": "string",
"justification": "string",
"submittedAt": "2025-01-01T00:00:00Z",
"submittedBy": "string",
"submittedByDid": "string",
"submittedByUserId": "string"
}
],
"number": 0,
"observation": {
"correlationId": "string",
"observedAt": "2025-01-01T00:00:00Z",
"request": {
"model": "string",
"provider": "string",
"systemPromptCid": "string",
"tools": [
"string"
]
},
"stateCid": "string",
"subAgent": "string",
"viperVersion": "string"
},
"pins": [
{
"cid": "string",
"document": {},
"draftVersion": 0,
"kind": "configuration",
"pinnedAt": "2025-01-01T00:00:00Z",
"pinnedBy": "string",
"pinnedByUserId": "string",
"profileVersion": 0,
"request": {
"model": "string",
"provider": "string",
"systemPromptCid": "string",
"tools": [
"string"
]
}
}
],
"profile": {
"cid": "string",
"document": {},
"version": 0
},
"profileCheck": {},
"statementId": "string",
"status": "in_review",
"submittedAt": "2025-01-01T00:00:00Z",
"submittedBy": "string",
"submittedByDid": "string",
"submittedByUserId": "string",
"uuid": "string",
"witness": {
"observedAt": "2025-01-01T00:00:00Z",
"signerDid": "string",
"stateCid": "string"
}
}400Bad request - Invalid request, the draft is incomplete or unchanged, or the agent registry can no longer use the pinned state
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"
}409The draft changed since draftVersion, or another submission happened at once
errorstring
{
"error": "Invalid request body"
}500Internal server error
errorstring
{
"error": "Invalid request body"
}503The agent registry or file storage is unavailable
errorstring
{
"error": "Invalid request body"
}