curl --request PATCH \
--url https://api.enokilabs.ai/api/v1/findings/{finding_id}/close \
--header 'Content-Type: application/json' \
--data '
{
"explanation": "<string>"
}
'import requests
url = "https://api.enokilabs.ai/api/v1/findings/{finding_id}/close"
payload = { "explanation": "<string>" }
headers = {"Content-Type": "application/json"}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({explanation: '<string>'})
};
fetch('https://api.enokilabs.ai/api/v1/findings/{finding_id}/close', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.enokilabs.ai/api/v1/findings/{finding_id}/close",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'explanation' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"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 := "https://api.enokilabs.ai/api/v1/findings/{finding_id}/close"
payload := strings.NewReader("{\n \"explanation\": \"<string>\"\n}")
req, _ := http.NewRequest("PATCH", url, payload)
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.patch("https://api.enokilabs.ai/api/v1/findings/{finding_id}/close")
.header("Content-Type", "application/json")
.body("{\n \"explanation\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.enokilabs.ai/api/v1/findings/{finding_id}/close")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"explanation\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"finding_id": "<string>",
"lens": "security",
"category": "<string>",
"severity": "critical",
"description": "<string>",
"expected_behavior": "<string>",
"status": "<string>",
"created_at": "<string>",
"attacks": [
{
"attack_id": "<string>",
"score": 123,
"source": {
"source_type": "manual",
"assessment_id": "<string>",
"attack_index": 123,
"date": "<string>"
},
"replay_script": [
{
"step": "chat",
"message": "<string>",
"response": "<string>",
"session_id": "<string>",
"method": "<string>",
"url": "<string>",
"headers": {},
"body": "<string>",
"response_status": 123,
"response_body": "<string>",
"target": "<string>",
"assertion": "<string>",
"expected_behavior": "<string>",
"capture_as": "<string>"
}
],
"last_replay_status": "pending",
"created_at": "<string>",
"last_replayed_at": "<string>"
}
]
}Close a finding (mark fixed or dismiss as not an issue)
Closes a finding from CI/CD or an agent: mark it ‘fixed’ or dismiss it as not an issue (‘dismissed’). Every close requires an explanation, and a dismissal also requires a category (reason_code). Workspace-scoped: a finding from another workspace resolves to 404.
curl --request PATCH \
--url https://api.enokilabs.ai/api/v1/findings/{finding_id}/close \
--header 'Content-Type: application/json' \
--data '
{
"explanation": "<string>"
}
'import requests
url = "https://api.enokilabs.ai/api/v1/findings/{finding_id}/close"
payload = { "explanation": "<string>" }
headers = {"Content-Type": "application/json"}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({explanation: '<string>'})
};
fetch('https://api.enokilabs.ai/api/v1/findings/{finding_id}/close', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.enokilabs.ai/api/v1/findings/{finding_id}/close",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'explanation' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"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 := "https://api.enokilabs.ai/api/v1/findings/{finding_id}/close"
payload := strings.NewReader("{\n \"explanation\": \"<string>\"\n}")
req, _ := http.NewRequest("PATCH", url, payload)
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.patch("https://api.enokilabs.ai/api/v1/findings/{finding_id}/close")
.header("Content-Type", "application/json")
.body("{\n \"explanation\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.enokilabs.ai/api/v1/findings/{finding_id}/close")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"explanation\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"finding_id": "<string>",
"lens": "security",
"category": "<string>",
"severity": "critical",
"description": "<string>",
"expected_behavior": "<string>",
"status": "<string>",
"created_at": "<string>",
"attacks": [
{
"attack_id": "<string>",
"score": 123,
"source": {
"source_type": "manual",
"assessment_id": "<string>",
"attack_index": 123,
"date": "<string>"
},
"replay_script": [
{
"step": "chat",
"message": "<string>",
"response": "<string>",
"session_id": "<string>",
"method": "<string>",
"url": "<string>",
"headers": {},
"body": "<string>",
"response_status": 123,
"response_body": "<string>",
"target": "<string>",
"assertion": "<string>",
"expected_behavior": "<string>",
"capture_as": "<string>"
}
],
"last_replay_status": "pending",
"created_at": "<string>",
"last_replayed_at": "<string>"
}
]
}Headers
Bearer token with API key
Path Parameters
Body
Request body to close a finding via the external API.
Deliberately stricter than the dashboard's
:class:FindingStatusUpdateRequest: a category and an explanation
are mandatory for every close, so an API-driven close is always
well-formed and carries the rationale we record as the fix
(fixed) or feed back to the attacker (dismissed).
statusis limited to the two close actions.explanationis always required (non-empty after trimming). It maps tofix_descriptionwhenfixedand todismissed_reason_textwhendismissed.reason_codeis the dismissal category, typed as the :data:DismissalReasonCodeliteral so the allowed values are published in the schema and membership is enforced automatically. It is required whenstatusisdismissedand unused otherwise.
Close action: 'fixed' (resolved) or 'dismissed' (not an issue).
fixed, dismissed Why the finding is being closed. Required for every close: recorded as the fix description when 'fixed' and as the dismissal rationale when 'dismissed'. Maximum 4000 characters.
1 - 4000Dismissal category. Required when status is 'dismissed'; ignored otherwise. Allowed values: data_is_public, authenticated_users_only, working_as_designed, mitigated_elsewhere, accepted_risk, false_positive, other.
data_is_public, authenticated_users_only, working_as_designed, mitigated_elsewhere, accepted_risk, false_positive, other Response
Successful Response
One finding in the external API response.
severity is typed as the canonical FindingSeverity enum so
OpenAPI publishes the allowed values and JSON output is always
lowercase. attacks carries the per-probe replay artefacts; the
finding row carries only the technique-level narrative shared
across every attack under it.
Taxonomy partition a Finding (or test suite) is scoped to.
Each lens is a top-level taxonomy bucket prefix. security and
safety are the v1 lenses; quality and policy are
reserved for future expansion. The lens drives:
- Discriminator on the persisted
Finding(security vs safety subtypes). - Scope enforcement at ingest: security-lens runs may only emit
security.*categories (gated on the source type), and the safety generation onlysafety.*(declared by its caller). - Per-lens system prompts at the generator and retest layers.
Adding COMPLIANCE here would break the discriminated-union
invariant, since there is no compliance Finding subtype. See
domain.testing.taxonomy.TaxonomyLens for how the three
lens-shaped enums differ.
security, safety, quality, policy Business-impact severity levels, ordered from most to least severe.
critical, high, medium, low Show child attributes
Show child attributes