Appearance
Reports
Aggregate audit metrics, export project state to PDF/CSV/JSON, and share the result over email, Slack, or a signed link. Reports work against the project's current findings, verifications, and regressions — there is no separate scheduling primitive.
Base URL: https://app.krafter.dev/api/v1
Report Summary
Retrieve aggregated metrics for a project: a 7-day risk burn-down series, the verification pass rate, the regression rate, and the count of open findings. The summary is computed live from the project's current data.
GET /orgs/:org_id/projects/:project_id/audit/reports/summaryRequired scope: audit:read
Date filtering is not yet wired up
The endpoint accepts date_from and date_to query parameters but does not currently apply them to the underlying counts — the metrics always reflect the project's current state. The risk burn-down series is always the last 7 days.
Example Request
bash
curl "https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/reports/summary" \
-H "Authorization: Bearer kr_live_abc123def456"Example Response
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": "Z-hrvhkiE1oqzTTTXmd"
},
"error": null
}risk_burn_downis a fixed 7-element series (today and the previous 6 days). Each element is{date, open_risk_score}whereopen_risk_scoreis the sum ofpriority_scoreacross findings in non-terminal statuses (new,triaged,in_progress,ready_verify).verification_pass_rateis a0..1ratio of verifications withresult: "passed"over total verifications. Returns0when there are no verifications.regression_rateis a0..1ratio ofopenregressions over findings in terminal statuses (resolved,dismissed). Returns0when nothing has been resolved yet.open_findingsis the count of findings in non-terminal statuses (new,triaged,in_progress,ready_verify).
Export Report
Queue an export of the current report summary as a JSON, CSV, or PDF artefact. The export is processed asynchronously by the Report Export Worker and consumes one unit of the team's AI quota.
POST /orgs/:org_id/projects/:project_id/audit/reports/exportRequired scope: audit:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
format | string | No | One of pdf, csv, json. Defaults to pdf when omitted. |
period_start | string | No | Start of the snapshot window (ISO 8601 date, e.g. 2025-06-04). Defaults to 6 days ago. |
period_end | string | No | End of the snapshot window (ISO 8601 date, e.g. 2025-06-10). Defaults to today. |
PDF export currently produces HTML
The pdf format option creates a queued export, but the worker currently emits an HTML artefact (Content-Type: text/html) rather than a binary PDF. Treat the pdf value as "human-readable export" until the binary renderer is wired up.
Example Request
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",
"period_start": "2025-06-01",
"period_end": "2025-06-10"
}'Example Response
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-01",
"period_end": "2025-06-10",
"format": "json",
"status": "queued",
"snapshot": {
"risk_burn_down": [],
"verification_pass_rate": 0.84,
"regression_rate": 0.12,
"open_findings": 11
},
"exported_at": null,
"shared_at": null,
"created_at": "2025-06-12T15:00:00Z",
"updated_at": "2025-06-12T15:00:00Z"
},
"meta": {
"request_id": "aa-iswiljF2praUUUYne"
},
"error": null
}status transitions through the queued, exporting, exported, shared, failed lifecycle as the worker progresses. snapshot is the report summary captured at export time.
Error Responses
json
// 422 Unprocessable Entity — invalid format, invalid date, etc.
{
"data": null,
"meta": {
"request_id": "aa-iswiljF2praUUUYne"
},
"error": {
"code": "invalid_params"
}
}json
// 429 Too Many Requests — AI quota exhausted for this team
{
"data": null,
"meta": {
"request_id": "aa-iswiljF2praUUUYne"
},
"error": {
"code": "quota_exceeded"
}
}Share Report
Share the project's most recent report over email, Slack, or a signed link. If no report exists yet, an empty placeholder JSON report is created so the share can succeed.
POST /orgs/:org_id/projects/:project_id/audit/reports/shareRequired scope: audit:write
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
channel | string | Yes | Delivery channel. One of email, slack, link. |
recipients | string[] | No | Email addresses when channel: "email". Ignored otherwise. |
webhook_url | string | No | Slack incoming-webhook URL when channel: "slack". URL is SSRF-validated. Ignored otherwise. |
The shared report is always the latest report row in the project — there is no report_id parameter. To share a specific export, call Export Report immediately before sharing so it is the most recent one.
Example Request (link)
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"
}'Example Request (email)
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": "email",
"recipients": ["security@example.com", "ops@example.com"]
}'Example Request (slack)
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": "slack",
"webhook_url": "https://hooks.slack.com/services/T0000/B0000/xxxxxxxx"
}'Example Response
json
{
"data": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"channel": "link",
"recipients": [],
"shared_at": "2025-06-12T15:00:00Z",
"share_url": "https://app.krafter.dev/insights/audit/reports/shared/SFMyNTY.g2gDdAAAAAQ..."
},
"meta": {
"request_id": "Y-gqugjhD0npySSSWlc"
},
"error": null
}share_url is populated only when channel: "link" (a Phoenix-signed token URL pointing at the public viewer). For email and slack, share_url is null and the report is delivered to the listed recipients / webhook.
Slack webhook delivery is best-effort
Slack delivery posts to webhook_url with a 10s HTTP timeout and a SSRF safety check. Failures are logged but do not surface in the API response — the call still returns success as long as the report row was updated.
Error Responses
json
// 422 Unprocessable Entity — channel not in {email, slack, link}
{
"data": null,
"meta": {
"request_id": "Y-gqugjhD0npySSSWlc"
},
"error": {
"code": "invalid_channel"
}
}json
// 422 Unprocessable Entity — report row update failed
{
"data": null,
"meta": {
"request_id": "Y-gqugjhD0npySSSWlc"
},
"error": {
"code": "request_failed"
}
}