Appearance
Remediation
Once you have triaged a finding, Krafter Audit gives you a full remediation workflow: create tasks, run verifications, configure policies, connect external tools, and generate reports. This guide covers each stage end-to-end.
Remediation tasks
A task is the unit of work that closes a finding. It carries an owner, a priority, an SLA, and optional pointers to an external ticket or pull request. Each task belongs to exactly one finding.
Creating a task
Create a task from a finding:
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/tasks" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"finding_id": "d4e5f6a7-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
"title": "Add Content-Security-Policy header",
"priority": "high",
"owner_id": "22222222-2222-2222-2222-222222222222",
"sla_due_at": "2025-07-01T00:00:00Z"
}'json
// 201 Created
{
"data": {
"id": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c",
"finding_id": "d4e5f6a7-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
"title": "Add Content-Security-Policy header",
"status": "backlog",
"priority": "high",
"owner_id": "22222222-2222-2222-2222-222222222222",
"sla_due_at": "2025-07-01T00:00:00Z",
"external_ticket_key": null,
"pr_url": null,
"created_at": "2025-06-15T12:00:00Z",
"updated_at": "2025-06-15T12:00:00Z"
},
"meta": {
"request_id": "F-mxbAQk6m9lXeQAAATj"
},
"error": null
}Tasks have no description or assignee (email) fields. The remediation guidance lives on the linked finding's recommended_fix object, and ownership is tracked by owner_id (a user UUID). To link an external tracker or PR, use external_ticket_key and pr_url instead.
Task statuses
Tasks follow this lifecycle:
backlog → in_progress → ready_verify → done
↘ blocked ↗| Status | Meaning |
|---|---|
| backlog | Task has been created, work has not started. The default initial status. |
| in_progress | Someone is actively working on the fix. |
| blocked | Work is paused waiting on an external dependency. |
| ready_verify | Work is complete; the next step is to run a verification. |
| done | The fix has been verified. Set automatically when a verification is approved. |
priority is one of low, medium, high, critical and defaults to medium for new tasks.
Don't set done by hand
Approving a verification automatically transitions the linked task to done and the linked finding to resolved. The recommended path is to move the task to ready_verify, run a verification, and let the approval close the loop.
Updating a task
Only the fields you provide are changed; the request must include at least one updatable field.
bash
curl -X PATCH "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/tasks/f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"status": "in_progress",
"pr_url": "https://github.com/acme/web-app/pull/142"
}'The response is the full task shape with the new fields applied. The finding_id cannot be changed once the task is created — make a new task instead if you need to re-link work.
Listing tasks
Filter tasks by status:
bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/tasks?status=in_progress&limit=25" \
-H "Authorization: Bearer kr_live_abc123def456"json
{
"data": [
{
"id": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c",
"finding_id": "d4e5f6a7-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
"title": "Add Content-Security-Policy header",
"status": "in_progress",
"priority": "high",
"owner_id": "22222222-2222-2222-2222-222222222222",
"sla_due_at": "2025-07-01T00:00:00Z",
"external_ticket_key": null,
"pr_url": "https://github.com/acme/web-app/pull/142",
"created_at": "2025-06-15T12:00:00Z",
"updated_at": "2025-06-15T13:00:00Z"
}
],
"meta": {
"request_id": "G-nyc8sPL2vXfRBBBEUk",
"total": 1,
"next_cursor": null
},
"error": null
}Verification workflow
A verification confirms that a remediation task actually fixed the underlying finding. Verifications are created against a single task — the task's linked finding is what gets re-checked. Each call consumes one unit of the team's AI quota.
Running a verification
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/verifications/run" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"task_id": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c"
}'json
// 202 Accepted
{
"data": {
"id": "f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"task_id": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c",
"result": "pending",
"checks": [
{ "key": "security_header_csp", "before": "failed", "after": "pending", "status": "running" }
],
"verified_by_id": null,
"verified_at": null,
"created_at": "2025-06-20T15:00:00Z",
"updated_at": "2025-06-20T15:00:00Z"
},
"meta": {
"request_id": "H-pzd9tQM3wYgSCCCFVl"
},
"error": null
}result is one of pending, passed, failed. checks carries per-check {key, before, after, status} records that the worker fills in as it runs. There are no reviewer or notes fields — the audit trail is the per-check before/after pairs.
One task per call
The endpoint takes a single task_id, not an array. To verify multiple tasks, call /verifications/run once per task.
Approving a verification
If the verification confirms the fix, approve it. This is a transactional action: on success the verification's result becomes passed, the linked task moves to done, the linked finding moves to resolved, and any open regressions for that finding are closed.
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/verifications/f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d/approve" \
-H "Authorization: Bearer kr_live_abc123def456"json
{
"data": {
"id": "f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"task_id": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c",
"result": "passed",
"checks": [
{ "key": "security_header_csp", "before": "failed", "after": "passed", "status": "passed" }
],
"verified_by_id": "22222222-2222-2222-2222-222222222222",
"verified_at": "2025-06-20T15:30:00Z",
"created_at": "2025-06-20T15:00:00Z",
"updated_at": "2025-06-20T15:30:00Z"
},
"meta": {
"request_id": "I-qae0uRN4xZhTDDDGWm"
},
"error": null
}Reopening a verification
If the issue persists, reopen the verification. This is the inverse transactional action: result becomes failed, the linked task moves back to in_progress, the linked finding moves back to in_progress, and a new regression is recorded against the finding with trigger: "verification_reopened".
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/verifications/f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d/reopen" \
-H "Authorization: Bearer kr_live_abc123def456"json
{
"data": {
"id": "f1e2d3c4-b5a6-9788-7c6d-5e4f3a2b1c0d",
"task_id": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f9a0b1c",
"result": "failed",
"checks": [
{ "key": "security_header_csp", "before": "failed", "after": "pending", "status": "failed" }
],
"verified_by_id": "22222222-2222-2222-2222-222222222222",
"verified_at": "2025-06-20T16:00:00Z",
"created_at": "2025-06-20T15:00:00Z",
"updated_at": "2025-06-20T16:00:00Z"
},
"meta": {
"request_id": "J-rbf1vSO5yajUEEEHXn"
},
"error": null
}Listing verifications
bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/verifications" \
-H "Authorization: Bearer kr_live_abc123def456"The list response is the array form of the same task_id / result / checks shape shown above. See the Verifications API reference for the full field reference.
Policy management
Each project has a single active policy interpreted by the scanner. The config field is a free-form JSON object — the API stores it opaquely and does not validate the shape of individual rules. The example below shows the default policy the API creates on first read; once you write your own config it is echoed back exactly as sent.
Getting the current policy
bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/policies/current" \
-H "Authorization: Bearer kr_live_abc123def456"json
{
"data": {
"id": "b9c8d7e6-5a4f-3e2d-1c0b-9a8b7c6d5e4f",
"name": "Default Audit Policy",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"config": {
"severity_rules": {
"security": "critical",
"performance": "high",
"accessibility": "medium",
"seo": "low"
},
"sla_hours": { "critical": 24, "high": 72, "medium": 168, "low": 336 },
"alert_routing": { "critical": ["slack", "email"], "high": ["slack"] }
},
"active": true,
"updated_at": "2025-06-08T12:00:00Z"
},
"meta": {
"request_id": "K-scg2wTP6zbkVFFFIYo"
},
"error": null
}Updating the policy
PUT replaces the active policy's name and config. The config field is replaced wholesale, not merged — always send the complete config you want to retain.
bash
curl -X PUT "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/policies/current" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"name": "Strict Production Policy",
"config": {
"severity_rules": { "security": "critical", "performance": "high" },
"sla_hours": { "critical": 12, "high": 48 }
}
}'The response is the full policy shape with the new fields reflected.
Testing a policy
The test endpoint runs a what-if simulation against the project's current findings. Today it returns the project's existing finding count, the count already at critical severity, and echoes the submitted config back as config_preview. Use it to wire up the simulation UI without surprises — note that the supplied config is not yet applied to override severities.
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/policies/test" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"config": { "severity_rules": { "performance": "high" } }
}'json
{
"data": {
"simulated_findings": 42,
"escalated_to_critical": 3,
"config_preview": {
"severity_rules": { "performance": "high" }
}
},
"meta": {
"request_id": "L-tdh3xUQ7aclWGGGJZp"
},
"error": null
}Integrations
Connect external systems to your audit workflow. Supported providers: ga4, gsc, sentry, github, gitlab, jira, linear, cloudflare. The Audit API tracks one row per provider per project.
Listing integrations
The list endpoint always returns one entry per supported provider — providers that have never been connected return a placeholder row with status: "disconnected" and id: null.
bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations" \
-H "Authorization: Bearer kr_live_abc123def456"json
{
"data": [
{
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"provider": "github",
"status": "connected",
"last_sync_at": "2025-06-10T14:00:00Z",
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-10T14:00:00Z"
},
{
"id": null,
"provider": "sentry",
"status": "disconnected",
"credentials": {},
"last_sync_at": null,
"inserted_at": null,
"updated_at": null
}
],
"meta": {
"request_id": "M-uei4yVR8bdmXHHHKaq"
},
"error": null
}status is one of connected, disconnected, syncing, error. Credentials are never echoed back — see the Integrations API → Security for details on AES-256-GCM encryption at rest.
Connecting an integration
The request body is provider-agnostic: pass the provider's secrets in credentials (a free-form map) or a short-lived OAuth auth_code. Per-provider key names (e.g. token, repository) are conventions the worker interprets, not validated by the API.
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations/github/connect" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"credentials": {
"token": "ghp_xxxxxxxxxxxx",
"repository": "acme/web-app"
}
}'The connect call upserts on (team, project, provider) — calling it again replaces the stored credentials.
Syncing an integration
Trigger a manual sync. The integration is moved to status: "syncing" synchronously and a background worker pushes/pulls findings and tasks. The endpoint takes no body — it always syncs everything pending for that provider.
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/integrations/github/sync" \
-H "Authorization: Bearer kr_live_abc123def456"Poll List Integrations to see when status flips back to connected (or error) and last_sync_at advances.
Reports
Aggregate audit metrics, export project state, and share the result over email, Slack, or a signed link.
Report summary
The summary returns a 7-day risk burn-down series, the verification pass rate, the regression rate, and the count of open findings. Counts always reflect the project's current state — date filtering query parameters are accepted but not yet wired up.
bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/reports/summary" \
-H "Authorization: Bearer kr_live_abc123def456"json
{
"data": {
"risk_burn_down": [
{ "date": "2025-06-04", "open_risk_score": 320 },
{ "date": "2025-06-05", "open_risk_score": 305 },
{ "date": "2025-06-06", "open_risk_score": 290 },
{ "date": "2025-06-07", "open_risk_score": 260 },
{ "date": "2025-06-08", "open_risk_score": 240 },
{ "date": "2025-06-09", "open_risk_score": 220 },
{ "date": "2025-06-10", "open_risk_score": 210 }
],
"verification_pass_rate": 0.84,
"regression_rate": 0.12,
"open_findings": 11
},
"meta": {
"request_id": "N-vfj5zWS9cenYIIILbr"
},
"error": null
}open_findings counts findings in non-terminal statuses (new, triaged, in_progress, ready_verify). regression_rate is open_regressions / (resolved + dismissed findings).
Exporting a report
Queue an export of the current report summary as JSON, CSV, or PDF. Each export consumes one unit of the team's AI quota.
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/reports/export" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"format": "json"
}'json
// 202 Accepted
{
"data": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"org_id": "11111111-1111-1111-1111-111111111111",
"project_id": "production-website",
"period_start": "2025-06-04",
"period_end": "2025-06-10",
"format": "json",
"status": "queued",
"snapshot": { "open_findings": 11, "verification_pass_rate": 0.84 },
"exported_at": null,
"shared_at": null,
"created_at": "2025-06-12T15:00:00Z",
"updated_at": "2025-06-12T15:00:00Z"
},
"meta": {
"request_id": "O-wgk6aXT0dfoZJJJMcs"
},
"error": null
}status transitions through queued → exporting → exported → shared as the worker progresses (or failed if it errors). Available formats: pdf, csv, json. The pdf format currently produces an HTML artefact while the binary renderer is being wired up.
Sharing a report
Share the project's most recent report. The endpoint always operates on the latest report row — there is no report_id parameter.
bash
curl -X POST "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/reports/share" \
-H "Authorization: Bearer kr_live_abc123def456" \
-H "Content-Type: application/json" \
-d '{
"channel": "link"
}'json
{
"data": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"channel": "link",
"recipients": [],
"shared_at": "2025-06-20T16:00:00Z",
"share_url": "https://app.krafter.dev/insights/audit/reports/shared/SFMyNTY.g2gDdAAAAAQ..."
},
"meta": {
"request_id": "P-xhl7bYU1egpaKKKNdt"
},
"error": null
}share_url is populated only when channel: "link". For email, pass a recipients array of addresses. For slack, pass a webhook_url (Slack incoming-webhook URL — the URL is SSRF-validated and delivery is best-effort).
Next steps
- Quickstart — Run your first scan and triage a finding
- Findings Guide — Severity levels, status lifecycle, filtering, bulk operations, and regression tracking