curl --request POST \
--url http://localhost:3000/api/v1/reports/{id}/finalize \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"uploadId": "<string>",
"sha256": "<string>",
"schemaVersion": "1.0.0",
"summary": {
"findingCount": 123,
"severityCounts": {
"critical": 123,
"high": 123,
"medium": 123,
"low": 123,
"info": 123
},
"verificationCounts": {
"verified": 123,
"strong_evidence": 123,
"needs_review": 123
},
"candidateCount": 123,
"duplicateCount": 123,
"fixedCount": 123
},
"durationMs": 123,
"tokenUsage": {
"count": 123,
"estimator": "<string>"
}
}
'import requests
url = "http://localhost:3000/api/v1/reports/{id}/finalize"
payload = {
"uploadId": "<string>",
"sha256": "<string>",
"schemaVersion": "1.0.0",
"summary": {
"findingCount": 123,
"severityCounts": {
"critical": 123,
"high": 123,
"medium": 123,
"low": 123,
"info": 123
},
"verificationCounts": {
"verified": 123,
"strong_evidence": 123,
"needs_review": 123
},
"candidateCount": 123,
"duplicateCount": 123,
"fixedCount": 123
},
"durationMs": 123,
"tokenUsage": {
"count": 123,
"estimator": "<string>"
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
uploadId: '<string>',
sha256: '<string>',
schemaVersion: '1.0.0',
summary: {
findingCount: 123,
severityCounts: {critical: 123, high: 123, medium: 123, low: 123, info: 123},
verificationCounts: {verified: 123, strong_evidence: 123, needs_review: 123},
candidateCount: 123,
duplicateCount: 123,
fixedCount: 123
},
durationMs: 123,
tokenUsage: {count: 123, estimator: '<string>'}
})
};
fetch('http://localhost:3000/api/v1/reports/{id}/finalize', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "3000",
CURLOPT_URL => "http://localhost:3000/api/v1/reports/{id}/finalize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'uploadId' => '<string>',
'sha256' => '<string>',
'schemaVersion' => '1.0.0',
'summary' => [
'findingCount' => 123,
'severityCounts' => [
'critical' => 123,
'high' => 123,
'medium' => 123,
'low' => 123,
'info' => 123
],
'verificationCounts' => [
'verified' => 123,
'strong_evidence' => 123,
'needs_review' => 123
],
'candidateCount' => 123,
'duplicateCount' => 123,
'fixedCount' => 123
],
'durationMs' => 123,
'tokenUsage' => [
'count' => 123,
'estimator' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "http://localhost:3000/api/v1/reports/{id}/finalize"
payload := strings.NewReader("{\n \"uploadId\": \"<string>\",\n \"sha256\": \"<string>\",\n \"schemaVersion\": \"1.0.0\",\n \"summary\": {\n \"findingCount\": 123,\n \"severityCounts\": {\n \"critical\": 123,\n \"high\": 123,\n \"medium\": 123,\n \"low\": 123,\n \"info\": 123\n },\n \"verificationCounts\": {\n \"verified\": 123,\n \"strong_evidence\": 123,\n \"needs_review\": 123\n },\n \"candidateCount\": 123,\n \"duplicateCount\": 123,\n \"fixedCount\": 123\n },\n \"durationMs\": 123,\n \"tokenUsage\": {\n \"count\": 123,\n \"estimator\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("http://localhost:3000/api/v1/reports/{id}/finalize")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"uploadId\": \"<string>\",\n \"sha256\": \"<string>\",\n \"schemaVersion\": \"1.0.0\",\n \"summary\": {\n \"findingCount\": 123,\n \"severityCounts\": {\n \"critical\": 123,\n \"high\": 123,\n \"medium\": 123,\n \"low\": 123,\n \"info\": 123\n },\n \"verificationCounts\": {\n \"verified\": 123,\n \"strong_evidence\": 123,\n \"needs_review\": 123\n },\n \"candidateCount\": 123,\n \"duplicateCount\": 123,\n \"fixedCount\": 123\n },\n \"durationMs\": 123,\n \"tokenUsage\": {\n \"count\": 123,\n \"estimator\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:3000/api/v1/reports/{id}/finalize")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"uploadId\": \"<string>\",\n \"sha256\": \"<string>\",\n \"schemaVersion\": \"1.0.0\",\n \"summary\": {\n \"findingCount\": 123,\n \"severityCounts\": {\n \"critical\": 123,\n \"high\": 123,\n \"medium\": 123,\n \"low\": 123,\n \"info\": 123\n },\n \"verificationCounts\": {\n \"verified\": 123,\n \"strong_evidence\": 123,\n \"needs_review\": 123\n },\n \"candidateCount\": 123,\n \"duplicateCount\": 123,\n \"fixedCount\": 123\n },\n \"durationMs\": 123,\n \"tokenUsage\": {\n \"count\": 123,\n \"estimator\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"reportId": "<string>",
"state": "complete",
"reportUrl": "<string>",
"expiresAt": "2023-11-07T05:31:56Z"
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}Finalize a cloud report
State becomes complete only after ownership, digest, and schema all check out, and the upload must already be in the received state. schemaVersion must be exactly 1.0.0.
The sync unit is spent here rather than at creation, so a report whose upload leg failed never charged the user. A finished report is kept even when the reservation was already reaped and there is no room to re-take it.
curl --request POST \
--url http://localhost:3000/api/v1/reports/{id}/finalize \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"uploadId": "<string>",
"sha256": "<string>",
"schemaVersion": "1.0.0",
"summary": {
"findingCount": 123,
"severityCounts": {
"critical": 123,
"high": 123,
"medium": 123,
"low": 123,
"info": 123
},
"verificationCounts": {
"verified": 123,
"strong_evidence": 123,
"needs_review": 123
},
"candidateCount": 123,
"duplicateCount": 123,
"fixedCount": 123
},
"durationMs": 123,
"tokenUsage": {
"count": 123,
"estimator": "<string>"
}
}
'import requests
url = "http://localhost:3000/api/v1/reports/{id}/finalize"
payload = {
"uploadId": "<string>",
"sha256": "<string>",
"schemaVersion": "1.0.0",
"summary": {
"findingCount": 123,
"severityCounts": {
"critical": 123,
"high": 123,
"medium": 123,
"low": 123,
"info": 123
},
"verificationCounts": {
"verified": 123,
"strong_evidence": 123,
"needs_review": 123
},
"candidateCount": 123,
"duplicateCount": 123,
"fixedCount": 123
},
"durationMs": 123,
"tokenUsage": {
"count": 123,
"estimator": "<string>"
}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
uploadId: '<string>',
sha256: '<string>',
schemaVersion: '1.0.0',
summary: {
findingCount: 123,
severityCounts: {critical: 123, high: 123, medium: 123, low: 123, info: 123},
verificationCounts: {verified: 123, strong_evidence: 123, needs_review: 123},
candidateCount: 123,
duplicateCount: 123,
fixedCount: 123
},
durationMs: 123,
tokenUsage: {count: 123, estimator: '<string>'}
})
};
fetch('http://localhost:3000/api/v1/reports/{id}/finalize', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "3000",
CURLOPT_URL => "http://localhost:3000/api/v1/reports/{id}/finalize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'uploadId' => '<string>',
'sha256' => '<string>',
'schemaVersion' => '1.0.0',
'summary' => [
'findingCount' => 123,
'severityCounts' => [
'critical' => 123,
'high' => 123,
'medium' => 123,
'low' => 123,
'info' => 123
],
'verificationCounts' => [
'verified' => 123,
'strong_evidence' => 123,
'needs_review' => 123
],
'candidateCount' => 123,
'duplicateCount' => 123,
'fixedCount' => 123
],
'durationMs' => 123,
'tokenUsage' => [
'count' => 123,
'estimator' => '<string>'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "http://localhost:3000/api/v1/reports/{id}/finalize"
payload := strings.NewReader("{\n \"uploadId\": \"<string>\",\n \"sha256\": \"<string>\",\n \"schemaVersion\": \"1.0.0\",\n \"summary\": {\n \"findingCount\": 123,\n \"severityCounts\": {\n \"critical\": 123,\n \"high\": 123,\n \"medium\": 123,\n \"low\": 123,\n \"info\": 123\n },\n \"verificationCounts\": {\n \"verified\": 123,\n \"strong_evidence\": 123,\n \"needs_review\": 123\n },\n \"candidateCount\": 123,\n \"duplicateCount\": 123,\n \"fixedCount\": 123\n },\n \"durationMs\": 123,\n \"tokenUsage\": {\n \"count\": 123,\n \"estimator\": \"<string>\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("http://localhost:3000/api/v1/reports/{id}/finalize")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"uploadId\": \"<string>\",\n \"sha256\": \"<string>\",\n \"schemaVersion\": \"1.0.0\",\n \"summary\": {\n \"findingCount\": 123,\n \"severityCounts\": {\n \"critical\": 123,\n \"high\": 123,\n \"medium\": 123,\n \"low\": 123,\n \"info\": 123\n },\n \"verificationCounts\": {\n \"verified\": 123,\n \"strong_evidence\": 123,\n \"needs_review\": 123\n },\n \"candidateCount\": 123,\n \"duplicateCount\": 123,\n \"fixedCount\": 123\n },\n \"durationMs\": 123,\n \"tokenUsage\": {\n \"count\": 123,\n \"estimator\": \"<string>\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("http://localhost:3000/api/v1/reports/{id}/finalize")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"uploadId\": \"<string>\",\n \"sha256\": \"<string>\",\n \"schemaVersion\": \"1.0.0\",\n \"summary\": {\n \"findingCount\": 123,\n \"severityCounts\": {\n \"critical\": 123,\n \"high\": 123,\n \"medium\": 123,\n \"low\": 123,\n \"info\": 123\n },\n \"verificationCounts\": {\n \"verified\": 123,\n \"strong_evidence\": 123,\n \"needs_review\": 123\n },\n \"candidateCount\": 123,\n \"duplicateCount\": 123,\n \"fixedCount\": 123\n },\n \"durationMs\": 123,\n \"tokenUsage\": {\n \"count\": 123,\n \"estimator\": \"<string>\"\n }\n}"
response = http.request(request)
puts response.read_body{
"reportId": "<string>",
"state": "complete",
"reportUrl": "<string>",
"expiresAt": "2023-11-07T05:31:56Z"
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}{
"error": {
"code": "SYNC_WEEKLY_LIMIT_REACHED",
"message": "Cloud report limit reached. This scan stays local.",
"requestId": "8f14e45f-ceea-467a-9f8b-2b6b2b6a1f2c",
"details": {
"weekly": {
"used": 15,
"limit": 15,
"resetsAt": "2026-08-24T06:00:00.000Z"
}
}
}
}Authorizations
The CLI's device credential, obtained once from the device-link exchange and stored in the OS keystore. Sent as Authorization: Bearer myrq_dev_…. The server never trusts a CLI-supplied user id — identity comes from the credential row.
Path Parameters
The cloud report id.
Body
^[a-f0-9]{64}$"1.0.0"Show child attributes
Show child attributes
Provenance is mandatory. An exact count is never fabricated.
Show child attributes
Show child attributes
low, high, xhigh, ultra The host agent and model are often only known after the scan starts, so the final values replace whatever the shell was created with.
Show child attributes
Show child attributes