# Review payments

Release payments that are clearly the owner's own. Send everything else to a person.

Category: Review payments and posts. Live demo: https://levanto.ai/examples/payment-review

Most payments are routine: the same rent, to the same landlord, from the same phone. A few are not, such as a large transfer to a new payee minutes after a password reset. This guide asks Sage2 **one strict question** about each payment and releases only the ones it is confident about.

Everything else goes to **a person, before the money moves**.

## Setup

Base URL: `https://sage.levanto.ai`. Send `Authorization: Bearer $SAGE_API_KEY` on every call. Get a key, free, at https://platform.levanto.ai. Keep the key on your server.

A `null` answer is not an error: Sage2 isn't sure. Each step says what to do with it.

## Step by step

### 1. Ask if it's safe to release

Send what a reviewer would look at as `content`: the account's history, the device and location, recent account changes, and the payment itself.

The question is phrased so that **only a clear case is a yes**: "clearly the account owner's own, and safe to release without a person checking it". An unusual payment gets a no.

The `result` has `answer` (`yes`, `no` or `null`) and `probability`, the chance the answer is yes.

```python
payment = """Customer since 2022. Average payment 80 EUR.
Password reset 12 minutes ago. New device in another country.
Payment: 9,400 EUR to a payee added 4 minutes ago."""

result = sage("/decide", {
    "content": payment,
    "question": {
        "id": "safe_to_release",
        "kind": "yesno",
        "instructions": "Is this payment clearly the account owner's own, and safe to release without a person checking it?",
    },
})["result"]
```

```typescript
const payment = `Customer since 2022. Average payment 80 EUR.
Password reset 12 minutes ago. New device in another country.
Payment: 9,400 EUR to a payee added 4 minutes ago.`;

const result = (await sage("/decide", {
  content: payment,
  question: {
    id: "safe_to_release",
    kind: "yesno",
    instructions: "Is this payment clearly the account owner's own, and safe to release without a person checking it?",
  },
})).result;
```

Response, abridged (a real run):

```json
{
  "id": "safe_to_release",
  "kind": "yesno",
  "result": {
    "answer": "no",
    "probability": 0.0001
  },
  "meta": {
    "model": "levanto-sage-2",
    "latency_ms": 3.2,
    "question_count": 1,
    "usage": {
      "billed_input_tokens": 108,
      "image_count": 0,
      "image_tokens": 0,
      "input_tokens": 108,
      "output_tokens": 0
    }
  }
}
```

### 2. Release only above your bar

Release a payment only when the answer is `yes` **and** `probability` is at or above a bar you choose. Everything else goes to the review queue:

- a `no`,
- a `null`, when Sage2 isn't sure,
- a `yes` below your bar.

The payment in this recipe, from a new device to a new payee, goes to the queue. The monthly rent sample comes back yes at about 99%, so it is released.

A higher bar sends more payments to a person.

```python
BAR = 0.98  # release only when Sage is at least this sure

if result["answer"] == "yes" and result["probability"] >= BAR:
    print("Release")
else:
    # "no", null (not sure), or a "yes" below the bar: a person checks it
    print("Review queue:", result)
```

```typescript
const BAR = 0.98; // release only when Sage is at least this sure

if (result.answer === "yes" && result.probability >= BAR) {
  console.log("Release");
} else {
  // "no", null (not sure), or a "yes" below the bar: a person checks it
  console.log("Review queue:", result);
}
```

## Before you ship

- **Start strict.** Begin with a high bar. Lower it only after you check what the review queue catches.
- **Send the context.** Sage2 only knows what is in `content`. Include account age, usual amounts, device and recent changes.
- **Fail closed.** If the Sage2 call errors or times out, hold the payment for review.
- **Keep fixed rules in code.** Hard limits and blocklists stay deterministic. Sage2 handles the cases between them.
- **Fast and small.** One check reads about 110 tokens and answers in about 130 ms.

## Full recipe (Python, `pip install requests`)

```python
import os
import requests

def sage(path, body):
    r = requests.post(
        "https://sage.levanto.ai" + path,
        headers={"Authorization": f"Bearer {os.environ['SAGE_API_KEY']}"},
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    return r.json()

payment = """Customer since 2022. Average payment 80 EUR.
Password reset 12 minutes ago. New device in another country.
Payment: 9,400 EUR to a payee added 4 minutes ago."""

result = sage("/decide", {
    "content": payment,
    "question": {
        "id": "safe_to_release",
        "kind": "yesno",
        "instructions": "Is this payment clearly the account owner's own, and safe to release without a person checking it?",
    },
})["result"]

BAR = 0.98  # release only when Sage is at least this sure

if result["answer"] == "yes" and result["probability"] >= BAR:
    print("Release")
else:
    # "no", null (not sure), or a "yes" below the bar: a person checks it
    print("Review queue:", result)
```

## Full recipe (TypeScript, Node 22+, nothing to install: save as `recipe.mts`, run `npx tsx recipe.mts`)

```typescript
// Node 22+. Save as recipe.mts and run npx tsx recipe.mts
async function sage(path: string, body: unknown): Promise<any> {
  const r = await fetch("https://sage.levanto.ai" + path, {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SAGE_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(30_000),
  });
  if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
  return r.json();
}

const payment = `Customer since 2022. Average payment 80 EUR.
Password reset 12 minutes ago. New device in another country.
Payment: 9,400 EUR to a payee added 4 minutes ago.`;

const result = (await sage("/decide", {
  content: payment,
  question: {
    id: "safe_to_release",
    kind: "yesno",
    instructions: "Is this payment clearly the account owner's own, and safe to release without a person checking it?",
  },
})).result;

const BAR = 0.98; // release only when Sage is at least this sure

if (result.answer === "yes" && result.probability >= BAR) {
  console.log("Release");
} else {
  // "no", null (not sure), or a "yes" below the bar: a person checks it
  console.log("Review queue:", result);
}
```

## Requests

### 1. POST /decide

```bash
curl -s https://sage.levanto.ai/decide \
  -H "Authorization: Bearer $SAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<'JSON'
{
  "content": "Customer since 2022. Average payment 80 EUR.\nPassword reset 12 minutes ago. New device in another country.\nPayment: 9,400 EUR to a payee added 4 minutes ago.",
  "question": {
    "id": "safe_to_release",
    "kind": "yesno",
    "instructions": "Is this payment clearly the account owner's own, and safe to release without a person checking it?"
  }
}
JSON
```

Response, abridged (a real run):

```json
{
  "id": "safe_to_release",
  "kind": "yesno",
  "result": {
    "answer": "no",
    "probability": 0.0001
  },
  "meta": {
    "model": "levanto-sage-2",
    "latency_ms": 3.2,
    "question_count": 1,
    "usage": {
      "billed_input_tokens": 108,
      "image_count": 0,
      "image_tokens": 0,
      "input_tokens": 108,
      "output_tokens": 0
    }
  }
}
```

## Docs

- Yes/No: https://docs.levanto.ai/decision-model/yesno
- Limits and errors: https://docs.levanto.ai/decision-model/limits
- OpenAPI: https://sage.levanto.ai/openapi.json
