> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rotascale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authorise an action

> One call before a consequential action, and how to handle each of the four ways it can refuse.

## The call

```python theme={"system"}
decision = rs.authorize(
    "grt_01KZXYD62VG0",
    {"tool": ["payments.settle"]},
    amount_minor=250_00,
    currency="EUR",
    trajectory_id=t.id,
)
```

The first two arguments are positional: the grant, and the scope of what is
being attempted. Everything after is keyword-only.

By default `authorize` **raises** on a refusal. That is deliberate: the unsafe
path is the one you have to write on purpose, rather than the one you get by
forgetting to inspect a return value.

```python theme={"system"}
decision = rs.authorize(..., raise_on_refusal=False)
if decision.allowed:
    settle(...)
```

## Handling the four refusals

Each maps to an exception, and they are different findings with different
remedies. Collapsing them into one `except` throws away the diagnosis.

```python theme={"system"}
from rotascale import Blocked, Exhausted, Gated, ReviewRequired, EnforcementUnavailable

try:
    rs.authorize("grt_01KZXYD62VG0", {"tool": ["payments.settle"]},
                 amount_minor=250_00, currency="EUR")
    settle(...)

except Exhausted:
    # Permitted, but the allowance is spent. Retrying cannot help, and the
    # agent is told so rather than being left to loop.
    escalate("ceiling reached")

except Gated:
    # Untrusted content entered this trajectory and this action requires a
    # clean one. See /concepts/clean-context.
    escalate("context not clean")

except ReviewRequired as exc:
    # A person has to decide. Park on exc.review_id and come back.
    park(exc.review_id)

except Blocked:
    # Policy refused it. This is the one that is a policy conversation.
    stop()

except EnforcementUnavailable:
    # Rotascale could not be reached. Enforcement fails CLOSED by default.
    stop()
```

<Warning>
  `Exhausted` and `Gated` both subclass `Blocked`, so order your `except` clauses
  from specific to general. A bare `except Blocked` first will swallow all three
  and you will lose the reason.
</Warning>

## What comes back on success

```python theme={"system"}
Decision(
    outcome="allow",
    allowed=True,
    reason="within grant",
    grant_id="grt_01KZXYD62VG0",
    ledger_id="led_01KZZWKNEPCJ",
    remaining_amount_minor=2475000,
    remaining_count=39,
    policy_outcome="allow",
    enforcement_mode="enforce",
)
```

`policy_outcome` is what the policy decided **before** the grant's enforcement
mode was applied, and `enforcement_mode` is the mode itself. In `observe` and
`shadow` those two diverge, and the divergence is the entire value of running
those rungs: it is what the control would have done.

`ledger_id` is the handle for that decision in the record. Keep it if you need
to settle later.

## When Rotascale is unreachable

Capture fails **open** and enforcement fails **closed**. A recording problem
must not cause an outage; an unreachable control plane must not silently permit
an ungoverned action.

Teams that cannot accept a closed failure set `fail_open_enforcement` and own
that explicitly. The SDK logs at `error` level and the decision comes back with
`outcome="unavailable"`, so the choice is at least a recorded one rather than
an accident.

## Next

<Card title="The six outcomes" icon="list-check" href="/concepts/outcomes">
  What each one means and where it sends an operator.
</Card>
