A generated client
The Recurso Python client (packagerecurso, version 1.4.0) is generated by
openapi-python-client.
Its shape is deliberately different from the Node and Go SDKs: there are no
resource objects like client.plans.create(...). Instead, every API operation
is its own module with plain functions, and every request/response body is a
model class.
If you’ve read the Node or Go guides, don’t translate them literally. The
generated idiom is
operation_module.sync(client=client, body=Model(...)).
Follow the shapes on this page.Install
The client isn’t on PyPI yet. Install it from a checkout of therecurso-python repo (which sits beside the other Recurso repos):
httpx and attrs.
Construct a client
There are two client classes in the top-levelrecurso package. Use
AuthenticatedClient for the API (every endpoint requires a key); Client is
the unauthenticated variant.
base_url and token are the two arguments you need. Under the hood the client
sends Authorization: Bearer <token> (the prefix defaults to "Bearer" and
auth_header_name to "Authorization").
Point base_url wherever your API runs:
- Recurso cloud:
https://api.recurso.dev - Self-hosted: your own host, e.g.
https://billing.internal.example.com - Local
make demostack:http://localhost:8080
The generated idiom
Each path + method becomes a module underrecurso.api.<tag>. Import the module
(not a symbol from it) and call one of its four functions. Request bodies are
model classes under recurso.models.
Notes that trip people up:
clientis always a keyword argument (client=client).- The request body is passed as
body=...; path parameters like anidare positional (e.g.get_plan.sync(plan_id, client=client)). - Model classes are re-exported from
recurso.models, sofrom recurso.models import CreatePlanRequestworks. Enum fields have their own classes (e.g.CreatePlanRequestIntervalUnit.MONTH). - Optional fields default to
UNSET(fromrecurso.types) — omit them and they aren’t sent. - Amounts are integers in the currency’s minor unit (
2900= $29.00).
End-to-end example
Create a plan, a customer, and a subscription, then read the plan back and list plans. IDs returned on models areuuid.UUID values and can be passed straight
into the next request.
1
Create a plan
2
Create a customer
3
Create a subscription
customer_id and plan_id take UUID values — pass the IDs from the models
you just created.4
Read the plan back
Path parameters are positional. The single-resource GET wraps the object in a
response envelope whose payload is on
.data.5
List plans
List operations take
q, limit, and a 1-based page. The results live on
.data.Error handling
The client setsraise_on_unexpected_status=True (a deliberate deviation from
the generator’s default), so the two failure modes are:
-
Documented non-2xx statuses (e.g.
400,401,404) do not raise. They parse into anErrormodel, sosyncreturns a union — for examplePlan | Error | None. Guard withisinstance: -
Undocumented statuses raise
recurso.errors.UnexpectedStatus, which carries.status_codeand.content:
sync_detailed and read
response.status_code (an http.HTTPStatus); the typed body, if any, is on
response.parsed:
httpx.TimeoutException.
Pagination
List operations acceptlimit and a 1-based page (plus an optional q
search string); some also take a filter such as status. Page through by
incrementing page until a short page comes back:
Idempotency
Mutating calls honor theIdempotency-Key header so a retry settles at most
once. Attach it with with_headers, which returns a new client:
Async
Every operation hasasyncio / asyncio_detailed twins with identical
arguments. Use the client as an async context manager:
Next
Bill a customer
Customer → plan → subscription → first invoice, end to end.
Usage-based billing
Define a metric, record usage, and rate it.