Skip to main content

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.

Naming asymmetry, by design: top-level request fields are snake_case (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.
PUT/v1/ai/policy

Body fields (all optional)

region_pinstring | nullHard residency constraint: us or eu. A deployment in any other region is rejected, including globally-routed ones, so residency is never approximated.
allowed_providersstring[] | nullProviders permitted to process this workspace's data: anthropic, bedrock, foundry, openai, azure-openai, google, mistral, local. Null means every provider the platform permits.
denied_modelsstring[] | nullModels this workspace may never be routed to, whatever else permits them. Each entry must exist in the model catalog.
model_substitutionsobjectRemap one model onto another wherever the first would be chosen, for example {"claude-opus": "claude-sonnet"}. The substitute is still subject to every mandatory constraint.
default_modelsobjectPreferred model per speed tier where no class governs the call. Keys are fast, default, powerful.
max_attemptsintegerCeiling on provider attempts for one logical call. 1 to 8.
model_classesobjectNamed model lineage overrides. Values are camelCase: { description, chain: [{ model, deployments: [{ provider, region }] }], requiredCapabilities, fallback }. fallback is chain or primary_only.
operation_classesobjectPer-operation class overrides keyed by operation type, for example {"extraction": "standard"}.
featuresobjectFeature toggles with camelCase keys mapped to booleans. Absent toggles stay on.
statusstringdraft, shadow, or active. Only active is enforced at runtime. Default: active
change_reasonstringWhy this change is being made, up to 200 characters. Recorded on the stored version.
PATCH/v1/ai/policy

cURL: 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"]
}

Frequently asked questions

When should I use PUT and when PATCH?+
Use PUT when the submitted document should be the whole policy, for example when it is generated from your own compliance source of truth: omitted fields are cleared, so nothing stale survives. Use PATCH to change one field or one override while keeping everything else, including override-map keys you did not resend.
How do I remove a single override without touching the rest?+
PATCH with null as the map value, for example {"operation_classes": {"extraction": null}}. That removes only the extraction override and restores the platform default for it; every other override in the map survives. On PUT the same effect comes from omitting the key from the submitted map.
Why was my write refused with policy_unsatisfiable?+
Activating the submitted policy would leave at least one registered operation with no compliant route, and the platform refuses to store a policy that would make the workspace's next document fail. The response names the stranded operations. Widen the allowed providers, the region pin, or the denied models, or save as draft while iterating.
Why are some keys camelCase inside an otherwise snake_case body?+
Values inside model_classes (chain, requiredCapabilities, fallback) and the keys of features pass straight through to the shared policy validator, the same one the admin console uses, so one validator defines what a legal policy is. The top-level wire fields are snake_case like the rest of the public API; the inner shapes keep the validator's casing.