---
title: "API Performance Testing Checklist: 12 Things Teams Forget"
description: "Twelve concrete checks before and during API load tests—environment realism, auth, data safety, metrics, and gates—grounded in k6 and release practice."
publishDate: "2026-05-10"
draft: false
coverImage: "/images/performate-ai-report-summary.png"
coverImageAlt: "AI performance report summary highlighting actionable API bottlenecks"
keyword: "api performance testing checklist"
intent: "BOFU"
cta: "Download Performate to execute this checklist with repeatable scenarios, reports, and exports."
tags: ["api-performance", "checklist", "load-testing", "k6"]
---

Your staging run hit 500 concurrent users and checkout stayed green—so the release shipped. Two days later production `p99` doubled because the test never exercised OAuth token refresh, shared sandbox rows collided under write load, and nobody tagged requests by route. Serious API load testing is less about "how many VUs" and more about whether the **question, environment, and metrics** match production risk.

This checklist captures twelve checks teams forget before they scale traffic—especially when stakeholders will read the outcome. Pair it with [stress vs load vs spike](/en/blog/stress-vs-load-vs-spike-testing) for the right traffic shape and [k6 thresholds examples](/en/blog/k6-thresholds-examples) when you define pass/fail.

## Why checklists fail without executable structure

Most performance postmortems trace back to the same gaps:

- **Unnamed decisions:** runs happen because "we should load test," not because a release gate needs a specific answer.
- **Environment lies:** staging without realistic data volume, cache warmth, or network path produces charts nobody should trust.
- **Untagged metrics:** aggregate `http_req_duration` hides which endpoint regressed.
- **Missing abort ownership:** saturation spills outside the sandbox and surprises security or platform on-call.

A checklist only helps when it maps to **k6 scenarios, thresholds, and archived parameters**—not slide decks. The sections below turn each item into something you can verify in script and summary output.

## Practical k6 implementation: checklist encoded in options

The example script embeds checklist items as tags, thresholds, and setup hooks so pass/fail is objective—not a meeting opinion.

**Example script (illustrative—not a production-ready test).** Adapt URLs, auth, datasets, and thresholds to your environment.

**What this example demonstrates:**

- **Decision tag:** `decision:release-gate-checkout` documents why the run exists.
- **Route-scoped thresholds:** checkout gets tighter SLOs than a health probe.
- **Setup isolation:** `setup()` fetches a dedicated test tenant token instead of reusing shared credentials.
- **Smoke-first duration:** two-minute ramp before the main stage—item 6 on the checklist.

```javascript
import http from 'k6/http';
import { check, sleep } from 'k6';

const BASE = __ENV.API_BASE || 'https://staging.example.com';
const DECISION = __ENV.TEST_DECISION || 'release-gate-checkout';

export function setup() {
  // Checklist #3: freeze credentials — dedicated tenant per run
  const authRes = http.post(`${BASE}/oauth/token`, {
    grant_type: 'client_credentials',
    client_id: __ENV.CLIENT_ID,
    client_secret: __ENV.CLIENT_SECRET,
    scope: 'checkout:write',
  });
  check(authRes, { 'token issued': (r) => r.status === 200 });
  return { token: authRes.json('access_token') };
}

export const options = {
  tags: { decision: DECISION, env: __ENV.ENV_NAME || 'staging' },
  scenarios: {
    smoke_auth: {
      executor: 'constant-vus',
      vus: 5,
      duration: '2m',
      tags: { phase: 'smoke', route: 'checkout' },
      exec: 'checkout',
    },
    load_checkout: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '3m', target: 40 },
        { duration: '10m', target: 40 },
        { duration: '2m', target: 0 },
      ],
      tags: { phase: 'load', route: 'checkout' },
      exec: 'checkout',
      startTime: '2m',
    },
  },
  thresholds: {
    'http_req_duration{route:checkout}': ['p(95)<800', 'p(99)<1200'],
    'http_req_failed{route:checkout}': ['rate<0.01'],
    'checks{phase:smoke}': ['rate>0.99'],
  },
};

export function checkout(data) {
  const idempotencyKey = `load-${__VU}-${__ITER}-${Date.now()}`;
  const body = JSON.stringify({ sku: 'SKU-100', qty: 1 });

  const res = http.post(`${BASE}/v2/checkout`, body, {
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${data.token}`,
      'Idempotency-Key': idempotencyKey,
    },
    tags: { route: 'checkout', decision: DECISION },
  });

  check(res, {
    'checkout 2xx': (r) => r.status >= 200 && r.status < 300,
    'no timeout': (r) => r.status !== 0,
  });
  sleep(0.5);
}
```

**Pro tip (example command):** archive parameters with the run for checklist item 11.

```bash
TEST_DECISION=release-gate-checkout ENV_NAME=staging-v4 k6 run checkout-checklist.js --summary-export=run-evidence.json
```

## The 12 checks: decision table

| # | Check | Pass signal | Common miss |
|:---|:---|:---|:---|
| 1 | Name the decision the run supports | One-sentence gate documented in tags or README | "See how it handles load" |
| 2 | Match environment fidelity to the question | Missing fidelity listed in run notes | Treating staging charts as prod forecasts |
| 3 | Freeze URLs, tenants, credentials | Env vars recorded; no mystery sandboxes | Rotating keys mid-run |
| 4 | Isolate or reset test data | Idempotency keys or dedicated prefixes | Shared rows → flaky "regressions" |
| 5 | Tag requests for post-run diagnosis | Per-route `http_req_duration` in summary | One aggregate latency line |
| 6 | Smoke before marathon | Short scenario passes checks first | Debugging script bugs at scale |
| 7 | Tie thresholds to product risk | Tighter SLO on checkout vs admin | Vanity hero numbers |
| 8 | Separate functional from load proof | Contract tests in CI; load answers concurrency | Green functional = ship |
| 9 | Watch errors by type | Timeouts vs 5xx vs business soft-fails split | Single `http_req_failed` blob |
| 10 | Correlate server-side when allowed | APM window matches test duration | Optimizing scripts not systems |
| 11 | Capture scenario params with report | VU model, duration, git SHA archived | Chart with no context |
| 12 | Assign abort owner | Named contact + comms path to on-call | Surprise saturation |

**Use strict thresholds on revenue paths** (items 7–9). **Use lighter gates on internal admin** unless the decision explicitly covers those routes.

**Use smoke-first staging** (items 5–6) before any marathon soak or stress campaign ([common mistakes](/en/blog/common-load-testing-mistakes)).

**Use correlation** (item 10) with [API bottleneck analysis](/en/blog/api-bottleneck-analysis) when APM exists for the same window.

## Pre-run checklist (copy before you scale)

Before you start the main scenario:

- [ ] Decision sentence written: "After this test we will approve/reject ______ because we measured ______."
- [ ] Environment gaps documented (data volume, cache warmth, network path).
- [ ] Base URL, OAuth flow, and tenant per VU recorded in env file—not chat.
- [ ] Test data isolation plan confirmed with platform (prefixes, resets, idempotency).
- [ ] Tags defined for every route under test (`route:checkout`, etc.).
- [ ] Smoke scenario scheduled with strict check thresholds.
- [ ] k6 thresholds encode product risk, not best-day weekend numbers.
- [ ] Abort owner named with authority to stop run and notify on-call.
- [ ] Server-side dashboards open for the same time window (if policy allows).
- [ ] Export path chosen for summary JSON + git/build identifiers.

Google's SRE material frames **aligned incentives between SLIs and real user pain**—your environment contract and checklist should reflect that ([alerting on SLOs](https://sre.google/workbook/alerting-on-slos/)).

## How Performate helps you execute this checklist

Boilerplate kills compliance with items 1–12. Below is a **concrete workflow example** for the checkout release gate this article discusses.

**Example: run the checklist on a Postman collection without losing tags or evidence**

1. **Import the collection** that product already trusts for functional tests. *Problem solved:* checklist item 3—URLs and auth shapes stay aligned with daily QA.
2. **Add tags per request** in the scenario panel: `route:checkout`, `decision:release-gate-checkout`. *Problem solved:* item 5—summaries slice latency by endpoint automatically.
3. **Configure smoke then load** as two scenarios—2 minutes at 5 VUs, then ramp to 40. *Problem solved:* item 6 without hand-editing executor blocks.
4. **Set thresholds in the UI** matching negotiated SLO (`p95`, error rate). *Problem solved:* item 7—objective pass/fail visible before export.
5. **Run and open the integrated report**; split errors by type (timeouts vs 5xx). *Problem solved:* items 9 and 11 in one view.
6. **Export k6 script + summary** for CI and attach git SHA from your release branch. *Problem solved:* evidence survives the meeting; reruns compare apples to apples.

Starting from Postman? Follow [Postman to k6 step-by-step](/en/blog/postman-to-k6-step-by-step) first so imports do not skip critical variables.

That workflow maps to the `cta` in this post: execute the checklist with repeatable scenarios, reports, and exports in one desktop workflow.

## Closing takeaway

A load test without a decision, tags, and archived parameters is a demo—not evidence. Walk the twelve checks before you scale; encode the important ones in k6 options so pass/fail survives the release meeting.

Run smoke on your next candidate build this week—and confirm checkout tags, thresholds, and abort owner are named before anyone asks for "just five more minutes at 2× traffic."

[Try Performate free](https://performate.app) | [Book a demo](/demo) | [k6 scenarios documentation](https://k6.io/docs/using-k6/scenarios/)
