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