BETA
Skip to content

Policies ​

Each project has a single active audit policy: a JSON document (config) that the pipeline applies in two places. Any other key is stored as-is and ignored.

KeyApplied whereMeaning
severity_ruleswhen a scanner writes a findingA cap per domain (security, performance, accessibility, seo). The scanner's grade stands unless it is more severe than the domain's rule, in which case it is lowered to the rule. A rule never raises a finding, and a domain without a rule keeps the scanner's grade.
sla_hourswhen a task is createdHours until the task is due, by the finding's severity (critical, high, medium, low). Used only when the caller does not pass sla_due_at.

Cap, not override

security: critical does not turn every security finding into a critical — a missing Referrer-Policy header stays low. It says security findings may go up to critical. To de-prioritise a domain, cap it lower: seo: low makes every SEO finding at most low. Use Test Policy to see exactly which findings a rule would move before saving it.

The cap is applied when a finding is written, i.e. on the next scan after the policy changes. Saving a policy does not re-grade findings already in the project — Test Policy shows what the next scan would change.

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

Get Current Policy ​

Retrieve the currently active policy for a project. If the project has no active policy yet, a default one is created on the fly and returned.

GET /orgs/:org_id/projects/:project_id/audit/policies/current

Required scope: audit:read

Example Request ​

bash
curl https://app.krafter.dev/api/v1/orgs/:org_id/projects/:project_id/audit/policies/current \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response ​

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
      }
    },
    "active": true,
    "updated_at": "2025-06-08T12:00:00Z"
  },
  "meta": {
    "request_id": "R-zjn9daW3girMMMPfv"
  },
  "error": null
}

The response shape is {id, name, org_id, project_id, config, active, updated_at}. The example config above is the default policy that the API creates the first time the endpoint is hit for a project. It is also what the pipeline applies to a project that has never touched this endpoint, so a project with no policy row behaves exactly like one with the default. Once you write your own config via Update Policy, config echoes back exactly what you sent.


Update Policy ​

Replace the current policy's name and config. The policy is upserted: if no active policy exists, one is created; otherwise, the existing active policy is updated in place. The API stores config as-is; the two keys it applies are described at the top of this page, and a severity_rules value that is not one of critical, high, medium, low is ignored rather than applied.

PUT /orgs/:org_id/projects/:project_id/audit/policies/current

Required scope: audit:write

Request Body ​

FieldTypeRequiredDescription
namestringNoHuman-readable policy name. Defaults to "Default Audit Policy" when omitted.
configobjectYesThe policy. Any JSON object is accepted; severity_rules and sla_hours are applied, the rest is stored.

Full replacement

The config field is replaced wholesale, not merged. Always send the complete config you want to retain.

Example Request ​

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",
        "accessibility": "high",
        "seo": "medium"
      },
      "sla_hours": {
        "critical": 12,
        "high": 48,
        "medium": 120,
        "low": 240
      }
    }
  }'

Example Response ​

The response is the full policy shape (same as Get Current Policy), reflecting the applied changes.

Error Response ​

json
// 422 Unprocessable Entity — changeset failed (e.g. config is not a map)
{
  "data": null,
  "meta": {
    "request_id": "S-akoaebX4hjsNNNQgw"
  },
  "error": {
    "code": "invalid_params"
  }
}

Test Policy ​

A dry run: what applying a config on the next scan would do to the project's findings today. Nothing is written. The same rule the scanners use at write time produces the preview, so the two cannot disagree.

Pass a config to preview a change before saving it; pass none (or {}) to preview the policy currently active — the question the Policies page's Test button asks.

POST /orgs/:org_id/projects/:project_id/audit/policies/test

Required scope: audit:write

Request Body ​

FieldTypeRequiredDescription
configobjectNoThe policy to preview. Empty or omitted previews the active policy.

Example Request ​

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": {
        "seo": "low",
        "accessibility": "medium"
      }
    }
  }'

Example Response ​

json
{
  "data": {
    "simulated_findings": 42,
    "changed": 5,
    "critical_after": 3,
    "by_domain": [
      { "domain": "accessibility", "from": "critical", "to": "medium", "count": 1 },
      { "domain": "seo", "from": "medium", "to": "low", "count": 4 }
    ],
    "config_preview": {
      "severity_rules": {
        "seo": "low",
        "accessibility": "medium"
      }
    }
  },
  "meta": {
    "request_id": "T-blpbfcY5iktOOORhx"
  },
  "error": null
}
  • simulated_findings — every finding in the project the rules were run over.
  • changed — how many would end up with a different severity.
  • critical_after — how many would be critical once the rules are applied.
  • by_domain — each {domain, from, to} transition and how many findings take it, so you can see which findings a rule touches rather than a bare total. Empty when nothing would change.
  • config_preview — the config the preview ran with: yours, or the active one when you sent none.

Changed from earlier releases

Before this endpoint applied the config, its response carried escalated_to_critical, which was the number of findings already critical. That field is gone: under a cap nothing escalates, so it could only ever have been zero. simulated_findings and config_preview keep their meaning.

Built by Krafter Studio