BETA
Skip to content

Findings

List, inspect, and manage audit findings. A finding is a security, performance, accessibility, or SEO issue discovered during a scan.

Base URL: https://app.krafter.dev/api/v1

List Findings

Retrieve all findings for a project with optional filtering by severity, status, or domain.

GET /orgs/:org_id/projects/:project_id/audit/findings?severity=...&status=...&domain=...&limit=...

Required scope: audit:read

Query Parameters

ParameterTypeRequiredDescription
severitystringNoFilter by severity. One of critical, high, medium, low.
statusstringNoFilter by status. One of new, triaged, in_progress, ready_verify, resolved, dismissed.
domainstringNoFilter by domain. One of security, performance, accessibility, seo.
limitintegerNoPage size. Defaults to the server limit.
offsetintegerNoNumber of records to skip. Findings list uses offset, not cursor pagination.

Pagination

The findings list endpoint always returns meta.next_cursor: null — it does not yet emit a cursor token. Use limit + offset to page through results, or read meta.total to size your queries.

Findings are ordered by priority_score descending, then inserted_at descending, then id descending.

Example Request

bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/findings?severity=high&status=new&limit=20" \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

json
{
  "data": [
    {
      "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "org_id": "11111111-1111-1111-1111-111111111111",
      "project_id": "production-website",
      "domain": "security",
      "title": "Missing Content-Security-Policy header",
      "description": "The application does not set a Content-Security-Policy header.",
      "severity": "high",
      "status": "new",
      "confidence": 0.95,
      "priority": {
        "impact": 80,
        "effort": 20,
        "risk": 90,
        "score": 88
      },
      "owner_id": null,
      "affected_assets": [
        { "type": "url", "value": "https://example.com" }
      ],
      "evidence": [],
      "recommended_fix": {
        "summary": "Add a Content-Security-Policy header to all responses.",
        "steps": ["Configure CSP at the reverse proxy", "Start with a restrictive policy", "Relax as needed"],
        "estimated_minutes": 60
      },
      "created_at": "2025-06-10T14:30:00Z",
      "updated_at": "2025-06-10T14:30:00Z"
    }
  ],
  "meta": {
    "request_id": "F-mxbAQk6m9lXeQAAATj",
    "total": 8,
    "next_cursor": null
  },
  "error": null
}

Notes on the response shape:

  • confidence is a float between 0.0 and 1.0 reflecting how confident the scanner is that the finding is real.
  • priority contains four integer scores (impact, effort, risk, score), each in the 0..100 range. score is the composite priority used to order findings.
  • owner_id is the user UUID that owns the finding, or null if unassigned.
  • affected_assets is an array of {type, value} items collected from the scanner. The shape of each item depends on the scanner — type: "url" is the most common.
  • evidence is an array of evidence records (see below). It is empty when the scanner did not attach evidence artefacts.
  • recommended_fix is always present with summary, steps, and estimated_minutes keys; values default to empty string / empty list / 0 when the scanner did not produce a recommendation.

Evidence item shape

Each entry in evidence follows this shape:

json
{
  "id": "ev_a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "type": "screenshot",
  "artifact_url": "https://artifacts.krafter.dev/audit/...",
  "checksum": "sha256:abcd...",
  "captured_at": "2025-06-10T14:30:00Z"
}

Get Finding

Retrieve a single finding by ID.

GET /orgs/:org_id/projects/:project_id/audit/findings/:finding_id

Required scope: audit:read

Example Request

bash
curl https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/findings/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

json
{
  "data": {
    "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
    "org_id": "11111111-1111-1111-1111-111111111111",
    "project_id": "production-website",
    "domain": "security",
    "title": "Missing Content-Security-Policy header",
    "description": "The application does not set a Content-Security-Policy header.",
    "severity": "high",
    "status": "new",
    "confidence": 0.95,
    "priority": {
      "impact": 80,
      "effort": 20,
      "risk": 90,
      "score": 88
    },
    "owner_id": null,
    "affected_assets": [
      { "type": "url", "value": "https://example.com" }
    ],
    "evidence": [],
    "recommended_fix": {
      "summary": "Add a Content-Security-Policy header to all responses.",
      "steps": ["Configure CSP at the reverse proxy", "Start with a restrictive policy", "Relax as needed"],
      "estimated_minutes": 60
    },
    "created_at": "2025-06-10T14:30:00Z",
    "updated_at": "2025-06-10T14:30:00Z"
  },
  "meta": {
    "request_id": "G-nyc8sPL2vXfRBBBEUk"
  },
  "error": null
}

Error Response

json
// 404 Not Found
{
  "data": null,
  "meta": {
    "request_id": "G-nyc8sPL2vXfRBBBEUk"
  },
  "error": {
    "code": "not_found"
  }
}

Update Finding

Update the status, severity, or owner of a finding. Only provided fields are changed; the request must include at least one field or it returns 422 invalid_params.

PATCH /orgs/:org_id/projects/:project_id/audit/findings/:finding_id

Required scope: audit:write

Request Body

FieldTypeRequiredDescription
statusstringNoNew status. One of new, triaged, in_progress, ready_verify, resolved, dismissed.
severitystringNoNew severity. One of critical, high, medium, low.
owner_idstringNoUUID of the user to assign as owner. Pass an empty string to clear the assignment.

Tags are not supported

Findings have no tags field. The schema only carries domain, severity, status, scoring fields, and owner_id. Use owner_id and the tasks API to organise work.

Example Request

bash
curl -X PATCH https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/findings/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "triaged",
    "owner_id": "22222222-2222-2222-2222-222222222222"
  }'

Example Response

The response is the full finding shape (same as Get Finding), reflecting the applied changes.

Error Responses

json
// 422 Unprocessable Entity — invalid status/severity, or empty patch
{
  "data": null,
  "meta": {
    "request_id": "H-pzd9tQM3wYgSCCCFVl"
  },
  "error": {
    "code": "invalid_params"
  }
}
json
// 404 Not Found
{
  "data": null,
  "meta": {
    "request_id": "H-pzd9tQM3wYgSCCCFVl"
  },
  "error": {
    "code": "not_found"
  }
}

Bulk Update Findings

Update multiple findings at once. Applies the same patch to every finding in finding_ids.

POST /orgs/:org_id/projects/:project_id/audit/findings/bulk

Required scope: audit:write

Request Body

FieldTypeRequiredDescription
finding_idsstring[]YesArray of finding UUIDs. Non-string entries are filtered; duplicates are de-duplicated. Empty arrays return 422 invalid_params.
patchobjectYesFields to update on each finding. Must contain at least one of the fields below.
patch.statusstringNoNew status. Same enum as Update Finding.
patch.severitystringNoNew severity. Same enum as Update Finding.
patch.owner_idstringNoUUID of the user to assign as owner.

Example Request

bash
curl -X POST https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/findings/bulk \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "finding_ids": [
      "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
      "c3d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
    ],
    "patch": {
      "status": "dismissed"
    }
  }'

Example Response

json
{
  "data": {
    "updated_count": 3,
    "requested_count": 3,
    "ids": [
      "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "b2c3d4e5-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
      "c3d4e5f6-7a8b-9c0d-1e2f-3a4b5c6d7e8f"
    ]
  },
  "meta": {
    "request_id": "I-qae0uRN4xZhTDDDGWm"
  },
  "error": null
}

updated_count reflects how many records were actually updated within the project scope. requested_count is the de-duplicated input size. ids lists the IDs that matched the project scope and were updated. IDs that belong to a different project (or do not exist) are silently dropped — compare requested_count against updated_count to detect mismatches.


List Regressions

Retrieve regressions tracked against findings in the project. A regression record is created when a previously-resolved finding is detected again.

GET /orgs/:org_id/projects/:project_id/audit/regressions?cursor=...&limit=...

Required scope: audit:read

Query Parameters

ParameterTypeRequiredDescription
cursorstringNoPagination cursor from a previous response.
limitintegerNoPage size. Defaults to the server limit.

Regressions are ordered by inserted_at descending, then id descending.

Example Request

bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/regressions?limit=10" \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

json
{
  "data": [
    {
      "id": "d4e5f6a7-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
      "finding_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "severity": "high",
      "trigger": "scan",
      "status": "open",
      "impacted_assets": [
        { "type": "url", "value": "https://example.com" }
      ],
      "opened_at": "2025-06-10T14:30:00Z",
      "closed_at": null,
      "created_at": "2025-06-10T14:30:00Z",
      "updated_at": "2025-06-10T14:30:00Z"
    }
  ],
  "meta": {
    "request_id": "J-rbf1vSO5yajUEEEHXn",
    "total": 1,
    "next_cursor": null
  },
  "error": null
}

status is open while the regression is active and closed once it is resolved again. closed_at is null while status is open.

Built by Krafter Studio