API Testing: A Complete Guide for Developers

February 11, 2026

APIs are the backbone of modern software. Every time you log into an app, load a dashboard, or submit a form, there is an API call happening behind the scenes. If that API breaks, everything downstream breaks too. API testing is how you make sure that does not happen.

This guide covers everything you need to know about API testing, from foundational concepts to hands-on techniques you can start using today. Whether you are building your first REST endpoint or managing a fleet of microservices, you will find practical, actionable advice here.

1. What Is API Testing?

API testing is the process of sending requests to an API endpoint and verifying that the response is correct. Unlike UI testing, where you interact with buttons and forms, API testing works directly at the data and logic layer. You send an HTTP request, receive a response, and check whether the status code, headers, response body, and timing meet your expectations.

A simple example: you send a GET request to /api/users/42 and verify that you receive a 200 OK response containing a JSON object with the correct user data. That is an API test.

API testing operates at the service layer, which sits between the UI and the database. This gives it a unique advantage: it can catch bugs that UI tests miss (because the UI might mask backend errors) and that unit tests miss (because unit tests do not cover the interaction between components).

⚙ Try it yourself: Use our HTTP Request Tester to send your first API request right in the browser — no installation needed.

2. Why API Testing Matters

There are several reasons why API testing deserves a central place in your testing strategy:

3. Types of API Tests

Not all API tests serve the same purpose. A well-rounded testing strategy includes several different types, each catching a different class of bug.

Unit Tests

Unit tests verify individual functions or methods within your API code. They do not make actual HTTP requests. Instead, they test the logic in isolation, mocking external dependencies like databases and third-party services.

// Example: Unit testing a validation function
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

// Test cases
assert(validateEmail("user@example.com") === true);
assert(validateEmail("invalid-email") === false);
assert(validateEmail("") === false);

Unit tests are fast and pinpoint exactly where a bug is. But they do not tell you whether your API actually works end-to-end.

Integration Tests

Integration tests verify that your API works correctly when all its components are connected. They send real HTTP requests to a running server and check the full request/response cycle, including database interactions, authentication middleware, and serialization.

// Example: Integration test with a real HTTP request
const response = await fetch("http://localhost:3000/api/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Jane", email: "jane@example.com" })
});

assert(response.status === 201);
const user = await response.json();
assert(user.id !== undefined);
assert(user.name === "Jane");

Integration tests are slower than unit tests but catch a wider range of bugs, especially issues in how components interact.

Contract Tests

Contract tests verify that an API conforms to a predefined specification. If your API documentation says GET /users returns an array of objects with id (number), name (string), and email (string), a contract test checks exactly that.

Tools like Pact and Dredd specialize in contract testing. They compare your actual API responses against an OpenAPI/Swagger specification and flag any mismatches. This is especially valuable in microservices architectures, where multiple teams need to stay in sync.

End-to-End (E2E) Tests

E2E tests verify complete user workflows that span multiple API calls. For example, testing a checkout flow might involve: create a user, add items to a cart, apply a coupon, and process a payment. Each step depends on the result of the previous one.

# E2E test: Complete order workflow
# Step 1: Create user
user=$(curl -s -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Test User", "email": "test@example.com"}')
user_id=$(echo $user | jq -r '.id')

# Step 2: Add item to cart
curl -s -X POST "http://localhost:3000/api/cart/$user_id/items" \
  -H "Content-Type: application/json" \
  -d '{"product_id": "SKU-001", "quantity": 2}'

# Step 3: Checkout
order=$(curl -s -X POST "http://localhost:3000/api/orders" \
  -H "Content-Type: application/json" \
  -d "{\"user_id\": \"$user_id\", \"payment_method\": \"card\"}")
echo $order | jq -r '.status'
# Expected: "confirmed"

Load and Performance Tests

Load tests measure how your API performs under stress. They answer questions like: How many concurrent requests can the API handle? What is the 95th percentile response time? At what point does the server start returning errors?

Common metrics to track during load testing:

Tools like k6, Apache JMeter, and Artillery are popular choices for load testing. Start with a baseline test, then gradually increase load to find your breaking point.

Security Tests

Security tests verify that your API is not vulnerable to common attacks. This includes testing for SQL injection, checking that authentication tokens are required, verifying that users cannot access other users' data, and ensuring sensitive fields like passwords are never returned in responses.

4. API Testing Tools

You do not need a complex setup to start testing APIs. Here are the most common tools, from simple to advanced.

curl (Command Line)

curl is the Swiss Army knife of API testing. It is pre-installed on virtually every Unix system and lets you make any type of HTTP request from the command line.

# Simple GET request
curl https://api.example.com/users

# GET with headers
curl -H "Authorization: Bearer YOUR_TOKEN" \
     -H "Accept: application/json" \
     https://api.example.com/users

# POST with JSON body
curl -X POST https://api.example.com/users \
     -H "Content-Type: application/json" \
     -d '{"name": "Alice", "email": "alice@example.com"}'

# See full response headers and body
curl -v https://api.example.com/health

# Save response to file
curl -o response.json https://api.example.com/data

The -v (verbose) flag is especially useful for debugging. It shows you the full request/response cycle, including TLS negotiation, request headers, and response headers.

Postman

Postman is the most popular graphical API testing tool. It provides a visual interface for building requests, organizing them into collections, writing test scripts, and sharing them with your team. Postman also supports environment variables, pre-request scripts, and automated test runs through its CLI tool (Newman).

Key features that make Postman useful for API testing:

Browser-Based Tools

If you need to quickly test an endpoint without installing anything, browser-based HTTP testers let you send requests and inspect responses directly in your web browser.

⚙ Try it: Our HTTP Request Tester lets you send GET, POST, PUT, PATCH, and DELETE requests with custom headers and bodies. The response is displayed with formatted JSON, status codes, and timing — all without leaving your browser.

Programming Language Libraries

For automated testing in CI/CD pipelines, you will use HTTP libraries within your test framework:

5. Making Your First API Request

Let us walk through making API requests using three different approaches: curl, JavaScript, and Python. We will use a public API for the examples.

With curl

# GET request - fetch a list of users
curl -s https://jsonplaceholder.typicode.com/users | head -20

# POST request - create a new resource
curl -s -X POST https://jsonplaceholder.typicode.com/posts \
  -H "Content-Type: application/json" \
  -d '{
    "title": "API Testing Guide",
    "body": "This is a test post created via curl",
    "userId": 1
  }'

# PUT request - update a resource
curl -s -X PUT https://jsonplaceholder.typicode.com/posts/1 \
  -H "Content-Type: application/json" \
  -d '{
    "id": 1,
    "title": "Updated Title",
    "body": "Updated body content",
    "userId": 1
  }'

# DELETE request
curl -s -X DELETE https://jsonplaceholder.typicode.com/posts/1 \
  -w "\nHTTP Status: %{http_code}\n"

With JavaScript (fetch)

// GET request
async function getUsers() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");

  console.log("Status:", response.status);
  console.log("Content-Type:", response.headers.get("content-type"));

  const users = await response.json();
  console.log(`Fetched ${users.length} users`);
  return users;
}

// POST request with error handling
async function createPost(title, body, userId) {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Accept": "application/json"
      },
      body: JSON.stringify({ title, body, userId })
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const post = await response.json();
    console.log("Created post with ID:", post.id);
    return post;
  } catch (error) {
    console.error("Request failed:", error.message);
    throw error;
  }
}

// PATCH request - partial update
async function updatePostTitle(postId, newTitle) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${postId}`,
    {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ title: newTitle })
    }
  );

  return response.json();
}

With Python (requests)

import requests
import json

# GET request
response = requests.get("https://jsonplaceholder.typicode.com/users")
print(f"Status: {response.status_code}")
print(f"Users: {len(response.json())}")

# POST request
new_post = {
    "title": "API Testing with Python",
    "body": "Testing the requests library",
    "userId": 1
}
response = requests.post(
    "https://jsonplaceholder.typicode.com/posts",
    json=new_post,
    headers={"Accept": "application/json"}
)
print(f"Created: {response.status_code}")
print(json.dumps(response.json(), indent=2))

# PUT request with timeout
try:
    response = requests.put(
        "https://jsonplaceholder.typicode.com/posts/1",
        json={"id": 1, "title": "Updated", "body": "New content", "userId": 1},
        timeout=10
    )
    response.raise_for_status()  # Raises exception for 4xx/5xx
    print("Update successful")
except requests.exceptions.Timeout:
    print("Request timed out")
except requests.exceptions.HTTPError as e:
    print(f"HTTP error: {e}")

# DELETE request
response = requests.delete("https://jsonplaceholder.typicode.com/posts/1")
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
⚙ Format your responses: API responses can be hard to read when compressed. Paste them into our JSON Formatter to pretty-print and syntax-highlight the output.

6. HTTP Status Codes You Need to Know

When testing APIs, the status code is the first thing you check. It tells you whether the request succeeded, failed, or was redirected. Here are the codes you will encounter most often.

Success Codes (2xx)

Code Name When to Use
200 OK Successful GET, PUT, or PATCH request
201 Created Successful POST that created a new resource
204 No Content Successful DELETE or update with no body

Client Error Codes (4xx)

Code Name What It Means
400 Bad Request Malformed request syntax or invalid data
401 Unauthorized Missing or invalid authentication credentials
403 Forbidden Authenticated but lacking permission
404 Not Found Resource does not exist at the given URL
409 Conflict Request conflicts with current state (e.g., duplicate)
422 Unprocessable Entity Valid syntax but semantically invalid data
429 Too Many Requests Rate limit exceeded

Server Error Codes (5xx)

Code Name What It Means
500 Internal Server Error Unhandled exception on the server
502 Bad Gateway Upstream server returned an invalid response
503 Service Unavailable Server temporarily overloaded or in maintenance
504 Gateway Timeout Upstream server did not respond in time
⚙ Quick reference: Bookmark our HTTP Status Codes Cheat Sheet for a complete, searchable reference of all standard HTTP status codes.

7. Authentication Methods in API Testing

Most real-world APIs require authentication. Understanding how each method works is essential for writing effective API tests.

API Keys

The simplest form of authentication. An API key is a static token sent as a header or query parameter.

# API key in header
curl -H "X-API-Key: your-api-key-here" \
     https://api.example.com/data

# API key as query parameter
curl "https://api.example.com/data?api_key=your-api-key-here"

API keys are easy to implement but offer limited security. They do not expire automatically, they cannot represent a specific user's permissions, and they are easily leaked if included in URLs (which get logged by servers and proxies).

Bearer Tokens (JWT)

Bearer tokens, especially JSON Web Tokens (JWTs), are the most common authentication mechanism in modern APIs. The client authenticates once (usually with username and password) and receives a token that is sent with subsequent requests.

# Step 1: Authenticate and get a token
TOKEN=$(curl -s -X POST https://api.example.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "secret"}' \
  | jq -r '.token')

# Step 2: Use the token for subsequent requests
curl -H "Authorization: Bearer $TOKEN" \
     https://api.example.com/protected/resource

When testing JWT-based APIs, always test these scenarios:

⚙ Decode tokens: Need to inspect what is inside a JWT? Use our JWT Decoder to decode the header, payload, and verify the signature.

OAuth 2.0

OAuth 2.0 is a framework for delegated authorization. It is used when a user wants to grant a third-party application access to their data without sharing their password (e.g., "Sign in with Google").

Testing OAuth flows is more complex because it involves multiple steps: authorization request, user consent, callback with authorization code, and token exchange. For automated testing, most teams use service account credentials or pre-generated tokens to bypass the interactive consent flow.

Basic Authentication

HTTP Basic Auth encodes the username and password in a Base64 header. It is simple but insecure unless used over HTTPS.

# Basic auth with curl (curl handles encoding)
curl -u "username:password" https://api.example.com/data

# Equivalent manual encoding
curl -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" \
     https://api.example.com/data

Basic auth is increasingly rare in new APIs but still common in internal tools, CI/CD integrations, and legacy systems.

8. Response Validation Techniques

Sending a request is only half the job. The other half is verifying that the response is correct. Here are the key aspects of a response to validate.

Status Code Validation

Always check the status code first. It is the quickest way to determine if a request succeeded or failed.

# Python: Check status code
response = requests.get("https://api.example.com/users/42")
assert response.status_code == 200, f"Expected 200, got {response.status_code}"

# JavaScript: Check status code
const response = await fetch("https://api.example.com/users/42");
if (!response.ok) {
  throw new Error(`Expected success, got ${response.status}`);
}

Response Body Validation

Check that the response body contains the expected data, with the correct types and structure.

# Python: Validate response structure
response = requests.get("https://api.example.com/users/42")
user = response.json()

# Check required fields exist
assert "id" in user, "Missing 'id' field"
assert "name" in user, "Missing 'name' field"
assert "email" in user, "Missing 'email' field"

# Check types
assert isinstance(user["id"], int), "id should be an integer"
assert isinstance(user["name"], str), "name should be a string"
assert "@" in user["email"], "email should contain @"

# Check specific values
assert user["id"] == 42
assert len(user["name"]) > 0, "name should not be empty"

Header Validation

Response headers carry important metadata. Check content type, caching directives, rate limit information, and CORS headers.

# Python: Validate headers
response = requests.get("https://api.example.com/users")

# Check content type
assert "application/json" in response.headers["Content-Type"]

# Check rate limiting headers
remaining = int(response.headers.get("X-RateLimit-Remaining", 0))
print(f"Rate limit remaining: {remaining}")

# Check caching
cache_control = response.headers.get("Cache-Control", "")
print(f"Cache policy: {cache_control}")

JSON Schema Validation

For thorough validation, use JSON Schema to define the exact shape of your expected response. This is more robust than checking individual fields because it validates the entire structure in one step.

# Python: JSON Schema validation
from jsonschema import validate

user_schema = {
    "type": "object",
    "required": ["id", "name", "email", "created_at"],
    "properties": {
        "id": {"type": "integer", "minimum": 1},
        "name": {"type": "string", "minLength": 1},
        "email": {"type": "string", "format": "email"},
        "created_at": {"type": "string", "format": "date-time"},
        "role": {"type": "string", "enum": ["admin", "user", "viewer"]}
    },
    "additionalProperties": False
}

response = requests.get("https://api.example.com/users/42")
validate(instance=response.json(), schema=user_schema)  # Raises on failure
⚙ Validate JSON: Use our JSON Validator to quickly check that API response bodies are valid JSON, and our JSON Formatter to make them readable.

Response Time Validation

Performance matters. Set acceptable thresholds and fail tests when the API is too slow.

# Python: Check response time
import time

start = time.time()
response = requests.get("https://api.example.com/users")
elapsed_ms = (time.time() - start) * 1000

print(f"Response time: {elapsed_ms:.0f}ms")
assert elapsed_ms < 500, f"Response too slow: {elapsed_ms:.0f}ms (limit: 500ms)"

# curl: Measure response time
# curl -o /dev/null -s -w "Time: %{time_total}s\n" https://api.example.com/users

9. API Testing Best Practices

Following these practices will make your API tests more reliable, maintainable, and useful.

Test Both Happy and Sad Paths

It is tempting to only test the successful case. But most bugs hide in error handling. For every endpoint, test:

# Test matrix for POST /api/users
# Happy path
curl -X POST /api/users -d '{"name": "Alice", "email": "alice@test.com"}' → 201

# Missing required field
curl -X POST /api/users -d '{"name": "Alice"}' → 422

# Invalid email format
curl -X POST /api/users -d '{"name": "Alice", "email": "not-an-email"}' → 422

# Duplicate email
curl -X POST /api/users -d '{"name": "Alice", "email": "existing@test.com"}' → 409

# Empty body
curl -X POST /api/users -d '{}' → 422

# No auth
curl -X POST /api/users -d '{"name": "Alice", "email": "a@test.com"}' → 401

Keep Tests Independent

Each test should be able to run on its own, in any order. Tests that depend on each other are fragile and hard to debug. If a test needs a user to exist, create the user in the test setup, do not rely on another test having created it.

Use Environment Variables for Configuration

Never hardcode URLs, API keys, or credentials in your tests. Use environment variables so the same tests can run against different environments (development, staging, production).

# Python: Use environment variables
import os

BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:3000")
API_KEY = os.environ["API_KEY"]  # Fail loudly if missing

response = requests.get(
    f"{BASE_URL}/api/users",
    headers={"Authorization": f"Bearer {API_KEY}"}
)

Clean Up After Tests

Tests that create data should delete it when finished. This prevents test data from accumulating and interfering with other tests. Use setup and teardown functions in your test framework.

# Python: pytest fixture for cleanup
import pytest

@pytest.fixture
def test_user():
    # Setup: create a user
    response = requests.post(f"{BASE_URL}/api/users",
        json={"name": "Test User", "email": f"test-{uuid4()}@example.com"})
    user = response.json()
    yield user
    # Teardown: delete the user
    requests.delete(f"{BASE_URL}/api/users/{user['id']}")

Version Your API Tests

If your API has versions (/v1/users, /v2/users), maintain separate test suites for each version. When you deprecate a version, do not delete the tests immediately; keep them running until the old version is fully decommissioned.

Test Rate Limiting

If your API has rate limits, write tests that verify the limits work correctly. Send requests until you get a 429 Too Many Requests response, then verify the Retry-After header is present.

Automate in CI/CD

API tests should run automatically on every pull request and deployment. Integrate them into your CI/CD pipeline so broken APIs never reach production.

# GitHub Actions example
name: API Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Start API server
        run: docker-compose up -d
      - name: Wait for API to be ready
        run: |
          for i in $(seq 1 30); do
            curl -sf http://localhost:3000/health && break
            sleep 1
          done
      - name: Run API tests
        run: pytest tests/api/ -v --tb=short
        env:
          API_BASE_URL: http://localhost:3000

10. Common API Testing Mistakes

These are the mistakes developers make most often when testing APIs. Avoiding them will save you hours of debugging.

Only Testing the Happy Path

If your test suite only sends valid requests, you have no idea how your API handles bad input. In production, users will send every imaginable combination of invalid data. Test for it.

Ignoring Response Headers

The response body is not the only thing that matters. Headers like Content-Type, Cache-Control, X-Request-Id, and CORS headers are all part of your API's contract. If they change unexpectedly, it can break clients.

Not Testing Concurrency

What happens when two users update the same resource at the same time? Race conditions in APIs can cause data corruption, duplicate records, or lost updates. Test concurrent access to shared resources.

Hardcoding Test Data

Using hardcoded IDs like user_id: 1 makes tests brittle. The data might not exist in every environment. Generate unique test data or use fixtures instead.

Forgetting to Test Pagination

APIs that return lists usually have pagination. Test that:

Not Validating Error Response Format

Error responses should follow a consistent format. When your API returns an error, verify that the response body contains a structured error message, not a stack trace or generic HTML page.

# Good error response format to test for
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is required",
    "details": [
      {
        "field": "email",
        "message": "This field is required"
      }
    ]
  }
}

11. Building a Complete API Test Suite

Here is a practical example of a well-structured API test suite in Python that you can adapt for your own projects. It demonstrates the patterns discussed throughout this guide.

"""
API Test Suite - User Management Endpoints
Run with: pytest test_users_api.py -v
"""
import os
import pytest
import requests
from uuid import uuid4

BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:3000")
API_KEY = os.environ.get("API_KEY", "test-key")
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json"
}


@pytest.fixture
def unique_email():
    """Generate a unique email for each test."""
    return f"test-{uuid4().hex[:8]}@example.com"


@pytest.fixture
def created_user(unique_email):
    """Create a user and clean up after the test."""
    response = requests.post(
        f"{BASE_URL}/api/users",
        json={"name": "Test User", "email": unique_email},
        headers=HEADERS
    )
    assert response.status_code == 201
    user = response.json()
    yield user
    # Cleanup
    requests.delete(f"{BASE_URL}/api/users/{user['id']}", headers=HEADERS)


class TestCreateUser:
    """Tests for POST /api/users"""

    def test_create_user_success(self, unique_email):
        response = requests.post(
            f"{BASE_URL}/api/users",
            json={"name": "Alice", "email": unique_email},
            headers=HEADERS
        )
        assert response.status_code == 201
        user = response.json()
        assert user["name"] == "Alice"
        assert user["email"] == unique_email
        assert "id" in user
        # Cleanup
        requests.delete(f"{BASE_URL}/api/users/{user['id']}", headers=HEADERS)

    def test_create_user_missing_email(self):
        response = requests.post(
            f"{BASE_URL}/api/users",
            json={"name": "Alice"},
            headers=HEADERS
        )
        assert response.status_code == 422

    def test_create_user_invalid_email(self):
        response = requests.post(
            f"{BASE_URL}/api/users",
            json={"name": "Alice", "email": "not-valid"},
            headers=HEADERS
        )
        assert response.status_code == 422

    def test_create_user_no_auth(self, unique_email):
        response = requests.post(
            f"{BASE_URL}/api/users",
            json={"name": "Alice", "email": unique_email}
        )
        assert response.status_code == 401


class TestGetUser:
    """Tests for GET /api/users/:id"""

    def test_get_existing_user(self, created_user):
        response = requests.get(
            f"{BASE_URL}/api/users/{created_user['id']}",
            headers=HEADERS
        )
        assert response.status_code == 200
        assert response.json()["id"] == created_user["id"]
        assert "application/json" in response.headers["Content-Type"]

    def test_get_nonexistent_user(self):
        response = requests.get(
            f"{BASE_URL}/api/users/999999999",
            headers=HEADERS
        )
        assert response.status_code == 404

    def test_response_time(self, created_user):
        response = requests.get(
            f"{BASE_URL}/api/users/{created_user['id']}",
            headers=HEADERS
        )
        assert response.elapsed.total_seconds() < 0.5


class TestListUsers:
    """Tests for GET /api/users"""

    def test_list_users_returns_array(self):
        response = requests.get(f"{BASE_URL}/api/users", headers=HEADERS)
        assert response.status_code == 200
        assert isinstance(response.json(), list)

    def test_pagination(self):
        response = requests.get(
            f"{BASE_URL}/api/users?page=1&limit=10",
            headers=HEADERS
        )
        assert response.status_code == 200
        assert len(response.json()) <= 10

This test suite follows all the best practices discussed above: tests are independent, data is cleaned up, authentication is tested, both happy and sad paths are covered, and configuration uses environment variables.

Summary

API testing is not optional for professional software development. It catches bugs early, runs fast, and gives you confidence that your services work correctly under real-world conditions.

Here is what to remember:

  1. Start simple. A curl command that checks the status code is better than no test at all.
  2. Cover multiple test types. Unit tests for logic, integration tests for the full stack, contract tests for stability, and load tests for performance.
  3. Test failures, not just successes. Invalid input, missing authentication, and nonexistent resources should all return appropriate error responses.
  4. Validate the complete response. Status code, headers, body structure, data types, and response time.
  5. Automate everything. Run your API tests in CI/CD so regressions are caught before they reach users.
  6. Use the right tool for the job. curl for quick checks, Postman for exploration, programming libraries for automated suites.
⚙ Start practicing: Open our HTTP Request Tester and try sending a GET request to https://jsonplaceholder.typicode.com/posts/1. Then format the response with our JSON Formatter.

Related Tools & Resources

HTTP Request Tester
Send HTTP requests and inspect responses in your browser
JSON Formatter
Pretty-print and format API response data
JSON Validator
Validate JSON syntax and structure
JWT Decoder
Decode and inspect JWT tokens for API auth testing
HTTP Status Codes Cheat Sheet
Complete reference for all HTTP status codes
JSON API Debugging Tips
10 practical tips for debugging JSON in APIs