What Cypress API testing is genuinely good at

  • Setup and teardown. Creating the state a UI test needs through the API instead of clicking through five screens. Faster, more reliable, and the right use of the capability.

  • Cross-layer assertions. Do something in the interface, then confirm the backend recorded it correctly. This is a real category and Cypress handles it neatly.

  • A handful of endpoint checks. When the API surface is small, keeping it in the same suite is a reasonable trade for the simplicity.

The four problems that arrive with scale

Session lifetime

  • Tokens expire mid-suite and the failures look like product bugs.

  • Refresh logic ends up hand-rolled in a support file.

Values between calls

  • An identifier from a create call has to reach the delete call, and Cypress command chaining makes this awkward across tests.

  • People hardcode, and the suite works once.

Ordering

  • Real API coverage is a graph, and test files are a list. Encoding the graph by hand is where the maintenance goes.

The fourth is cleanup. Records accumulate, uniqueness constraints start firing, and pagination assertions drift as the data grows.

Why these are not really Cypress problems

Cypress was designed around a browser-driven test lifecycle. Everything above is about state that lives outside a single test and outside a single run. You can solve all of it in support files, and the solution becomes a small framework that somebody has to maintain, which is the cost nobody plans for.

What a purpose-built API layer handles instead

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.

Where the support file becomes a framework

There is a recognizable point where an API suite inside Cypress stops being a few requests and becomes infrastructure, and it is worth naming so you can decide deliberately rather than drift.

It starts with a login command in the support file. Then a token cache, because logging in for every test is slow. Then refresh logic, because the cache goes stale mid-run. Then a helper that stores created identifiers so later tests can find them, then ordering conventions in the file names, then a cleanup task that runs after the suite and knows about all of it.

None of those steps is wrong and each is the obvious next move. The result is a few hundred lines of framework living in a support directory, understood by one person, and outside the runner it was built on. Recognizing the trajectory early is the whole value of describing it.

The split worth adopting

Keep cy.request for setup, teardown and the cross-layer assertions that genuinely need both sides in one test. Move standalone endpoint and integration coverage to a layer that treats those four problems as part of its job rather than as your homework.

The two suites do not need to know about each other, and both can run from the same pipeline.

The trigger matters more than the mechanism. Pointing it at your deployment event means every change is checked without anyone deciding to; the GitHub App does that from the dashboard, and a GitHub Actions step does it from inside your workflow. Details in the CLI repository.

What you stop maintaining

The support-file framework described above is the thing TestSprite replaces. Auto-Authentication handles session lifetime. Dynamic Variables carry an identifier from a create call to the delete call. Dependency Chains derive a safe order from what each case needs and produces. Auto-Cleanup removes exactly what the run created, which is what makes parallel execution safe.

Coverage also stops depending on who has time. Plans are generated from a specification or a discovery pass over the running service, including the error paths and boundary values that never get written by hand, and refined in plain language.

Cypress keeps what it is good at: setup, teardown and the cross-layer checks where you genuinely need the browser and the API in one test. The two run from the same pipeline without knowing about each other.

Is cy.request bad practice?

Not at all. It is the right tool for setup and for cross-layer checks. The limit is scale, not correctness.

Can we keep everything in Cypress?

You can, and you will end up maintaining session handling, value capture, ordering and cleanup yourself. Worth it for a small surface, expensive past that.

What about Cypress component testing?

Unaffected by any of this. Component tests answer a different question and are best kept where they are.

How do we avoid duplicate coverage?

Split by question. Cypress answers whether the user-visible flow works. The API layer answers whether the endpoints behave. If a test would pass in both places, it belongs in the cheaper one.

Does the API layer need our source code?

No. It works against the running service, using a specification or a discovery pass to know what exists.

The short version

Keep cy.request for setup, not for the whole API suite.

Cypress API testing is excellent for fixtures and cross-layer assertions. Once standalone API coverage grows, sessions, captured values, ordering and cleanup turn into a framework you maintain by hand, and that is the moment to move that layer somewhere built for it.