Update the Policy
Replace or partially update the tenant AI policy. PUT clears omitted fields, PATCH preserves them, and the satisfiability gate refuses activations that strand operations.
Two write routes share one body shape and one guarantee. PUT /v1/ai/policy replaces the policy wholesale: every field is optional in the body, but an omitted field is cleared, which makes PUT the right route when the policy is generated from the customer's own source of truth and the submitted document should be the whole truth. PATCH /v1/ai/policy updates partially: omitted fields keep their current value, and inside the override maps omitted keys survive too.
The PATCH merge rule extends into the maps. model_substitutions, default_models, model_classes, operation_classes, and features are override maps; a PATCH that sends {"operation_classes": {"extraction": "complex"}} changes only the extraction entry and leaves every other override in place. Sending null as a map value removes that single override and restores the platform default for it. On PUT there is nothing to merge: the submitted maps replace the stored ones entirely, and an omitted map clears all of its overrides.
Both routes default to activating. status falls back to active on PUT (and to the policy's current status on PATCH), and activation runs the satisfiability gate: a policy that would leave a registered operation with no compliant route is refused with 400 policy_unsatisfiable, the response names the stranded operations, and nothing is stored. draft and shadow are stored without taking effect; only active policies are enforced at routing time. change_reason (up to 200 characters) is recorded on the stored version and surfaces in the audit trail, so a residency change stays attributable to an intent, not just to a key.
region_pin, denied_models, model_classes), but values INSIDE model_classes use camelCase (chain, requiredCapabilities, fallback), and features keys are camelCase too (dataCapture, markdownExtraction, assetVision, assetAnnotation, classificationEscalation, richSummary, documentClassification). Those inner shapes pass straight to the shared policy validator, which one validator both the public API and the admin console use. A snake_case key inside model_classes or features is rejected as invalid, not silently ignored./v1/ai/policyBody fields (all optional)
active/v1/ai/policycURL: PATCH one override away, deny one model
curl -X PATCH "https://api.talonic.com/v1/ai/policy" \
-H "Authorization: Bearer $TALONIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"denied_models": ["claude-opus"],
"operation_classes": { "extraction": null },
"change_reason": "Deny one model, drop the extraction class override"
}'Response: Saved version plus the effective view
{
"saved": { "version": 7, "status": "active" },
"policy": { "version": 7, "status": "active", "region_pin": "eu", "...": "..." },
"inherited": false,
"constraints": {
"region_pin": "eu",
"allowed_providers": ["bedrock"],
"denied_models": ["claude-opus"],
"platform_provider_floor": ["bedrock"]
},
"operation_classes": {
"extraction": { "class": "standard", "source": "builtin", "overridden": false }
},
"status": {
"satisfiable": true,
"unroutable_operations": [],
"advisory_warnings": ["document_ocr"]
}
}Write Errors
A refused write stores nothing; the current policy stays exactly as it was. policy_unsatisfiable means activation would strand registered operations, and the response lists them in unroutable_operations alongside advisory_warnings, so the fix (widen the providers, the region pin, or the denials) can be targeted rather than guessed. invalid_policy means the shape is wrong or a referenced model or class does not exist in the catalog. workspace_scope_required means the key is not scoped to a single workspace.
Response: 400 policy_unsatisfiable
{
"error": "policy_unsatisfiable",
"message": "This policy leaves 2 operations with no compliant route: extraction, classification. Widen the allowed providers, the region pin, or the denied models, then submit again.",
"unroutable_operations": ["extraction", "classification"],
"advisory_warnings": ["document_ocr"]
}