ReqRes Blog

API Testing in CI/CD - Skip the Mock Server, Use a Real API

Feb 6, 2026 3 min read
ci-cd automation github-actions qa api-testing devops
Next step

Want to try this in 2 minutes? Start a project or open the Notes example.

Adding API tests to CI shouldn't mean maintaining mock servers, Docker containers, or fragile test fixtures. ReqRes is a free hosted API that's always available, returns stable payloads, and supports auth - so your CI tests run reliably without infrastructure overhead. Here's how to wire it up.

Why do CI API tests need a stable external API?

Common approaches create friction:

  • Local mock servers add Docker/startup complexity, slow down pipelines, and diverge from real API behaviour
  • Shared staging environments are flaky, slow, and block other teams
  • Inline mocking (Jest mocks, page.route) only tests your mocks, not your actual integration

What you want: an API that's always up, returns predictable data, supports auth, and doesn't require any setup in CI.

ReqRes gives you exactly that - a real REST API you can hit from any CI runner without provisioning anything.

How do I test APIs in GitHub Actions?

Here are three examples using common tools.

With cURL (simplest)

name: API Smoke Tests
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Test API is reachable
        run: |
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
            -H "x-api-key: ${{ secrets.REQRES_API_KEY }}" \
            https://reqres.in/api/users?page=2)
          if [ "$STATUS" != "200" ]; then exit 1; fi

      - name: Test user creation
        run: |
          RESPONSE=$(curl -s -X POST \
            -H "x-api-key: ${{ secrets.REQRES_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"name":"CI Test","job":"Pipeline"}' \
            https://reqres.in/api/users)
          echo "$RESPONSE" | jq -e '.id' > /dev/null

With Newman (Postman CLI)

- name: Run Postman collection
  run: |
    npm install -g newman
    newman run tests/api-collection.json \
      --env-var "api_key=${{ secrets.REQRES_API_KEY }}" \
      --env-var "base_url=https://reqres.in"

With Playwright

- name: Run Playwright API tests
  run: npx playwright test tests/api/
  env:
    REQRES_API_KEY: ${{ secrets.REQRES_API_KEY }}

Your Playwright test file:

import { test, expect } from '@playwright/test'

test('GET /api/users returns 200', async ({ request }) => {
    const response = await request.get('https://reqres.in/api/users?page=2', {
        headers: {
            'x-api-key': process.env.REQRES_API_KEY!,
        },
    })
    expect(response.status()).toBe(200)
    const data = await response.json()
    expect(data.data).toBeInstanceOf(Array)
})

test('POST /api/users returns 201', async ({ request }) => {
    const response = await request.post('https://reqres.in/api/users', {
        headers: {
            'x-api-key': process.env.REQRES_API_KEY!,
            'Content-Type': 'application/json',
        },
        data: { name: 'CI Test', job: 'Pipeline' },
    })
    expect(response.status()).toBe(201)
    const body = await response.json()
    expect(body.id).toBeDefined()
})

How do I test APIs in GitLab CI?

The same patterns work in .gitlab-ci.yml:

With cURL

stages:
  - test

api-smoke-test:
  stage: test
  image: alpine:latest
  before_script:
    - apk add --no-cache curl jq
  script:
    - |
      STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
        -H "x-api-key: $REQRES_API_KEY" \
        https://reqres.in/api/users?page=2)
      if [ "$STATUS" != "200" ]; then exit 1; fi
    - |
      RESPONSE=$(curl -s -X POST \
        -H "x-api-key: $REQRES_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"name":"CI Test","job":"Pipeline"}' \
        https://reqres.in/api/users)
      echo "$RESPONSE" | jq -e '.id' > /dev/null

With Newman

api-collection-test:
  stage: test
  image: node:18
  script:
    - npm install -g newman
    - newman run tests/api-collection.json
        --env-var "api_key=$REQRES_API_KEY"
        --env-var "base_url=https://reqres.in"

With Playwright

playwright-api-test:
  stage: test
  image: mcr.microsoft.com/playwright:v1.40.0-jammy
  script:
    - npm ci
    - npx playwright test tests/api/
  variables:
    REQRES_API_KEY: $REQRES_API_KEY

How do I handle API keys securely in CI?

Store your ReqRes API key as a CI secret - never commit it to code.

GitHub Actions:
Settings → Secrets and variables → Actions → New repository secret → REQRES_API_KEY

GitLab CI:
Settings → CI/CD → Variables → Add variable → REQRES_API_KEY (masked)

Jenkins:
Manage Jenkins → Credentials → Add Credentials → Secret text → REQRES_API_KEY

Reference in your pipeline config via environment variables:

# GitHub Actions
env:
  REQRES_API_KEY: ${{ secrets.REQRES_API_KEY }}

# GitLab CI
variables:
  REQRES_API_KEY: $REQRES_API_KEY

Get your API key at app.reqres.in/api-keys.

What makes a good CI API test suite?

Keep it practical:

Test the happy path first:

  • GET returns 200 with expected data shape
  • POST returns 201 with an ID
  • Auth endpoints return tokens

Test auth failures:

  • Missing x-api-key returns 401
  • Invalid key returns 403

Test edge cases:

  • Invalid payloads return 400
  • Missing resources return 404

Keep tests fast:

  • 10-20 targeted assertions beat 200 slow integration tests
  • Focus on critical paths, not exhaustive coverage

Debug with logs:

  • ReqRes logs every request with headers and response times
  • When tests fail, check logs to see exactly what was received

For detailed endpoint information, see the API documentation.

What about rate limits in CI?

Practical guidance:

  • ReqRes free tier handles CI test suites comfortably for most teams
  • Parallel pipelines across many repos may need higher limits - consider upgrading
  • Add retry logic with exponential backoff (3 retries, starting at 1 second)
  • Don't over-test - run smoke tests on push, full suite on PR/merge

Example retry wrapper for cURL:

retry_curl() {
  local max_attempts=3
  local delay=1
  local attempt=1

  while [ $attempt -le $max_attempts ]; do
    if curl "$@"; then
      return 0
    fi
    echo "Attempt $attempt failed. Retrying in ${delay}s..."
    sleep $delay
    delay=$((delay * 2))
    attempt=$((attempt + 1))
  done
  return 1
}

Get started

Get a free API key and add API tests to your pipeline in under 10 minutes. No mock servers, no Docker, no flaky staging environments.

Ready to ship? Continue in the app.