Where Python API testing usually starts, and why that is fine
Do not let anyone talk you out of requests and pytest. Tests are ordinary Python, fixtures compose, parametrize covers boundary values cleanly, and everyone on the team can read it. For a focused suite around a service you own, this remains an excellent answer.
The four things that turn into work
Sessions. A token fixture works until it expires mid-run, and then you are writing refresh logic and caching it across workers.
Captured values. An identifier from one call feeds the next. Fixtures handle it until the graph is wide, then the fixture graph becomes the thing you maintain.
Ordering. pytest does not order by dependency. Teams reach for ordering plugins, and a single early failure produces a screen of unrelated red.
The fourth is cleanup under parallelism. As soon as pytest-xdist is in play, deleting by name pattern stops being safe, and teardown has to know exactly what each run created.
The honest cost
None of this is hard individually. Together they become an internal framework: a conftest nobody wants to touch, a fixture graph only one person understands, and a suite that gets quarantined rather than fixed when it goes red. That outcome is common enough to plan around.
What a purpose-built layer handles
Four things decide whether an API suite survives its first year, whichever tool you use: sessions that expire, values that only exist at runtime, calls that depend on each other, and records nobody cleans up. TestSprite names these Auto-Authentication, Dynamic Variables, Dependency Chains and Auto-Cleanup, and the API testing documentation covers the mechanics.
Coverage also comes from a different place. Rather than the endpoints someone remembered, a plan generated from a specification or a discovery pass over the running service includes the error paths and boundary cases that are never written by hand.
If you want the tests in your repository
Some teams want the suite to live in the repo and be reviewed like any other code, and that is a legitimate preference. The CLI supports backend tests written as Python, with dependencies declared per test and teardown marked as its own case, so the graph is explicit rather than implied by fixture nesting. Details are in the CLI repository.
The conftest that ate the team
There is a recognizable shape to a Python suite that has outgrown itself, and it lives in one file. The conftest starts as a session fixture and a base URL. Then a token fixture, then a cache so the token is not fetched per test, then a lock because xdist workers were racing on the cache. Then factories, then a registry so teardown knows what to remove, then ordering markers because some factories depend on others.
Every step is correct in isolation and the result is six hundred lines that one person understands. The tell is when someone says a test is failing and the first question is whether the conftest changed.
This is worth naming because the decision to build it is never taken. It accumulates. Deciding early which parts you intend to own, and which you would rather not, is cheaper than discovering the answer in month nine.
Run it where the code changes
A Python suite that runs before releases catches things late. Running on every pull request is the change that matters.
Two routes, and the choice is really about who owns the pipeline. Connecting the GitHub App from the dashboard needs no change to your repository at all, because it reacts to the deployment your build already emits. Adding a GitHub Actions step puts the check in the repo where it gets reviewed like the rest of the build.
What TestSprite owns so you do not
The four things that turn a pytest suite into infrastructure are handled as product. Auto-Authentication obtains and refreshes sessions per environment. Dynamic Variables carry an identifier from one response into the next request. Dependency Chains derive execution order from what each case needs and produces, so a failure skips its dependents rather than failing them. Auto-Cleanup removes exactly what a run created, which is what makes parallel execution safe.
Coverage arrives from a different direction too. API Discovery enumerates the endpoints the running service actually exposes, and generated plans include the error paths and boundary values that never get written at the end of a sprint.
What you get is a suite nobody is afraid to change, with no conftest that one person understands. Keep pytest for the unit and logic tests where it is genuinely the best tool; this is about where the breadth lives.
Should we stop using pytest?
No. Keep it for the tests your team deliberately wrote, particularly unit and logic-level ones. The argument is about where broad API coverage lives.
What about httpx and async?
Better ergonomics for concurrency, same four problems. Sessions, captured values, ordering and cleanup are properties of testing a stateful service, not of the HTTP client.
How do we handle test data?
Create what each test needs and remove exactly that, tied to the run. Shared seeded data looks convenient and becomes the reason tests only pass in a particular order.
Can generated coverage and pytest coexist?
Yes, and it is the usual arrangement. Generated coverage for breadth, pytest for the cases you specified on purpose.
Is Python still a good choice here?
Yes. The language is not the constraint. The constraint is how much test infrastructure you want to own.
requests and pytest are right until state arrives.
Python API testing starts beautifully and turns into an internal framework around sessions, captured values, ordering and cleanup. Keep pytest for what you deliberately specified, and let a purpose-built layer carry breadth and the four state problems.