# FastAPI (/sdks/python/fastapi)

Use Python 3.11 through 3.14. From an empty directory, create and activate a virtual environment, then create the package used by this guide:

```bash
python -m venv .venv
. .venv/bin/activate
```

```bash
mkdir -p my_app
touch my_app/__init__.py my_app/app.py
```

Install the FastAPI extra. Set the merchant test key in your server environment; the SDK reads `RECUUT_API_KEY` automatically unless you pass a client or `api_key` to the decorator.

```bash
pip install "recuut[fastapi]" uvicorn
```

```bash
export RECUUT_API_KEY="recuut_test_..."
```

Before choosing a contract setup, complete [step 1 of the Direct HTTP guide](/guides/http/#1-create-the-test-payment-policy) to create and publish the resource, revision, Price, and test key. You do not need to build its raw HTTP server.

## Choose your contract setup




Generate a contract when your application can commit generated files. This keeps the revision ID and reporting fields in checked Python types.

After you publish the revision, add this output location to your project `pyproject.toml`:

```toml title="pyproject.toml"
[tool.recuut]
contracts-output-dir = "my_app/recuut_contracts"
```

Then pull the revision contract. The generated payment type makes the reporting fields part of the Python function signature, so a missing or misspelled field is caught by your type checker.

```bash
recuut pull-contracts -v
```

Create `my_app/app.py` using the generated revision and payment types. `TextGenerationV1` and `TextGenerationPayment` stand for the names generated for your published revision.

```python title="my_app/app.py"
from fastapi import FastAPI
from pydantic import BaseModel
from recuut import recuut

from my_app.recuut_contracts import TextGenerationPayment, TextGenerationV1

app = FastAPI()


class GenerateRequest(BaseModel):
    prompt: str


class GenerateResponse(BaseModel):
    text: str


@app.post("/generate")
@recuut(TextGenerationV1)
async def generate(
    payload: GenerateRequest,
    payment: TextGenerationPayment,
) -> GenerateResponse:
    text = payload.prompt.upper()
    payment.report(
        input_tokens=len(payload.prompt.split()),
        output_tokens=len(text.split()),
    )
    return GenerateResponse(text=text)
```

`payment.report(input_tokens=..., output_tokens=...)` is typed from that revision. The SDK validates the values again when it completes the transaction.




Use a raw revision only when your application cannot generate files. Set its explicit revision ID in addition to `RECUUT_API_KEY`:

```bash
export RECUUT_REVISION_ID="acr_..."
export RECUUT_SETTLEMENT_ASSET="USDC"
```

The reporting values are a dictionary, so your Python type checker cannot catch a missing or misspelled contract field.

```python title="my_app/app.py"
import os

from fastapi import FastAPI
from pydantic import BaseModel
from recuut import RawPayment, RawRevision, recuut
from recuut.merchant_api.models import SettlementAsset

app = FastAPI()
revision = RawRevision(
    os.environ["RECUUT_REVISION_ID"],
    SettlementAsset(os.environ["RECUUT_SETTLEMENT_ASSET"]),
)


class GenerateRequest(BaseModel):
    prompt: str


class GenerateResponse(BaseModel):
    text: str


@app.post("/generate")
@recuut(revision)
async def generate(
    payload: GenerateRequest,
    payment: RawPayment,
) -> GenerateResponse:
    text = payload.prompt.upper()
    payment.report(
        {
            "input_tokens": len(payload.prompt.split()),
            "output_tokens": len(text.split()),
        }
    )
    return GenerateResponse(text=text)
```




Start the application:

```bash
uvicorn my_app.app:app --reload
```

Check the unpaid path before you use a wallet:

```bash
curl --include \
  --request POST \
  --url http://127.0.0.1:8000/generate \
  --header 'Content-Type: application/json' \
  --data '{"prompt":"paid testnet response"}'
```

Expect HTTP `402`, a `PAYMENT-REQUIRED` header, and no uppercase result. If the response differs, verify the published revision, active Price, **Require payment** setting, and test key before continuing.

The integration validates normal input before it asks Recuut for access. A successful paid request completes payment before it returns the handler result with `PAYMENT-RESPONSE`. HTTP `202` remains private. A returned `4xx` or `5xx` response fails the transaction and reports only its status, duration, media type, and byte size. The response body and headers are not sent to recuut.

Use the [wallet and Circle faucet preflight](/guides/http/#4-create-and-fund-the-base-sepolia-wallet), then run the [paid AWAL retry](/guides/http/#5-run-the-paid-retry) against this same URL. The [receipt check](/guides/http/#6-verify-the-receipt) is identical.
