Python SDK

Use the maintained FastAPI integration to protect a handler and keep its result private until payment completes.

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:

python -m venv .venv. .venv/bin/activate
mkdir -p my_apptouch 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.

pip install "recuut[fastapi]" uvicorn
export RECUUT_API_KEY="recuut_test_..."

Before choosing a contract setup, complete step 1 of the Direct HTTP guide 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:

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.

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.

my_app/app.py
from fastapi import FastAPIfrom pydantic import BaseModelfrom recuut import recuutfrom my_app.recuut_contracts import TextGenerationPayment, TextGenerationV1app = FastAPI()class GenerateRequest(BaseModel):    prompt: strclass 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.

Start the application:

uvicorn my_app.app:app --reload

Check the unpaid path before you use a wallet:

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, then run the paid AWAL retry against this same URL. The receipt check is identical.