BETA
Skip to content

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/summary

Required 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_down is a fixed 7-element series (today and the previous 6 days). Each element is {date, open_risk_score} where open_risk_score is the sum of priority_score across findings in non-terminal statuses (new, triaged, in_progress, ready_verify).
  • verification_pass_rate is a 0..1 ratio of verifications with result: "passed" over total verifications. Returns 0 when there are no verifications.
  • regression_rate is a 0..1 ratio of open regressions over findings in terminal statuses (resolved, dismissed). Returns 0 when nothing has been resolved yet.
  • open_findings is 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/export

Required scope: audit:write

Request Body

FieldTypeRequiredDescription
formatstringNoOne of pdf, csv, json. Defaults to pdf when omitted.
period_startstringNoStart of the snapshot window (ISO 8601 date, e.g. 2025-06-04). Defaults to 6 days ago.
period_endstringNoEnd 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/share

Required scope: audit:write

Request Body

FieldTypeRequiredDescription
channelstringYesDelivery channel. One of email, slack, link.
recipientsstring[]NoEmail addresses when channel: "email". Ignored otherwise.
webhook_urlstringNoSlack 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.

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"
  }
}

Built by Krafter Studio