Intermediate Chapter 13 · 11 min read

API Chaining & Workflows

Chain multiple API calls together — create a resource, read it, update it, then delete it. Pass data between requests and test end-to-end flows.

Chaining API Calls

Real API testing rarely involves single isolated requests. Business workflows require chaining: create a user, then create their profile, then assign them to a team. Each step depends on data from the previous one.

Common Patterns

  • CRUD chain — Create → Read → Update → Delete a single resource
  • Data dependency — Use the ID from a POST response in subsequent GET/PUT/DELETE calls
  • Setup/teardown — Create test data via API before tests, clean up after
  • Business flow — Register → Login → Perform action → Verify state
api-chaining.test.js
const BASE = 'https://jsonplaceholder.typicode.com';
const headers = { 'Content-Type': 'application/json' };

// Full CRUD workflow chain
console.log('--- CRUD Workflow ---');

// 1. CREATE
const createResp = await fetch(`${BASE}/posts`, {
  method: 'POST', headers,
  body: JSON.stringify({
    title: 'Chained Post',
    body: 'Created in workflow',
    userId: 1
  })
});
const created = await createResp.json();
console.assert(createResp.status === 201);
const postId = created.id;
console.log(`1. Created post ID: ${postId}`);

// 2. READ — verify it exists
const readResp = await fetch(`${BASE}/posts/${postId}`);
const read = await readResp.json();
console.log(`2. Read post: "${read.title}"`);

// 3. UPDATE — modify the post
const updateResp = await fetch(`${BASE}/posts/${postId}`, {
  method: 'PUT', headers,
  body: JSON.stringify({
    id: postId,
    title: 'Updated Chained Post',
    body: 'Modified in workflow',
    userId: 1
  })
});
const updated = await updateResp.json();
console.assert(updated.title === 'Updated Chained Post');
console.log(`3. Updated to: "${updated.title}"`);

// 4. DELETE — remove the post
const deleteResp = await fetch(`${BASE}/posts/${postId}`, {
  method: 'DELETE'
});
console.assert(deleteResp.status === 200);
console.log(`4. Deleted post ${postId}`);

// Multi-resource workflow: User -> Posts -> Comments
console.log('\n--- Multi-Resource Workflow ---');
const userResp = await fetch(`${BASE}/users/1`);
const user = await userResp.json();
console.log(`User: ${user.name}`);

const userPosts = await fetch(`${BASE}/posts?userId=${user.id}`);
const posts = await userPosts.json();
console.log(`Posts by ${user.name}: ${posts.length}`);

const firstPostComments = await fetch(`${BASE}/posts/${posts[0].id}/comments`);
const comments = await firstPostComments.json();
console.log(`Comments on first post: ${comments.length}`);

console.log('All chaining tests passed!');
ApiChainingTest.java
import static io.restassured.RestAssured.*;
import io.restassured.response.Response;
import org.testng.annotations.Test;
import org.testng.Assert;
import java.util.List;

public class ApiChainingTest {

    private static final String BASE = "https://jsonplaceholder.typicode.com";

    @Test
    public void testCrudWorkflow() {
        // 1. CREATE
        Response createResp = given().baseUri(BASE)
            .contentType("application/json")
            .body("{\"title\": \"Chained Post\", \"body\": \"Created\", \"userId\": 1}")
        .when().post("/posts")
        .then().statusCode(201).extract().response();

        int postId = createResp.jsonPath().getInt("id");
        System.out.println("1. Created post ID: " + postId);

        // 2. READ
        String title = given().baseUri(BASE)
            .when().get("/posts/" + postId)
            .then().statusCode(200)
            .extract().jsonPath().getString("title");
        System.out.println("2. Read: " + title);

        // 3. UPDATE
        given().baseUri(BASE)
            .contentType("application/json")
            .body("{\"id\": " + postId + ", \"title\": \"Updated\", \"body\": \"Modified\", \"userId\": 1}")
        .when().put("/posts/" + postId)
        .then().statusCode(200)
            .body("title", org.hamcrest.Matchers.equalTo("Updated"));
        System.out.println("3. Updated");

        // 4. DELETE
        given().baseUri(BASE)
        .when().delete("/posts/" + postId)
        .then().statusCode(200);
        System.out.println("4. Deleted");
    }

    @Test
    public void testMultiResourceWorkflow() {
        // Get user
        Response userResp = given().baseUri(BASE)
            .when().get("/users/1")
            .then().statusCode(200).extract().response();
        String userName = userResp.jsonPath().getString("name");

        // Get user's posts
        List<Integer> postIds = given().baseUri(BASE)
            .queryParam("userId", 1)
        .when().get("/posts")
        .then().statusCode(200)
            .extract().jsonPath().getList("id");
        System.out.println(userName + " has " + postIds.size() + " posts");

        // Get comments on first post
        int commentCount = given().baseUri(BASE)
        .when().get("/posts/" + postIds.get(0) + "/comments")
        .then().statusCode(200)
            .extract().jsonPath().getList("$").size();
        System.out.println("First post has " + commentCount + " comments");
    }
}
test_api_chaining.py
import requests

BASE_URL = 'https://jsonplaceholder.typicode.com'

def test_crud_workflow():
    """Full CRUD chain: Create -> Read -> Update -> Delete"""

    # 1. CREATE
    create_resp = requests.post(
        f'{BASE_URL}/posts',
        json={'title': 'Chained Post', 'body': 'Created', 'userId': 1}
    )
    assert create_resp.status_code == 201
    post_id = create_resp.json()['id']
    print(f"1. Created post ID: {post_id}")

    # 2. READ
    read_resp = requests.get(f'{BASE_URL}/posts/{post_id}')
    assert read_resp.status_code == 200
    print(f"2. Read: {read_resp.json().get('title', 'N/A')}")

    # 3. UPDATE
    update_resp = requests.put(
        f'{BASE_URL}/posts/{post_id}',
        json={'id': post_id, 'title': 'Updated', 'body': 'Modified', 'userId': 1}
    )
    assert update_resp.status_code == 200
    assert update_resp.json()['title'] == 'Updated'
    print("3. Updated")

    # 4. DELETE
    delete_resp = requests.delete(f'{BASE_URL}/posts/{post_id}')
    assert delete_resp.status_code == 200
    print(f"4. Deleted post {post_id}")

def test_multi_resource_workflow():
    """Chain across multiple resource types"""
    # Get user
    user = requests.get(f'{BASE_URL}/users/1').json()
    print(f"User: {user['name']}")

    # Get user's posts
    posts = requests.get(f'{BASE_URL}/posts', params={'userId': user['id']}).json()
    print(f"Posts by {user['name']}: {len(posts)}")

    # Get comments on first post
    comments = requests.get(f'{BASE_URL}/posts/{posts[0]["id"]}/comments').json()
    print(f"Comments on first post: {len(comments)}")

    assert len(posts) > 0
    assert len(comments) > 0

API Testing Intermediate API Chaining & Workflows

Written by PV

© 2026 All Rights Reserved