Ensuring that your APIs perform reliably and securely is just as important as testing the user interface. Robust API testing helps verify that the system works as expected, can handle real-world traffic, and is secure from potential threats. While the Cypress test automation framework is widely recognized for its powerful UI end-to-end testing capabilities, it’s also well-suited for API testing.
In this blog post, we will walk you through a practical example using the Fake Store API backend to demonstrate how Cypress can be effectively applied to test APIs.
By the end of this post, you’ll learn how to use Cypress to:
- Call common HTTP request methods
- Add meaningful assertions to your tests
- Handle authentication processes
- Explore advanced API automation examples
- And maybe a few extra tips along the way.
If you’re already using Cypress for end-to-end testing in your project, adding API tests to your setup is a seamless and valuable enhancement that can significantly improve your test coverage.
If you want to skip all the steps and just jump to the finished project, you can find the link to the GitHub repository code here.
1. Setting up an API test folder
We will assume that you have already installed Cypress, but if you haven’t, don’t worry, this post explains exactly how to do that. Once you’ve set up Cypress, the first step is to keep our API tests separate from the UI tests by placing them in their own folder (e.g., cypress/e2e/api/). This separation ensures easier test execution and maintenance later on. So the project structure will look something like this:
cypress/
e2e/
api/
products.cy.js
users.cy.js
ui/
2. Using cy.request() & cy.api()
cy.request()
Out of the box, Cypress offers the ability to send requests using the default cy.request() command, which allows us to send all kinds of requests. Here’s what it looks like:
cy.request('GET', '/users').then((response) => {
expect(response.status).to.eq(200);
});

Awesome, the test passed, and everything seems great!
However, do you notice the blank white page on the right? That’s because Cypress primarily focuses on testing the web page's visible part—the user interface. In this case, we are only running API calls, and there’s no webpage to load. Our requests are made in the background, but we can still confirm that the request was successfully sent and that a status 200 was returned in the response.
cy.api()
We want to replace the blank page with something more user-friendly and meaningful. Luckily, there’s a cypress-plugin-api package that prints out information about the API call in the Cypress UI.
So let’s go ahead and install the package (shoutout to the creator Filip Hric):
npm i cypress-plugin-api
Next, we can import the plugin into cypress/support/e2e.js file using the code snippet below. Check out this demo file for reference.
import 'cypress-plugin-api'
Great! Now we can use the cy.api() command. If we run the same test with cy.api(), we’ll see that the blank page has been replaced with a nice, interactive UI that shows detailed information about the API call, such as the response, headers, and cookies.
cy.api('GET', '/users').then((response) => {
expect(response.status).to.eq(200);
});
3. Adding assertions
A basic check for a 200 status code is a good start, but it’s far from a complete test. We can go further by adding assertions for the response body and headers, like so:
cy.api('GET', '/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.headers['content-type']).to.include('application/json');
expect(response.body).to.be.an('array');
expect(response.body.length).to.be.greaterThan(0);
});

Flawless! But what about performance? Can we include some non-functional assertions as well? Absolutely. For example, we can write a test that sends 10 requests in a row and asserts that each response takes less than half a second:
Cypress._.times(10, () => {
cy.api('GET', '/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.duration).to.be.lessThan(500);
});
});
In the example above, we used the Lodash (Cypress._) library to loop the request 10 times. Since Lodash is built into Cypress by default, you can use it right away—no extra setup required.
4. Handling authentication
What if the application you’re testing requires authentication, like an OAuth 2.0 login, to access the API? In that case, you’ll need to retrieve a login token and reuse it for subsequent requests.
First, let’s send a POST request to the login endpoint and store the token as an environment variable:
cy.api('POST', '/auth/login', {
username: 'some_user',
password: 'some_password',
}).then((response) => {
expect(response.status).to.eq(200);
Cypress.env('authToken', response.body.token); // Store token variable
});
Once the token is stored, it can be reused across other requests by attaching it to the Authorization header:
cy.api({
method: 'GET',
url: '/carts/user/1',
headers: {
Authorization: `Bearer ${Cypress.env('authToken')}`,
},
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array');
});
This approach helps you streamline authenticated API testing by securely reusing tokens and keeping your test logic clean and modular.
5. Testing negative scenarios
It’s important to validate that your application responds correctly to invalid input or unauthorized access. Sometimes, we want requests to fail on purpose—for example, to confirm that a 401 Unauthorized status code is returned when incorrect login credentials are used.
To test this kind of negative scenario, we can reuse the login request and intentionally provide the wrong username and password:
cy.api({
method: 'POST',
url: '/auth/login',
body: {
username: 'wrong_username',
password: 'wrong_password',
},
failOnStatusCode: false,
}).then((response) => {
expect(response.status).to.eq(401);
});
As you may have noticed in the code above, we’ve set the failOnStatusCode flag to false. By default, Cypress automatically fails tests that return non-2xx status codes. Since a 401 is the expected result in this scenario, we disable this automatic failure to properly assert and handle the response.
6. Using fixtures
Fixtures in Cypress help you keep your test data separate, clean, and reusable, especially when working with long or structured API request payloads.
Let’s walk through an example where we want to create a new user using a data fixture. First, navigate to the cypress/fixtures/ folder and create a new file called newUser.json with some test data for the user:
{
"id": 0,
"username": "new_user_2025",
"email": "[email protected]",
"password": "securePassword123",
"name": {
"firstname": "John",
"lastname": "Doe"
}
}
Now, in your test, you can use the cy.fixture() command to load this data and keep your test code clean and readable—rather than hardcoding long data objects directly in the test:
cy.fixture('newUser').then((user) => {
cy.api('POST', '/users', user).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('id');
});
});
Using fixtures not only improves readability but also makes it easier to reuse and manage test data across multiple test cases.
7. Chaining requests
In many test scenarios, it's useful—or even necessary—to chain multiple API requests, especially when later requests depend on the data returned from earlier ones.
For example, in our fake store API, we may want to first retrieve a list of products and then use data from that list to fetch detailed information about a specific product. Here’s how we can chain these requests:
// Step 1: Get all products
cy.api('GET', '/products').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.be.an('array').and.not.be.empty;
// Step 2: Store the first product ID as `productId`
const firstProductData = response.body[0];
const firstProductId = firstProductData.id;
// Step 3: Fetch first product by ID
cy.api('GET', `/products/${firstProductId}`).then((singleResponse) => {
expect(singleResponse.status).to.eq(200);
expect(singleResponse.body).to.have.property('id', firstProductId);
expect(singleResponse.body.title).to.eq(firstProductData.title);
expect(singleResponse.body.price).to.eq(firstProductData.price);
});
});
By chaining requests like this, you can validate not only that endpoints work in isolation but that they interact correctly with each other and maintain data integrity across the flow.
Final thoughts
As we’ve seen throughout this post, Cypress is much more than a UI testing framework—it’s also a powerful and approachable tool for API testing. With the help of the cy.api() command (enabled via the cypress-plugin-api), Cypress brings visibility and structure to backend requests that are often hidden from view.
Using our example project, we demonstrated how Cypress can help you:
- Send standard HTTP requests with ease
- Visualize API traffic directly in the Cypress UI using cy.api()
- Write clear and meaningful assertions
- Handle authentication and reuse tokens across multiple tests
- Test negative scenarios like failed logins
- Use fixtures to keep test data clean and maintainable
- Chain requests to simulate real-world workflows
Cypress makes it seamless to combine UI and API testing into one unified automation strategy—especially if you're already using it in your project. And by applying the best practices we've covered, you can ensure your API tests are just as reliable, readable, and maintainable as your end-to-end tests.
Whether you're just getting started or looking to enhance your test suite, we hope this guide gives you the confidence and tools to explore API testing with Cypress.
Ready to try it yourself? Check out the complete example project on GitHub and start building smarter tests today. Happy testing!
Need help leveling up your test automation? Whether you’re just starting or looking to build a full-fledged automation strategy, we’re here to help. Get in touch to see how our testing services can help streamline your QA process and boost software quality from day one.