Jump Into Our API Testing Adventure – $250 Prize + Swag for first 15 submissions

Ready to flex your API testing skills?

This month’s challenge is all about creative, effective testing using Postman’s newest features.

Complete 5 guided tasks in our Public Workspace and then share how you used an external package (like chance, joi, or jsonwebtoken) in your Pre-request or Post-response Script.

Post your script, approach, or learnings in this topic to enter and have a shot at winning.

First time trying this? Don’t worry, each task includes helpful examples and walkthroughs.

  • One standout submission will win $250
  • The first 15 valid entries get Postman swag
  • All participants earn the Postman Builder Badge

Challenge runs June 24–July 1. Questions or want to see examples? Join us on Discord in the #challenges channel.

11 Likes
const dayjs = pm.require('npm:[email protected]');
const responseJson = pm.response.json();

const firstVehicle = responseJson[0]; 

pm.test("Should correctly calculate the age of the first vehicle", function () {
    const createdDate = dayjs(firstVehicle.created);
    const currentDate = dayjs();
    const age = currentDate.diff(createdDate, 'year');

    pm.expect(age).to.eql(10); 
});

It is simple script to check the age of the vehicle

2 Likes
const zod = pm.require('npm:[email protected]');

const response = pm.response.json();

//The schema to check the todos schema in the response body
const userSchema = zod.object({
    userId: zod.number(),
    id: zod.number(),
    title: zod.string(),
    completed: zod.boolean()
});

try {
    const parsedData = userSchema.parse(response);

    pm.test("The response data is valid against the schema", () => {
        pm.expect(parsedData).to.be.an('object');
    });
} catch (error) {
    pm.test("The response data is invalid against the schema", () => {
        console.error("Validation errors:", error.errors);
        pm.expect.fail("Check console for errors");
    });
}

This is the script to validate the json schema of the todos using Zod npm package that is fetched using the GET request in postman using this endpoint - https://jsonplaceholder.typicode.com/todos/1.

2 Likes
const zod = pm.require('npm:zod');

pm.test('Should return a 200 status code', function () {
    pm.response.to.have.status(200);
});

const response = pm.response.json();

const userSchema = zod.object({
    name: zod.string(),
    height: zod.string(),
    mass: zod.string(),
    hair_color: zod.string(),
    skin_color: zod.string(),
    eye_color: zod.string(),
    birth_year: zod.string(),
    gender: zod.string(),
    homeworld: zod.string().url(),
    films: zod.array(zod.string().url()),
    species: zod.array(zod.string().url()),
    vehicles: zod.array(zod.string().url()),
    starships: zod.array(zod.string().url()),
    created: zod.string(),
    edited: zod.string(),
    url: zod.string().url()
});
try {
    const parsedData = userSchema.parse(response);

    pm.test("The response data is valid against the schema", () => {
        pm.expect(parsedData).to.be.an('object');
    });
} catch (error) {
    pm.test("The response data is invalid against the schema", () => {
        console.error("Validation errors:", error.errors);
        pm.expect.fail("Schema validation failed. Check console for errors.");
    });
}

This script is used to check the structure and data types of the star wars object response with the given schema.

2 Likes

My name is Shreya Prashant Langote
My Submission for June API Testing Challenge

I completed all tasks including using external NPM packages.

External Packages used:

:one: chance
Generated random test data for API requests:

const chance = pm.require('npm:[email protected]');
const ch = new chance.Chance();
const randomTitle = ch.sentence({ words: 5 });
const randomUserId = ch.integer({ min: 1, max: 10 });

pm.collectionVariables.set("randomTitle", randomTitle);
pm.collectionVariables.set("randomUserId", randomUserId);

:two: dayjs
Calculated time differences and demonstrated data manipulation:

import dayjs from "dayjs";

const now = dayjs();
const past = now.subtract(5, 'hour');
const diffMinutes = now.diff(past, 'minute');

console.log("Current Time:", now.format());
console.log("Past Time (5 hours ago):", past.format());
console.log("Time Difference in minutes:", diffMinutes);

pm.collectionVariables.set("timeDifferenceMinutes", diffMinutes);

This was a fun learning experience with Postman’s new external package feature! :rocket:

This the link of my tasks performed : Postman

2 Likes

Hey Folks :waving_hand:,

I’ve been really excited about this challenge and can’t wait to see the creative ways people use external packages in their Collections!

With so many options available on npm and JSR, the possibilities are truly endless. :heart:

The examples provided are just a starting point to spark ideas and show what’s possible. What we’re really looking for are fresh, unique, and innovative uses of external packages.

So, how will you stand out and show off what you can do?

The most impressive entry will win the $250 USD :money_bag:


Here’s a couple of different examples:

Using the authenticator package:

const authenticator = pm.require('npm:[email protected]');

var formattedKey = authenticator.generateKey();
// "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz"
 
var formattedToken = authenticator.generateToken(formattedKey);
// "957 124"
 
authenticator.verifyToken(formattedKey, formattedToken);
// { delta: 0 }
 
authenticator.verifyToken(formattedKey, '000 000');
// null
 
authenticator.generateTotpUri(formattedKey, "[email protected]", "ACME Co", 'SHA1', 6, 30);

Using the uuid package:

const { v5 } = pm.require('npm:uuid');

Using the jose package:

const { importPKCS8, SignJWT } = pm.require("npm:jose");

const privateKeyPem = `
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
`;

const key = await importPKCS8(privateKeyPem.trim(), "ES256");

const jwt = await new SignJWT({ foo: "bar" })
  .setProtectedHeader({ alg: "ES256" })
  .setIssuedAt()
  .setExpirationTime("2h")
  .sign(key);

console.log("Generated ES256 JWT:", jwt);

2 Likes

I created a request that:

:small_blue_diamond: Fetches user data from jsonplaceholder
:small_blue_diamond: Uses 3 external NPM packages via pm.require():

  • qrcode to generate a QR code of contact info
  • @faker-js/faker to generate a fake strong password
  • validator to validate the email format

:light_bulb: The console shows:

  • A stylized terminal QR code
  • A strong password suggestion
  • :white_check_mark: or :cross_mark: based email validation
  • :tada: Final status with emojis

Super useful + fun to run. Logs feel alive, and it’s extensible for real use cases too!

:speech_balloon: Feedback welcome.

#JuneChallenge #PostmanScripts

1 Like

Nice @jyesh - Just ensure that you’re following the instructions in the Folder for the correct request name. You will receive an error for your overall submission as it is now :folded_hands:

I had missed updating it earlier, but it’s now been taken care of. Thanks for pointing it out @danny-dainton

1 Like

Hi,
This script generates a blurhash for a random image and then verifies whether the generated hash matches a specific regex pattern.

const { encode } = pm.require('npm:blurhash');
const jpeg = pm.require('npm:jpeg-js');
const buffer = Buffer.from(pm.response.stream);

const rawImageData = jpeg.decode(buffer, { useTArray: true });

if (!rawImageData || !rawImageData.data) {
    console.log("Failed to decode image");
} else {
    const bhash = encode(
        rawImageData.data,
        rawImageData.width,
        rawImageData.height,
        4, 4
    );

    console.log('BlurHash:', bhash);
    pm.test("BlurHash matches expected pattern", function () {
        pm.expect(bhash).to.match(/^[0-9A-Za-z#$%*+,\-.:;=?@[\]^_{|}~]{6,}$/);
    });

}

1 Like
const cryptoJs = pm.require('npm:[email protected]');

const asciiArtMap = {
    '0': ['  ___  ', ' / _ \\ ', '| | | |', '| |_| |', ' \\___/ '],
    '1': ['  _  ', ' | | ', ' | | ', ' | | ', ' |_| '],
    '2': ['  ___  ', ' / __| ', '| (__ ', ' \\___|', '      '],
    '3': ['  ___  ', ' / __| ', ' \\__ \\ ', ' |___/ ', '      '],
    '4': ['  _  _  ', ' | || | ', ' |__  | ', '    | | ', '    |_| '],
    '5': ['  ____ ', ' | ___|', ' |___  ', '  ___| ', ' |____|'],
    'a': ['   _    ', '  | |   ', ' / _ \\  ', '| |_| | ', ' \\___/  '],
    'b': ['  ____  ', ' | __ ) ', ' |  _ \\ ', ' | |_) |', ' |____/ '],
    'c': ['   ____ ', '  / ___|', ' | |    ', ' | |___ ', '  \\____|'],
    'd': ['  ____  ', ' |  _ \\ ', ' | | | |', ' | |_| |', ' |____/ '],
    'e': ['  ____ ', ' | ___|', ' | ___|', ' | |___ ', ' |____|'],
    'f': ['  ____ ', ' | ___|', ' | ___|', ' | |    ', ' |_|    ']
};

// Generate ASCII art from a string
function generateASCIIArt(text) {
    if (!text || typeof text !== 'string') return ['[NO TEXT]'];
    text = text.toLowerCase().slice(0, 6); // Limit to 6 characters
    const lines = ['', '', '', '', ''];
    
    for (let char of text) {
        const art = asciiArtMap[char] || ['  ___  ', ' / __| ', '| (__ ', ' \\___|', '      '];
        for (let i = 0; i < 5; i++) {
            lines[i] += (art[i] || '').padEnd(8);
        }
    }
    
    return lines.filter(line => line.trim());
}

// Create formatted output
function createFormattedOutput(asciiArt, originalText, hash, responseData) {
    const separator = '='.repeat(60);
    const timestamp = new Date().toLocaleString('en-US', { timeZone: 'Asia/Kolkata' });
    
    return `
${separator}
Crypto ASCII Response Formatter
${separator}

Original Text: "${originalText || 'N/A'}"
MD5 Hash: ${hash}
Response Status: ${pm.response.status || 'N/A'} ${pm.response.reason() || ''}
Response Time: ${pm.response.responseTime || 'N/A'}ms
Generated: ${timestamp}

${separator}

${asciiArt.join('\n')}

${separator}
Generated using crypto-js and custom ASCII art
Request: ${pm.info.requestName || 'Unknown'}
Collection: ${pm.info.collectionName || 'Unknown'}
${separator}
    `;
}

// Main execution
try {
    // Parse response
    let responseJson = {};
    try {
        responseJson = pm.response.json();
    } catch (e) {
        console.log('Could not parse JSON response:', e.message);
        responseJson = {};
    }
    
    // Extract text from response
    let textToConvert = 'SUCCESS';
    let sourceField = 'default';
    
    if (responseJson.title) {
        textToConvert = responseJson.title;
        sourceField = 'title';
    } else if (responseJson.body) {
        textToConvert = responseJson.body.slice(0, 50);
        sourceField = 'body';
    } else if (Array.isArray(responseJson) && responseJson.length > 0) {
        if (responseJson[0].title) {
            textToConvert = responseJson[0].title;
            sourceField = 'first item title';
        } else if (responseJson[0].name) {
            textToConvert = responseJson[0].name;
            sourceField = 'first item name';
        }
    } else if (responseJson.name) {
        textToConvert = responseJson.name;
        sourceField = 'name';
    }
    
    // Clean text
    textToConvert = textToConvert.replace(/[^\w\s]/gi, '').trim();
    
    // Generate MD5 hash
    const hash = CryptoJS.MD5(textToConvert).toString();
    const displayHash = hash.slice(0, 6);
    
    // Generate ASCII art from hash
    const asciiArt = generateASCIIArt(displayHash);
    const formattedOutput = createFormattedOutput(asciiArt, textToConvert, hash, responseJson);
    console.log(formattedOutput);
    
    // Store variables
    pm.collectionVariables.set('crypto_ascii_output', asciiArt.join('\n'));
    pm.collectionVariables.set('crypto_source_text', textToConvert);
    pm.collectionVariables.set('crypto_source_field', sourceField);
    pm.collectionVariables.set('crypto_hash', hash);
    
    // Tests
    pm.test("Response Status Check", function () {
        pm.response.to.have.status(200);
    });
    
    pm.test("MD5 Hash Generated", function () {
        pm.expect(hash).to.be.a('string').and.have.length(32);
    });
    
    pm.test("ASCII Art Generated Successfully", function () {
        pm.expect(asciiArt).to.not.be.empty;
        pm.expect(asciiArt.length).to.be.greaterThan(0);
    });
    
    pm.test(`Text Extracted from ${sourceField}`, function () {
        pm.expect(textToConvert).to.not.be.empty;
    });
    
    // Generate status code ASCII
    const statusCode = pm.response.status.toString().slice(0, 3);
    const statusArt = generateASCIIArt(statusCode);
    console.log(`\nResponse Status (${statusCode}):\n${statusArt.join('\n')}\n`);
    pm.collectionVariables.set('crypto_status_output', statusArt.join('\n'));
    
} catch (error) {
    console.error('Error processing response:', error.message);
    
    // Fallback ASCII art
    const errorArt = generateASCIIArt('ERROR');
    console.log(`\nError ASCII Art:\n${errorArt.join('\n')}\n`);
    
    pm.test("Error Handled Gracefully", function () {
        pm.expect(error.message).to.not.be.empty;
    });
}
var template = `
<style type="text/css">
    .tftable {font-size:14px;color:#333333;width:100%;border-width: 1px;border-color: #87ceeb;border-collapse: collapse;}
    .tftable th {font-size:18px;background-color:#87ceeb;border-width: 1px;padding: 8px;border-style: solid;border-color: #87ceeb;text-align:left;}
    .tftable tr {background-color:#ffffff;}
    .tftable td {font-size:14px;border-width: 1px;padding: 8px;border-style: solid;border-color: #87ceeb;}
    .tftable tr:hover {background-color:#e0ffff;}
</style>

<table class="tftable" border="1">
    <tr>
        <th>User ID</th>
        <th>ID</th>
        <th>Title</th>
        <th>Body</th>
    </tr>
    <tr>
        <td>{{response.userId}}</td>
        <td>{{response.id}}</td>
        <td>{{response.title}}</td>
        <td>{{response.body}}</td>
    </tr>
</table>
`;

function constructVisualizerPayload() {
    return {response: pm.response.json()};
}

pm.visualizer.set(template, constructVisualizerPayload());

This is the code I have created. I played with the platform a little haha. I used crypto.js to encrypt the message that is being received and made some ascii art using mapping method of that hashed message. (Used AI).

Overall I learned to test API. It is very easy to test and validate an API which is very useful to me. Then I learnt how to visualize with postman and also how to import modules using postman. It is quite a bit but was very easy to work with.

Thanks, That’s my submission!

1 Like

AI-Generated Caption from Random Image

I created an AI-Generated Caption experience using random images fetched from the Picsum Photos API.


:link: API Used:

https://picsum.photos/v2/list?page={{randomPage}}&limit=1

This returns random image metadata based on the page number.


:gear: Pre-request Script:

const randomPage = Math.floor(Math.random() * 100) + 1;
pm.variables.set("randomPage", randomPage);

Generates a new random image on every request.


:package: External Package Used:

lodash – to easily pick random words from arrays to build creative, natural-sounding captions.
compromise - used to generate and transform the caption (not just analyze it)


:scroll: Post-response Script:

const _ = require('lodash');
const compromise = pm.require('npm:[email protected]');

// Get image data from response
const image = pm.response.json()[0];
const imageUrl = `https://picsum.photos/id/${image.id}/400/300`;
const author = image.author;

// Word banks
const adjectives = ['serene', 'vivid', 'mystical', 'dramatic', 'picturesque', 'timeless'];
const scenes = [
    'a glimpse into nature',
    'urban beauty',
    'a peaceful escape',
    'an architectural wonder',
    'a dreamlike sky',
    'a glowing horizon'
];

// Step 1: Seed raw caption
const seedCaption = `The scene is ${_.sample(adjectives)} and shows ${_.sample(scenes)}.`;

// Step 2: Use compromise to smartly transform the caption
let doc = compromise(seedCaption);

// Use NLP to enhance sentence style
doc.verbs().toPresentTense();
doc.replace('The scene is', `Captured by ${author}, this moment feels`);
doc.replace('shows', 'offers');

// Final AI-generated caption
const caption = doc.text();

// NLP analysis just for fun
const nouns = doc.nouns().out('array');
const adjs = doc.adjectives().out('array');

// Set up the visualizer
pm.visualizer.set(`
  <div style="text-align:center; font-family: sans-serif; padding: 20px;">
    <img src="{{img}}" style="max-width:100%; border-radius:10px; box-shadow: 0 4px 10px rgba(0,0,0,0.1);" />
    <h3 style="margin-top: 20px;">📝 AI-Generated Caption</h3>
    <p style="font-size: 18px;">"{{caption}}"</p>
    <p style="color:gray;">📸 Author: {{author}} | 🆔 ID: {{id}}</p>
    <hr style="margin: 20px 0;" />
    <p><strong>🔤 NLP Analysis (from compromise):</strong></p>
    <p><strong>Nouns:</strong> {{nouns}}</p>
    <p><strong>Adjectives:</strong> {{adjs}}</p>
  </div>
`, {
    img: imageUrl,
    caption,
    author,
    id: image.id,
    nouns: nouns.join(', ') || 'None',
    adjs: adjs.join(', ') || 'None'
});


:magnifying_glass_tilted_left: Visualization:

Used pm.visualizer.set() to create a clean UI showing:

  • The randomly selected image
  • An AI-generated caption
  • Image author and ID

:link: View Collection:

:backhand_index_pointing_right: Open in Postman


While APIs and captioning are widely used in AI and web apps, they’re rarely visualized inside Postman. Postman is often used for testing and debugging APIs — not for creative or dynamic frontend-style visualizations.

This collection shows that Postman can be fun, interactive, and artistic — pushing the boundaries of what’s traditionally expected from API tools. It’s not common because most developers don’t think of Postman as a canvas for storytelling, but this proves it can be.


1 Like
    pm.test(' Should return a 200 status code', function () {
    pm.response.to.have.status(200);
   });

   const schema = {
    "type": "object",
    "properties": {
        "userId": { "type": "number" },
        "id": { "type": "number" },
        "title": { "type": "string" },
        "body": { "type": "string" },
        "author": { "type": "string" }
      },
      "required": ["userId", "id", "title", "body"],
      "additionalProperties": false
     };

    const response = pm.response.json();
    const title = response.title?.toLowerCase() || "";
    const body = response.body?.toLowerCase() || "";
    const blacklist = ["damn", "hell", "stupid", "idiot", "dumb", "kill"];

    const violations = blacklist.filter(word =>
    title.includes(word) || body.includes(word)
     );

    pm.test(" Response is culturally appropriate", function () {
    if (violations.length > 0) {
        console.warn("Offensive words:", violations);
        throw new Error("Inappropriate language detected.");
      }
     });

     const franc = pm.require('npm:franc');
     const langs = pm.require('npm:langs');

     pm.test("Response language should be English", function () {
    const combinedText = `${title} ${body}`.trim();
    
      if (!combinedText || combinedText.split(' ').length < 5) {
        console.warn("Skipping language detection (text too short)");
        return;
      }

    let langCode;
    try {
        langCode = franc(combinedText);
    } catch (err) {
        console.warn("Language detection failed:", err.message);
        return;
    }

    const langName = langs.where("3", langCode)?.name || "Unknown";
    console.log(`Detected language: ${langName}`);

    pm.expect(langCode).to.eql("eng", `Detected: ${langName}`);
    });

    pm.test("Schema is valid", function () {
    pm.response.to.have.jsonSchema(schema);
    });

I played a lot with all the requests and learned a lot of new stuff!!!

  1. Offensive Content Filter :Automatically scans the response title and body for inappropriate or culturally insensitive words using a custom blacklist. (this is awesome)
  2. Language Detection with franc + langs :Dynamically detects the language of the response content. Fails the test if it’s not English, and smartly skips the test if text is too short or detection fails — avoiding false alarms. (brainstormed a lot on this one!!)
  3. Schema Validation :Ensures the response strictly follows a defined schema — nothing extra, nothing missing.

overall learned a lot, how API testing is done, refreshing is also an option (cause I did not know it existed), and Gaines some more confidence !!!

1 Like

This is great @navigation-geologis5, it’s technically not using an external package on npm or jar though.

With a couple of changes, it could be. What do you think needs to change?

Yes. But we can use external package like compromise for it. Here is the updated code and results.

const compromise = pm.require('npm:[email protected]');

// Get image data from response
const image = pm.response.json()[0];
const imageUrl = `https://picsum.photos/id/${image.id}/400/300`;
const author = image.author;

// Word banks
const adjectives = ['serene', 'vivid', 'mystical', 'dramatic', 'picturesque', 'timeless'];
const scenes = [
    'a glimpse into nature',
    'urban beauty',
    'a peaceful escape',
    'an architectural wonder',
    'a dreamlike sky',
    'a glowing horizon'
];

// Step 1: Seed raw caption
const seedCaption = `The scene is ${_.sample(adjectives)} and shows ${_.sample(scenes)}.`;

// Step 2: Use compromise to smartly transform the caption
let doc = compromise(seedCaption);

// Use NLP to enhance sentence style
doc.verbs().toPresentTense();
doc.replace('The scene is', `Captured by ${author}, this moment feels`);
doc.replace('shows', 'offers');

// Final AI-generated caption
const caption = doc.text();

// NLP analysis just for fun
const nouns = doc.nouns().out('array');
const adjs = doc.adjectives().out('array');

// Set up the visualizer
pm.visualizer.set(`
  <div style="text-align:center; font-family: sans-serif; padding: 20px;">
    <img src="{{img}}" style="max-width:100%; border-radius:10px; box-shadow: 0 4px 10px rgba(0,0,0,0.1);" />
    <h3 style="margin-top: 20px;">📝 AI-Generated Caption</h3>
    <p style="font-size: 18px;">"{{caption}}"</p>
    <p style="color:gray;">📸 Author: {{author}} | 🆔 ID: {{id}}</p>
    <hr style="margin: 20px 0;" />
    <p><strong>🔤 NLP Analysis (from compromise):</strong></p>
    <p><strong>Nouns:</strong> {{nouns}}</p>
    <p><strong>Adjectives:</strong> {{adjs}}</p>
  </div>
`, {
    img: imageUrl,
    caption,
    author,
    id: image.id,
    nouns: nouns.join(', ') || 'None',
    adjs: adjs.join(', ') || 'None'
});

This uses external npm package.


Thank you @danny-dainton for your concern. I will update it to original post too.

1 Like

I have updated it here and on postman collections too. Thank you.

1 Like

Hello API-first advocates, my name is Rener Pires and I made this.

:test_tube: QUICKY AUTO TEST API with Postman

QUICKY is a lightweight automated testing setup for Postman collections, built to help ensure consistency and structural integrity in your API responses. It allows developers to validate both the content and structure of responses using simple, declarative request headers.

This approach is ideal for maintaining API stability, especially across test environments, contract changes, and mock servers — with minimal manual effort.


:sparkles: Key Features

  • :small_blue_diamond: Hash-based response consistency checks
  • :small_blue_diamond: Schema-based response validation using Zod
  • :small_blue_diamond: Header-driven automation — no need to write custom scripts per request
  • :small_blue_diamond: Friendly to dynamic and static endpoints
  • :small_blue_diamond: Fast setup — designed for developers who value speed and clarity

:gear: How It Works

QUICKY uses custom HTTP headers to control and trigger validation logic. These headers are interpreted in the Postman test script to decide what validation behaviour to execute.

Some actions run automatically; others must be added manually when needed.


:clipboard: Available Headers

:white_check_mark: X-QUICKY-COMPARE-RES-HASH

Automatically compares the current response against a previously stored hash. If any part of the response body has changed, the test fails — even for the smallest alteration.

Use this when the API is expected to return exactly the same result on each request.


:white_check_mark: X-QUICKY-VALIDATE-RES-SCHEMA

Validates the structure of the response body against a stored Zod schema. This is useful for endpoints where content might change (e.g., timestamps or IDs), but the shape of the data must remain consistent.

Ideal for dynamic responses where strict hashing isn’t practical.


:warning: X-QUICKY-UPDATE-RES-HASH

This is a manual action. It updates the stored response hash with the value from the current response. Use it:

  • When running the test for the first time
  • After intentionally updating the response output

Must be included manually when you want to overwrite the reference hash.


:warning: X-QUICKY-UPDATE-RES-SCHEMA

Also a manual action. This generates a new Zod schema based on the current response body and stores it for future validations.

Use this whenever:

  • The API response structure has changed legitimately
  • You’re adding a new endpoint to the collection
  • You want to initialise schema validation for the first time

:light_bulb: Use Cases

:white_check_mark: Static Endpoints

Example:
https://jsonplaceholder.typicode.com/posts/1

These endpoints return consistent responses. You can rely on:

  • X-QUICKY-COMPARE-RES-HASH for strict comparison
  • X-QUICKY-VALIDATE-RES-SCHEMA as a fallback for structural checks

:counterclockwise_arrows_button: Dynamic Endpoints

Example:
https://postman-echo.com/get returns a timestamp, so the hash will change on each call.

In these cases:

  • Use X-QUICKY-VALIDATE-RES-SCHEMA for structural consistency
  • If needed, use X-QUICKY-UPDATE-RES-SCHEMA manually when the shape of the response evolves

:brain: How It Stores Data

  • Hashes and schemas are saved using pm.collectionVariables and are scoped per pm.info.requestId
  • Schemas are stored as self-invoking Zod definitions using jsonToZod and eval
  • Tests are triggered via header keys and mapped internally in a script object called QUICKY_ACTIONS

:white_check_mark: Summary

With QUICKY, you can effortlessly automate:

  • Regression checks for critical endpoints
  • Schema validations without hardcoding
  • Change detection on shared or mock environments

This makes it ideal for API-first teams, QA automation, or lightweight CI integration, without needing an external test runner or framework.


:laptop: Script session

Can be putt in collection scope, so it replicates effortless through out all your requests.

Pre-request:

const QUICKY_ACTIONS = {
    'X-QUICKY-COMPARE-RES-HASH': () => {
        const CURRENT_HASH = pm.collectionVariables.get(`QUICKY_RES_HASH_${pm.info.requestId}`);
        if (!CURRENT_HASH) throw new Error('QUICKY ERROR: Initial hash not setted, run with X-QUICKY-UPDATE-RES-HASH header')
        const RESPONSE_HASH = getHash(JSON.stringify(pm.response.json()));
        pm.test('QUICKY_AUTO - COMPARE RESPONSE HASH', () => { pm.expect(CURRENT_HASH).to.be.eq(RESPONSE_HASH); })
    },
    'X-QUICKY-UPDATE-RES-HASH': () => {
        pm.collectionVariables.set(`QUICKY_RES_HASH_${pm.info.requestId}`, getHash(JSON.stringify(pm.response.json())));
        pm.test('QUICKY_AUTO - SAVE RESPONSE HASH', true)
    },
    'X-QUICKY-VALIDATE-RES-SCHEMA': () => {
        const CURRENT_SCHEMA = eval(pm.collectionVariables.get(`QUICKY_RES_SCHEMA_${pm.info.requestId}`));
        if (!CURRENT_SCHEMA) throw new Error('QUICKY ERROR: Initial schema not setted, run with X-QUICKY-UPDATE-RES-SCHEMA header')
        pm.test('QUICKY_AUTO - VALIDATE RESPONSE SCHEMA', () => {
            try {
                const parsedData = CURRENT_SCHEMA.parse(pm.response.json());
                pm.expect(parsedData).to.be.an('object');
            } catch (error) {
                console.error("Validation errors:", error);
                pm.expect(error.errors).to.be.empty()
            }
        })
    },
    'X-QUICKY-UPDATE-RES-SCHEMA': () => {
        const rawZodString = jsonToZod(pm.response.json());
        const wrapped = `(function() {
            ${rawZodString}
            return schema;
        })()`;

        pm.collectionVariables.set(`QUICKY_RES_SCHEMA_${pm.info.requestId}`, wrapped);
        pm.test('QUICKY_AUTO - SAVE RESPONSE SCHEMA', true);
    },
    // 'X-QUICKY-RUN-AI-TESTS': () => {
    //     TODO: integrate with a general AI LLM to generate `pm.test` like tests based on the response
    // },
    // 'X-QUICKY-UPDATE-AI-TESTS': () => {
    //     TODO: iterate over AI generated test using `pm.sendRequest` and `pm.test` to validate
    // }
}

const QUICKY_HEADERS = Object.keys(QUICKY_ACTIONS)

const QUICKY_DEFAULT_ACTIONS = JSON.parse(pm.collectionVariables.get('QUICKY_DEFAULT_ACTIONS') ?? [])

QUICKY_DEFAULT_ACTIONS.forEach((header) => {
    pm.request.addHeader({ key: header })
})

const ACTIVE_FLAGS = (pm.request.headers ?? []).filter(header => !header.disabled).map(header => header.key)

const QUICKY_AUTO_ACTIONS = ACTIVE_FLAGS.filter((action) => action && QUICKY_ACTIONS[action]).map((action) => String(QUICKY_ACTIONS[action]))

pm.collectionVariables.set('QUICKY_AUTO_ACTIONS', JSON.stringify(QUICKY_AUTO_ACTIONS))

QUICKY_HEADERS.forEach((header) => {
    pm.request.removeHeader(header)
})

Post-request:

const { z } = pm.require('npm:[email protected]');
const { jsonToZod } = pm.require('npm:[email protected]');
const crypto = pm.require('npm:[email protected]')

const getHash = (text) => crypto.MD5(text).toString()

const ACTIONS_TO_PERFORM = JSON.parse(pm.collectionVariables.get("QUICKY_AUTO_ACTIONS"))

ACTIONS_TO_PERFORM.forEach((action) => {
    try {
        const funct = eval(action)
        funct()
    } catch (error) {
        console.log(error)
    }
})
1 Like

:bullseye: Validated & Generated – My June API Challenge with Zod, Chance & UUID

:wrench: What I Did:

  • Generated multiple fake contact form entries using chance
  • Validated each entry with zod (fields: name, email, subject, message, phone, age, and UUID)
  • Used uuid to assign unique IDs to each entry
  • Intentionally made one entry invalid to showcase validation logic
  • Displayed the results in a clean visualizer table

:white_check_mark: All validation feedback is shown live in the console and beautifully rendered in the Visualizer.

:package: External packages used:

  • chance
  • zod
  • uuid

My Postman Code

const Chance = pm.require('npm:chance');
const { z } = pm.require('npm:zod');
const { v4: uuidv4 } = pm.require('npm:uuid');

const chance = new Chance();

//define zod schema with UUID
const schema = z.object({
  id: z.string().uuid(),
  name: z.string().min(3),
  email: z.string().email(),
  subject: z.string().min(5),
  message: z.string().min(10),
  phone: z.string().regex(/^\d{10}$/),
  age: z.number().min(18).max(60)
});

//generate multiple user form entries
let formEntries = [];

for (let i = 0; i < 5; i++) {
  const isValid = i !== 3; // Intentionally make 4th entry invalid

  const user = isValid ? {
    id: uuidv4(),
    name: chance.name(),
    email: chance.email(),
    subject: chance.sentence({ words: 3 }),
    message: chance.paragraph(),
    phone: chance.string({ pool: '0123456789', length: 10 }),
    age: chance.age({ type: 'adult' })
  } : {
    id: 'not-a-uuid',
    name: 'A',
    email: 'bademail.com',
    subject: 'Hi',
    message: 'Short',
    phone: '1234',
    age: 12
  };

  const result = schema.safeParse(user);
  formEntries.push({
    ...user,
    status: result.success ? '✅ Passed' : '❌ Failed'
  });

  if (!result.success) {
    console.log(`❌ Entry ${i + 1} failed:`);
    result.error.issues.forEach((e, j) => {
      console.log(`  [${j + 1}] ${e.path.join('.')} - ${e.message}`);
    });
  }
}

//visualizer Output
pm.visualizer.set(`
  <div style="font-family:'Segoe UI',sans-serif;background:#fff;padding:25px;max-width:800px;margin:auto;">
    <h2>📋 Contact Form Validation Results (Zod + Chance + UUID)</h2>
    <table style="width:100%;border-collapse:collapse;font-size:14px;">
      <thead>
        <tr style="background:#eee;">
          <th style="padding:8px;border:1px solid #ccc;">#</th>
          <th style="padding:8px;border:1px solid #ccc;">ID</th>
          <th style="padding:8px;border:1px solid #ccc;">Name</th>
          <th style="padding:8px;border:1px solid #ccc;">Email</th>
          <th style="padding:8px;border:1px solid #ccc;">Phone</th>
          <th style="padding:8px;border:1px solid #ccc;">Age</th>
          <th style="padding:8px;border:1px solid #ccc;">Status</th>
        </tr>
      </thead>
      <tbody>
        {{#entries}}
          <tr>
            <td style="padding:6px;border:1px solid #ccc;">{{@index}}</td>
            <td style="padding:6px;border:1px solid #ccc;">{{id}}</td>
            <td style="padding:6px;border:1px solid #ccc;">{{name}}</td>
            <td style="padding:6px;border:1px solid #ccc;">{{email}}</td>
            <td style="padding:6px;border:1px solid #ccc;">{{phone}}</td>
            <td style="padding:6px;border:1px solid #ccc;">{{age}}</td>
            <td style="padding:6px;border:1px solid #ccc;">{{status}}</td>
          </tr>
        {{/entries}}
      </tbody>
    </table>
  </div>
`, {
  entries: formEntries
});

:framed_picture: Screenshot below to show it in action!

:books: What I Learned

This challenge helped me dive into:

  • Zod for clean schema validation
  • Chance.js for dynamic mock data
  • UUID for unique ID generation

I also discovered some hidden npm gems through official docs.

What made it special? Postman turned testing into a fun, hands-on learning journey.
From scripting to visualizing, it felt like building something real.
Postman wasn’t just a tool—it was my guide.

Let me know what you think — open to feedback :raising_hands:
Thanks to Postman for this creative challenge!
postman api testing #JuneChallenge

1 Like

:shield::fire: API Fort Knox: AES Encryption, JWT Signing, Replay Protection and Multi Hash Integrity All Inside Postman Scripts with External Packages

Hello Everyone i am Aditya D and this is my official entry for june API challenge!!
I did just not create a script but i also weaponized Postman!!!

:fire: What I Created:
a fully loaded enterprise grade API request signing + payload encryption + tamper proofing + replay attack protection system running entirely inside Postman’s scripting sandbox and leveraging external packages like a boss. And yes all automated which means no external server, no hacks and no gimmicks.

:package: External Packages I Summoned
1. :id_button: [email protected]:

What it is?
A universally Unique Identifier generator

Why have i used it?

  • To generate a nonce (number used once) for every request
  • Protects againts replay attacks by ensuring every request carries a one time random value.
  • UUID V4 gives a randomly generated 128 bit value

Example

const nonce = uuid.v4();

2. :locked: [email protected]:

What it is?
A javascript library of crypto standards, offering hashing algorithms, AES encryption, HMAC signatures, Base 64 encoding and more.

Why have i used it?

  • AES Encryption of request payload for confidentiality.
  • HMAC SHA256 and HMAC SHA512 signatures for message integrity and validation.
  • MD5 Hashing for lightweight integrity checks.
  • Base64 Encoding for generating JWT token parts (header, payload, signature).
  • SHA256 Hash for generating tmpter detection header value.

Example

const encryptedBody = cryptoJs.AES.encrypt(requestBody, secretPassphrase).toString();
const sha256Hash = cryptoJs.HmacSHA256(requestBody, "SecretKey256").toString();
const sha512Hash = cryptoJs.HmacSHA512(requestBody, "SecretKey512").toString();
const md5Hash = cryptoJs.MD5(requestBody).toString();

3. :globe_with_meridians: [email protected]
What it is?
A powerful modern alternative for Javascript’s native Date object makes handling dates, times, timezones and ISO 8601 formats super clean and reliable.

Why have i used it?

  • Generate multi timezone timestamps for UTC, IST, Tokyo, New York, London, Sydney.
  • Convert and format timestamps to ISO 8601 strings.
  • Add time based expiry for JWT tokens.
  • Validate timestamp freshness in post response tests.

Examples

const utcNow = luxon.DateTime.utc();
const localTime = luxon.DateTime.local().toISO();
const zones = {
'UTC': utcNow.toISO(),
'New_York': utcNow.setZone('America/New_York').toISO(),
'London': utcNow.setZone('Europe/London').toISO(),
'Tokyo': utcNow.setZone('Asia/Tokyo').toISO(),
'Sydney': utcNow.setZone('Australia/Sydney').toISO(),
'India': utcNow.setZone('Asia/Kolkata').toISO()
};

:gear: What this beast does:

Pre Request:

  • :locked: AES encrypts your request payload.
  • :locked_with_key: Generates a JWT token signed with HMAC SHA256.
  • :id_button: Spawns a unique UUID based nonce for replay protections.
  • :globe_with_meridians: Attaches multi timezones ISO timestamps.
  • :receipt: Computes HMAC SHA256, HMAC 512 and MD5 hashes.
  • :safety_pin: Injects all custom headers automatically.
  • :card_index_dividers: Generates a tamper detection integrity hash.
  • :bomb: Logs everything in console for traceability.

Post Response:

  • :open_book: Decrypts the AES payload.
  • :man_detective: Validates echoed headers (inside response JSON body since postman echo behaves that way).
  • :three_o_clock: checks timestamp freshness (within 5 minutes).
  • :shield: Revalidate nonce and transaction integrity.

:receipt: Scripts

Pre Request:

const uuid = pm.require('npm:[email protected]');
const cryptoJs = pm.require('npm:[email protected]');
const luxon = pm.require('npm:[email protected]');


// Get current UTC time and multiple timezones
const utcNow = luxon.DateTime.utc();
const localTime = luxon.DateTime.local().toISO();
const zones = {
'UTC': utcNow.toISO(),
'New_York': utcNow.setZone('America/New_York').toISO(),
'London': utcNow.setZone('Europe/London').toISO(),
'Tokyo': utcNow.setZone('Asia/Tokyo').toISO(),
'Sydney': utcNow.setZone('Australia/Sydney').toISO(),
'India': utcNow.setZone('Asia/Kolkata').toISO()
};

// Log all timezones
console.log("Multi-Zone Timestamps:", zones);

// UUID v4 Nonce
const nonce = uuid.v4();
pm.environment.set("nonce", nonce);

// Generate dynamic Request Body
const requestBody = JSON.stringify({
username: "aditya",
transaction: "postman-challenge",
issued_at: zones['UTC'],
nonce: nonce
});

// Encrypt request body using AES
const secretPassphrase = "UltraSecurePass123!";
const encryptedBody = cryptoJs.AES.encrypt(requestBody, secretPassphrase).toString();
pm.environment.set("encrypted_body", encryptedBody);

// Create multiple cryptographic signatures
const sha256Hash = cryptoJs.HmacSHA256(requestBody, "SecretKey256").toString();
const sha512Hash = cryptoJs.HmacSHA512(requestBody, "SecretKey512").toString();
const md5Hash = cryptoJs.MD5(requestBody).toString();

// Generate a JWT-like token
const header = cryptoJs.enc.Base64.stringify(cryptoJs.enc.Utf8.parse(JSON.stringify({alg:"HS256", typ:"JWT"})));
const payload = cryptoJs.enc.Base64.stringify(cryptoJs.enc.Utf8.parse(JSON.stringify({
iss: "PostmanChallenger",
sub: "advanced-api",
iat: Date.now(),
exp: Date.now() + 300000
})));
const signature = cryptoJs.HmacSHA256(header + "." + payload, "JWTSecret").toString(cryptoJs.enc.Base64);
const jwtToken = `${header}.${payload}.${signature}`;
pm.environment.set("jwt_token", jwtToken);

// Compute tamper-detection hash (header + timestamp + nonce)
const integrityCheck = cryptoJs.SHA256("Headers:" + zones['UTC'] + ":" + nonce).toString();
pm.environment.set("header_integrity_hash", integrityCheck);

// Set all values into headers
pm.request.headers.add({key: "X-Timestamp-UTC", value: zones['UTC']});
pm.request.headers.add({key: "X-Nonce", value: nonce});
pm.request.headers.add({key: "X-SHA256-Signature", value: sha256Hash});
pm.request.headers.add({key: "X-SHA512-Signature", value: sha512Hash});
pm.request.headers.add({key: "X-MD5-Body-Hash", value: md5Hash});
pm.request.headers.add({key: "X-JWT-Token", value: jwtToken});
pm.request.headers.add({key: "X-Integrity-Hash", value: integrityCheck});

// Log everything for auditing
console.log("Request Body:", requestBody);
console.log("Encrypted Body (AES):", encryptedBody);
console.log("SHA256:", sha256Hash);
console.log("SHA512:", sha512Hash);
console.log("MD5:", md5Hash);
console.log("JWT Token:", jwtToken);
console.log("Header Integrity Hash:", integrityCheck);
console.log("Nonce:", nonce);

Post Response:

const luxon = pm.require('npm:[email protected]');
const cryptoJs = pm.require('npm:[email protected]');


// Get values from environment variables
const expectedNonce = pm.environment.get("nonce");
const expectedJWT = pm.environment.get("jwt_token");
const expectedIntegrityHash = pm.environment.get("header_integrity_hash");
const secretPassphrase = "UltraSecurePass123!";

// Parse JSON in response
const responseData = pm.response.json();

// Test that headers are repeated back correctly
pm.test("Nonce exists in response body headers", function () {
  const responseJson = pm.response.json();
  pm.expect(responseJson.headers["x-nonce"]).to.eql(expectedNonce);
});

pm.test("JWT Token matches", function () {
  const responseJson = pm.response.json();
  pm.expect(responseJson.headers["x-jwt-token"]).to.eql(expectedJWT);
});

pm.test("Header Integrity Hash matches", function () {
  const responseJson = pm.response.json();
  pm.expect(responseJson.headers["x-integrity-hash"]).to.eql(expectedIntegrityHash);
});


// Decrypt and parse encrypted body (AES)
const encryptedBody = pm.environment.get("encrypted_body");
const decryptedBytes = cryptoJs.AES.decrypt(encryptedBody, secretPassphrase);
const decryptedBody = decryptedBytes.toString(cryptoJs.enc.Utf8);
const decryptedData = JSON.parse(decryptedBody);

// Log decrypted body to console for verification
console.log("Decrypted Body (AES):", decryptedData);

// Validate timestamp recency (within 5 minutes)
const requestIssuedAt = luxon.DateTime.fromISO(decryptedData.issued_at);
const nowUtc = luxon.DateTime.utc();
const diffInSeconds = nowUtc.diff(requestIssuedAt, 'seconds').seconds;

pm.test("Timestamp is fresh (within 300s)", function () {
pm.expect(diffInSeconds).to.be.lessThan(300);
});

// Verify nonce and transaction
pm.test("Decrypted transaction data is not corrupted", function () {
pm.expect(decryptedData.transaction).to.equal("postman-challenge");
pm.expect(decryptedData.nonce).to.equal(expectedNonce);
});

:camera_with_flash: Screenshots and script in Action


:bullseye: Why this isn’t just cool and Extremely Useful:
:white_check_mark: Api engineers can prototype enterprise security workflows without needing backend services.
:white_check_mark: Simulates real world secure request signing and encryption flows for banking, fintech, web3 and defense grade APIs.
:white_check_mark: Replay attack protection using Nonce and Timestamp.
:white_check_mark: Verifiable payload encryption via AES.
:white_check_mark: Integrity validation using multi hash signatures.
:white_check_mark: JWT token generation and validation demo without external servers.

:high_voltage:TL;DR:

Postman Sandbox + External Packages + Insane Scripting = API Fort Knox
If you’re building APIs that handle sensitive data or want to simulate secured, signed, encrypted API calls entirely in postman this is your toolkit!!.

:sign_of_the_horns: Signing Off:
If you loved this, drop a like and reply , would love to see what other wild things folks built this month. Big shoutout to Postman team for cooking this beautiful external packages madness in the form of June API Challenge!! :fire:.
postman api testing #JuneChallenge

Aditya D
DevOps Engineer

1 Like