Skip to main content
POST
Create a policy

Authorizations

Authorization
string
header
required

An Earnie API key, sent as Authorization: Bearer sk_earnie_.... Create one under Settings > API keys. The key's scopes decide which operations it may call.

Headers

Idempotency-Key
string

Opaque client-generated key. Repeating the same request with the same key and actor returns the originally created policy (201, no second policy); reusing the key for a DIFFERENT request returns 409 idempotency_conflict.

Required string length: 1 - 255

Body

application/json
module
string
required
category
string
required
name
string
required
action
enum<string>
required

Enforcement action applied when the policy fires. Verdict mapping: block → block warn → warn require → require

Available options:
block,
warn,
require
expression
string
required
parameter_spec
object[]
required
values
object
required
slug
string

Optional. When omitted, the server derives a slug from name (kebab-cased) and disambiguates collisions. Supply an explicit slug only when migrating known-identity rows.

description
string
agent_rule
string

The imperative sentence an AI coding agent reads instead of the CEL (see Policy.agent_rule). Optional; omitted means the rendering falls back to description.

enabled
boolean
default:true
attach_project_id
string<uuid>

Optional. When present, the created policy is attached to this project in the SAME transaction and authorization context as its creation — a failed attachment rolls the creation back rather than leaving an orphan definition behind.

Omit it for organization-level authoring, which stays valid for the consumers that deliberately want a definition with no attachment yet. The project must belong to the caller's organization; one that does not refuses the whole request before anything is written.

Response

Created

id
integer<int64>
required
read-only
slug
string
required

Stable, human-readable identifier safe across deploys and migrations. Generated from name (kebab-cased) on Create when not supplied; templates ship with deterministic slugs (e.g. block-copyleft-licenses). Immutable after creation.

source
enum<string>
required

Server-managed origin of the policy. Seeded templates return seed; user-authored rows return custom.

Available options:
seed,
custom
module
string
required

Finding domain the policy targets (e.g. scans, crypto, crypto_finder). AI provenance is an xBOM enrichment, not a separate policy domain.

category
string
required

Grouping within a scanner (e.g. licenses, vulnerabilities).

name
string
required
description
string
required

Human-readable explanation of what the policy checks and why.

agent_rule
string
required

Keep it to roughly 200 bytes. That is not a stored-length limit — nothing rejects a longer one — it is where the RENDERED sentence is truncated with an ellipsis before it reaches an agent, measured after placeholders are substituted and in bytes rather than characters. A short rule whose {banned_licenses} expands to three hundred bytes is truncated too. The cap exists because this text sits in every prompt of every session.

Written in the second person, one sentence, no CEL: "Do not add dependencies licensed {banned_licenses}."

{key} placeholders name parameter_spec[].key entries and are substituted with the policy's current values at render time, so one sentence stays true after a parameter is tuned. A list value renders as a, b or c. A placeholder naming no parameter is rejected on save.

Empty is allowed and means "no agent rule": rendering falls back to description, and last to the CEL expression labelled as such, so a policy without one still reaches an agent. Like description it is prose and NOT part of the policy revision — rewording what an agent reads does not change what the gate does.

agent_rule_stale
boolean
required
read-only

True when agent_rule was written against different rule logic than this policy now carries — its action, expression, or the set of parameter keys has changed since the sentence was last saved.

An agent rule is hand-written prose and cannot be derived from CEL, so nothing keeps it true when the expression moves underneath it. This is how the product notices: the sentence still renders (it is usually still mostly right), but the policy is flagged so a human can review it. Saving the agent rule again clears the flag.

Parameter VALUES are deliberately excluded: {key} placeholders already keep the sentence true across a retune, so a threshold tweak never raises this. False when the policy has no agent rule, and false for a row that predates the stored basis — "unknown" must not read as "stale".

enabled
boolean
required
action
enum<string>
required

Enforcement action applied when the policy fires. Verdict mapping: block → block warn → warn require → require

Available options:
block,
warn,
require
expression
string
required

CEL string. Compiled via cel-go on save (parse + type check + params.X consistency) and evaluated against findings via Eval (see /preview for the live-evaluation surface).

parameter_spec
object[]
required
values
object
required

Current parameter values keyed by parameter_spec[].key. Every declared key needs a value or a default in its spec entry, including a parameter the expression only reads behind has(); a key left out is stored with its default. See ValuesInvalidError.

revision
string
required
read-only

The policy's immutable content revision: a sha256: digest over the canonical form of the gating content — action, expression, parameter_spec, values. It is byte-identical to the policy_hash an evaluation records and to the revision a scan's policy snapshot names, so "which revision produced this verdict" and "which revision is live now" are answered in one vocabulary.

Mutations echo it back in If-Match. A stale revision is 409 policy_conflict; a policy edit is never last-write-wins.

It covers the gating content and nothing else. Renaming a policy or rewriting its description leaves the revision unchanged, because neither changes what the gate does, and enabled is carried by presence in a scan's snapshot rather than by the digest.

created_at
string<date-time>
required
read-only
updated_at
string<date-time>
required
read-only
result_summary
object