---
title: "Explain Policy"
method: POST
path: "/v1/routing/policies/explain"
tags: ["routing"]
---

# Explain Policy

`POST /v1/routing/policies/explain`

Compile a policy and return the plan, without dispatching anything.

Master-key gated, and deliberately so: the response enumerates the policy's
targets, which is exactly the information a policy exists to keep off the wire.
It is a management surface, not a caller-facing one.

Accepts an unsaved ``spec`` as well as a saved ``name``, so a form can validate
what the operator is about to save. The response includes dropped candidates
with reasons, which is the part that catches a "failover" policy that has
quietly compiled down to a single attempt.

## Request body

- ExplainRequest — Ask what a policy would do, without dispatching anything. Either name a stored/configured policy (``name``) or pass a draft ``spec`` that has not been saved. The draft form is what makes authoring-time validation possible: the compiler filters candidates, so a chain can compile down to one attempt, and an author needs to see that before saving rather than during an outage.
  - `allowed_models` string[], nullable — Simulate an API key's allow-list. Omit for unrestricted.
  - `budget_remaining_usd` number, nullable — Simulated budget remaining, USD.
  - `budget_used_pct` number, nullable — Simulated budget usage percentage.
  - `key_id` string, nullable — Evaluate conditions as this API key id.
  - `name` string, nullable — An existing policy to explain.
  - `spec` object, nullable — An unsaved policy body to explain.
  - `user_id` string, nullable — Evaluate conditions as this user.

## Response `200`

Successful Response

- ExplainResponse — The plan a policy compiles to for the given inputs.
  - `candidates` CandidateResponse[], required
    - `dispatch_model` string, required
    - `instance` string, required
    - `model` string, required
    - `position` integer, required
    - `selection_reason` string, required
  - `dropped` DroppedResponse[], required
    - `detail` string, required
    - `reason` string, required
    - `selector` string, required
  - `guardrails` object[], required
  - `is_dynamic` boolean, required
  - `name` string, required
  - `router_backend` string, nullable
  - `router_candidates` string[]
  - `router_weights` object — For a weighted policy, the percentage of traffic each candidate receives, normalized over the candidates this caller may use. Empty for every other policy, and for a weighted policy whose whole split this caller may not use: a split over no candidate is not a split, and each filtered candidate is named in `dropped` instead. A weighted split needs no request state, so unlike a learned router's ranking it is knowable here: the plan above is the real ordering by share, not the decline path.
  - `selection_reason` string, required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.net/mozilla-ai/apis/otari.md) · [All operations](https://skmtc.net/mozilla-ai/apis/otari/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/mozilla-ai/otari/revisions/2ebadef75cc8/schema)
