Rule Versioning
Matching rules are versioned independently of the spec. Every time you create a rule via the API, the system auto-increments the version number for that rule name. Only one version per name can be active at a time — activating a new version automatically deactivates the previous one.
How It Works
Each MatchRule has these version-relevant fields:
| Field | Type | Description |
|---|---|---|
name | string | Rule identifier (e.g., "email_exact") |
version | integer | Auto-incremented per rule name |
is_active | boolean | Only one version per name can be true |
rule_type | string | Rule type ("exact", "similarity", "composite") |
config | JSON | Rule-specific configuration |
weight | float | Scoring weight (0.0–1.0) |
created_by | UUID | User who created this version |
created_at | timestamp | When this version was created |
When you create a rule with a name that already exists, the system:
- Assigns the next version number (e.g., v1 exists → new rule gets v2)
- If
is_active: true, deactivates all other versions of that name - Stores the new version — previous versions are preserved, never deleted
Creating a Rule Version
v1: Initial Rule
import httpx
resp = httpx.post("https://api.kanoniv.com/v1/rules",
headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
json={
"name": "email_exact",
"rule_type": "exact",
"config": {"field": "email", "case_sensitive": False},
"weight": 1.0,
"is_active": True
})
rule = resp.json()
print(f"Created {rule['name']} v{rule['version']}")
# Created email_exact v1curl -X POST "https://api.kanoniv.com/v1/rules" \
-H "X-API-Key: kn_..." \
-H "Content-Type: application/json" \
-d '{
"name": "email_exact",
"rule_type": "exact",
"config": {"field": "email", "case_sensitive": false},
"weight": 1.0,
"is_active": true
}'v2: Update the Rule
Later you decide case-sensitive matching is needed and increase the weight:
resp = httpx.post("https://api.kanoniv.com/v1/rules",
headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
json={
"name": "email_exact",
"rule_type": "exact",
"config": {"field": "email", "case_sensitive": True},
"weight": 0.95,
"is_active": True # automatically deactivates v1
})
rule = resp.json()
print(f"Created {rule['name']} v{rule['version']}")
# Created email_exact v2curl -X POST "https://api.kanoniv.com/v1/rules" \
-H "X-API-Key: kn_..." \
-H "Content-Type: application/json" \
-d '{
"name": "email_exact",
"rule_type": "exact",
"config": {"field": "email", "case_sensitive": true},
"weight": 0.95,
"is_active": true
}'After this, v1 is is_active: false and v2 is is_active: true. Both are stored.
Viewing Rule History
The history endpoint returns all versions of a rule, ordered by version number:
resp = httpx.get("https://api.kanoniv.com/v1/rules/email_exact/history",
headers={"X-API-Key": "kn_..."})
history = resp.json()
for v in history:
status = "ACTIVE" if v["is_active"] else "inactive"
print(f" v{v['version']}: weight={v['weight']}, "
f"case_sensitive={v['config']['case_sensitive']}, {status}")curl "https://api.kanoniv.com/v1/rules/email_exact/history" \
-H "X-API-Key: kn_..."Example output:
v2: weight=0.95, case_sensitive=True, ACTIVE
v1: weight=1.0, case_sensitive=False, inactiveTesting Before Activating
Create a new version with is_active: false to test it without affecting production:
# Create v3 as inactive — v2 stays active
resp = httpx.post("https://api.kanoniv.com/v1/rules",
headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
json={
"name": "email_exact",
"rule_type": "exact",
"config": {"field": "email", "case_sensitive": True, "normalize": True},
"weight": 0.9,
"is_active": False # does NOT deactivate v2
})
print(f"Created v{resp.json()['version']} (inactive, v2 still active)")Once you've validated the new version works as expected, activate it by creating the next version with is_active: true, or by creating a new version that copies v3's config with is_active: true.
Rolling Back
There's no dedicated rollback endpoint. Instead, create a new version with the old config:
# v2 is active but causing issues. Roll back to v1's config.
resp = httpx.post("https://api.kanoniv.com/v1/rules",
headers={"X-API-Key": "kn_...", "Content-Type": "application/json"},
json={
"name": "email_exact",
"rule_type": "exact",
"config": {"field": "email", "case_sensitive": False}, # same as v1
"weight": 1.0, # same as v1
"is_active": True
})
print(f"Rolled back — created v{resp.json()['version']} with v1 config")This creates v3 (or v4, depending on history) with v1's configuration. The full history is preserved:
v4: weight=1.0, case_sensitive=False, ACTIVE ← rollback
v3: weight=0.9, case_sensitive=True, inactive ← test version
v2: weight=0.95, case_sensitive=True, inactive ← the bad version
v1: weight=1.0, case_sensitive=False, inactive ← originalRule Versioning vs. Spec Versioning
| Rule Versioning | Spec Versioning | |
|---|---|---|
| Scope | Single matching rule | Entire resolution logic (all rules, sources, thresholds, survivorship) |
| Version format | Auto-incrementing integer | User-defined string (customer-v3) + computed SHA-256 hash |
| Change detection | Compare version numbers or config JSON | Compare plan_hash values or use diff() |
| Managed via | POST /v1/rules API | POST /v1/identity/specs API or Spec.from_file() + SDK |
| History | GET /v1/rules/:name/history | GET /v1/identity/specs (list all versions) |
| Rollback | Create new version with old config | Upload old spec YAML again |
Rule versioning is useful when you're tuning individual rules via the API (e.g., adjusting weights in production). Spec versioning captures the full picture — if you manage rules through YAML specs, the spec's plan_hash already tracks rule changes.
See Also
- Rules & Specs API Reference — Full endpoint documentation
- Spec Versioning —
identity_version,plan_hash, anddiff() - Matching Rules Concept — How rules work in the matching pipeline
