Back to Blog

API Automation with Nova Scripts: A Complete Guide to RESTK's Scripting Framework

Learn how to automate API testing workflows with RESTK's Nova scripting framework. Write pre-request scripts, test assertions, chain requests, and build complex automation flows.

RESTK Team
15 min read

API automation testing saves time, catches regressions, and gives you confidence that your services behave correctly under real-world conditions. But automation is only as good as the scripting framework behind it. If the syntax is clunky, the APIs are limited, or the tooling fights you at every step, the automation ends up being more work than the manual testing it was supposed to replace.

That is why we built Nova -- RESTK's scripting framework designed specifically for API automation. Nova provides a clean, modern, type-safe scripting API through the nova.* namespace that handles everything from simple assertions to complex multi-step workflows. This guide covers the entire framework from first principles through advanced patterns, with code examples you can use as reference in your own work.

What Is Nova Scripting?

Nova is RESTK's proprietary scripting framework for automating API workflows. Every script you write in RESTK -- whether it runs before a request, after a response, or at the collection level -- uses the nova.* namespace to interact with requests, responses, variables, and test assertions.

If you have used Postman's pm.* scripting API, the concept is familiar. The difference is that Nova was designed from scratch with modern JavaScript conventions, a consistent API surface, and built-in type safety. There is no legacy baggage, no inconsistent method signatures, and no hidden gotchas.

Nova scripts run in a local JavaScript environment inside RESTK. They execute locally on your machine -- no data is sent to external servers during script execution. This means your auth tokens, request bodies, and test logic remain private.

Getting Started

Where to Write Scripts

Every request in RESTK has a Scripts tab with two sections:

  • Pre-script: Runs before the request is sent. Use it to modify headers, set dynamic values, generate timestamps, or conditionally alter the request.
  • Post-script: Runs after the response is received. Use it to validate the response, extract data, set variables for subsequent requests, or run test assertions.

To open the script editor, select any request in your collection and click the Scripts tab. The editor includes syntax highlighting, autocomplete for the nova.* API, and inline error reporting.

Script Execution Order

Scripts run at multiple levels in a specific order:

1. Collection pre-script
2. Folder pre-script
3. Request pre-script
4. --- HTTP Request Sent ---
5. Request post-script
6. Folder post-script
7. Collection post-script

This hierarchy lets you define shared setup logic at the collection or folder level (such as authentication) and override or extend it at the request level.

The nova.* Namespace API

The Nova API is organized into clear, logical groups. Here is the complete reference.

Variables: nova.variable

Variables are how you pass data between requests, store tokens, and parameterize your workflows.

// Set a variable (accessible across all requests)
nova.variable.set("user_id", "12345");

// Get a variable
const userId = nova.variable.get("user_id");

// Check if a variable exists
if (nova.variable.has("access_token")) {
  // token is available
}

// Remove a variable
nova.variable.unset("temp_value");

Variables resolve in priority order: request variables > folder variables > collection variables > environment variables. You can also target a specific scope:

// Environment variables
nova.environment.set("base_url", "https://api.staging.example.com");
nova.environment.get("base_url");

// Collection variables (shared across all requests in the collection)
nova.collectionVariables.set("api_version", "v2");
nova.collectionVariables.get("api_version");

// Folder variables
nova.folderVariables.set("folder_token", "abc123");

// Request variables
nova.requestVariables.set("request_specific", "value");

Reference variables in the UI using double curly braces: {{base_url}}/users/{{user_id}}.

Testing: nova.test() and nova.expect()

The testing API lets you write assertions that appear in RESTK's test results panel.

nova.test("Test name", function() {
  // assertions go here
});

The nova.expect() method provides a rich set of matchers:

nova.expect(value).toBe(expected);           // Strict equality
nova.expect(value).toBeDefined();            // Not undefined
nova.expect(value).toBeTruthy();             // Boolean true
nova.expect(value).toBeGreaterThan(10);      // Numeric comparison
nova.expect(value).toBeLessThan(100);        // Numeric comparison
nova.expect(array).toContain(item);          // Array contains
nova.expect(object).toHaveProperty("key");   // Object has property
nova.expect(string).toMatch(/pattern/);      // Regex match

Response: nova.response

Available in post-scripts, this object gives you full access to the HTTP response.

nova.response.status          // HTTP status code (e.g., 200)
nova.response.body            // Raw response body
nova.response.responseTime    // Response time in milliseconds

// Parse body as JSON
const data = nova.response.json();

// Get body as text
const text = nova.response.text();

// Access response headers
const contentType = nova.response.headers.get("Content-Type");

Request: nova.request

Available in pre-scripts, this object lets you modify the request before it is sent.

// Read request properties
nova.request.url       // Full URL string
nova.request.method    // "GET", "POST", etc.
nova.request.body      // Request body

// Add headers
nova.request.headers.add({
  key: "X-Request-ID",
  value: nova.uuid()
});

// Set JSON body
nova.request.setJsonBody({
  username: "john",
  timestamp: Date.now()
});

// Set authentication
nova.request.setBearerToken(nova.variable.get("access_token"));
nova.request.setBasicAuth("username", "password");

Environment: nova.environment

Direct access to environment-scoped variables.

nova.environment.get("api_url");
nova.environment.set("api_url", "https://api.production.example.com");
nova.environment.has("api_key");
nova.environment.unset("deprecated_var");

Built-in Utilities

Nova includes helper functions you will use constantly:

nova.uuid()              // Generate a UUID v4
nova.timestamp()         // Unix timestamp in seconds
nova.timestampMs()       // Unix timestamp in milliseconds
nova.isoDate()           // ISO 8601 date string
nova.randomInt(1, 100)   // Random integer in range
nova.log("message")      // Log to the console

Crypto API

For signing requests, generating hashes, and encoding data:

nova.crypto.md5("string to hash");
nova.crypto.sha256("string to hash");
nova.crypto.hmacSha256("message", "secret-key");
nova.crypto.base64Encode("string to encode");
nova.crypto.base64Decode("encoded-string");

Pre-Request Scripts

Pre-scripts run before the request is sent. They are your opportunity to dynamically configure the request based on runtime conditions.

Dynamic Timestamps

Many APIs require a timestamp header or parameter for request signing:

// Add a timestamp header
nova.request.headers.add({
  key: "X-Timestamp",
  value: String(nova.timestamp())
});

// Add an ISO date to the request body
const body = JSON.parse(nova.request.body || "{}");
body.created_at = nova.isoDate();
nova.request.setJsonBody(body);

Auth Token Generation

Generate or refresh tokens before each request:

// Check if token has expired
const expiresAt = parseInt(nova.variable.get("token_expires") || "0");
const now = Date.now();

if (now > expiresAt) {
  // Token is expired -- set a flag for manual refresh
  nova.log("Token expired. Please re-authenticate.");
}

// Always attach the current token
const token = nova.variable.get("access_token");
if (token) {
  nova.request.setBearerToken(token);
}

HMAC Signature Generation

APIs like AWS, Stripe webhooks, and payment gateways often require HMAC signatures:

// Generate HMAC signature for request signing
const secret = nova.variable.get("api_secret");
const timestamp = String(nova.timestamp());
const method = nova.request.method;
const path = "/api/v1/orders";

// Create the signing string
const signingString = [timestamp, method, path].join("\n");

// Generate HMAC-SHA256 signature
const signature = nova.crypto.hmacSha256(signingString, secret);

// Add signature headers
nova.request.headers.add({ key: "X-Timestamp", value: timestamp });
nova.request.headers.add({ key: "X-Signature", value: signature });

Conditional Logic

Modify the request based on environment or context:

const env = nova.environment.get("ENV");

if (env === "production") {
  nova.request.headers.add({
    key: "X-Rate-Limit-Bypass",
    value: nova.variable.get("prod_bypass_key")
  });
} else {
  nova.request.headers.add({
    key: "X-Debug",
    value: "true"
  });
}

Post-Request Scripts and Test Assertions

Post-scripts run after the response is received. This is where you validate behavior and extract data.

Status Code Validation

The most fundamental assertion -- did the API respond with the expected status:

nova.test("Status is 200 OK", function() {
  nova.expect(nova.response.status).toBe(200);
});

// For creation endpoints
nova.test("Status is 201 Created", function() {
  nova.expect(nova.response.status).toBe(201);
});

// For delete endpoints
nova.test("Status is 204 No Content", function() {
  nova.expect(nova.response.status).toBe(204);
});

Body Validation

Check that the response body contains the expected structure and values:

nova.test("Response contains user data", function() {
  const data = nova.response.json();

  // Check top-level properties
  nova.expect(data).toHaveProperty("id");
  nova.expect(data).toHaveProperty("email");
  nova.expect(data).toHaveProperty("name");

  // Check specific values
  nova.expect(data.email).toMatch(/@/);
  nova.expect(data.name).toBeDefined();
});

// Validate nested objects
nova.test("User has address", function() {
  const user = nova.response.json();

  nova.expect(user).toHaveProperty("address");
  nova.expect(user.address).toHaveProperty("city");
  nova.expect(user.address).toHaveProperty("country");
});

// Validate arrays
nova.test("Returns a list of users", function() {
  const data = nova.response.json();

  nova.expect(data.users.length).toBeGreaterThan(0);
  nova.expect(data.users[0]).toHaveProperty("id");
});

Response Time Checks

Ensure your API responds within acceptable latency thresholds:

nova.test("Response time is under 500ms", function() {
  nova.expect(nova.response.responseTime).toBeLessThan(500);
});

nova.test("Response time is under 2 seconds", function() {
  nova.expect(nova.response.responseTime).toBeLessThan(2000);
});

Schema Validation

Validate that every item in a collection response has the required fields:

nova.test("All items have required fields", function() {
  const items = nova.response.json().data;

  items.forEach(function(item) {
    nova.expect(item).toHaveProperty("id");
    nova.expect(item).toHaveProperty("name");
    nova.expect(item).toHaveProperty("created_at");
    nova.expect(item).toHaveProperty("status");
  });
});

nova.test("Status values are valid", function() {
  const items = nova.response.json().data;
  const validStatuses = ["active", "inactive", "pending"];

  items.forEach(function(item) {
    nova.expect(validStatuses).toContain(item.status);
  });
});

Header Validation

Check that the response includes expected headers:

nova.test("Response has correct content type", function() {
  const contentType = nova.response.headers.get("Content-Type");
  nova.expect(contentType).toMatch(/application\/json/);
});

nova.test("Response includes pagination headers", function() {
  nova.expect(nova.response.headers.get("X-Total-Count")).toBeDefined();
  nova.expect(nova.response.headers.get("X-Page")).toBeDefined();
});

Request Chaining

One of the most powerful patterns in API automation testing is request chaining -- using the output of one request as the input for the next.

Example: Login and Use Token

Step 1: Create a "Login" request to POST /auth/login. In its post-script:

nova.test("Login successful", function() {
  nova.expect(nova.response.status).toBe(200);
});

const data = nova.response.json();

if (data && data.access_token) {
  nova.variable.set("access_token", data.access_token);
  nova.variable.set("refresh_token", data.refresh_token);

  // Store expiry for later checks
  const expiresAt = Date.now() + (data.expires_in * 1000);
  nova.variable.set("token_expires", String(expiresAt));

  nova.log("Tokens stored successfully");
}

Step 2: In all subsequent requests, reference the token. In the URL bar or headers, use {{access_token}}. Or set it programmatically in a pre-script:

nova.request.setBearerToken(nova.variable.get("access_token"));

Step 3: Create a "Get User Profile" request to GET /users/me. In its post-script, extract more data:

const user = nova.response.json();
nova.variable.set("user_id", String(user.id));
nova.variable.set("user_email", user.email);

if (user.organization) {
  nova.variable.set("org_id", String(user.organization.id));
}

Step 4: Use {{user_id}} and {{org_id}} in downstream requests like GET /organizations/{{org_id}}/members.

When you run these requests in sequence using RESTK's collection runner, the variables chain automatically from one request to the next.

Collection-Level and Folder-Level Scripts

Scripts are not limited to individual requests. You can attach pre-scripts and post-scripts to collections and folders, and they execute in the hierarchical order described earlier.

Collection Pre-Script

Attach a pre-script to the collection itself to run shared setup logic before every request in that collection:

// Collection pre-script: runs before EVERY request
// Attach the auth token to all requests automatically
const token = nova.variable.get("access_token");
if (token) {
  nova.request.setBearerToken(token);
}

// Add a correlation ID for tracing
nova.request.headers.add({
  key: "X-Correlation-ID",
  value: nova.uuid()
});

Folder Pre-Script

Use folder-level scripts to apply logic to a subset of requests. For example, a folder called "Admin Endpoints" might require an admin token:

// Folder pre-script for "Admin Endpoints"
const adminToken = nova.variable.get("admin_token");

if (adminToken) {
  nova.request.setBearerToken(adminToken);
} else {
  nova.log("WARNING: admin_token not set. Admin requests will fail.");
}

Folder Post-Script

Run common validation after every request in a folder:

// Folder post-script: validate all responses in this folder
nova.test("Response is JSON", function() {
  const contentType = nova.response.headers.get("Content-Type");
  nova.expect(contentType).toMatch(/application\/json/);
});

nova.test("No server errors", function() {
  nova.expect(nova.response.status).toBeLessThan(500);
});

Skipping Parent Scripts

If a specific request should not inherit collection or folder scripts, check the Skip Parent Scripts option on that request. This is useful for requests like health checks or public endpoints that do not need authentication.

Advanced Patterns

Conditional Request Execution

Use pre-scripts to skip requests based on conditions:

// Pre-script: only run if we have a user to delete
const userId = nova.variable.get("user_id_to_delete");

if (!userId) {
  nova.log("No user to delete. Skipping this request.");
  // Set a flag that the post-script can check
  nova.variable.set("skipped", "true");
}

Data-Driven Testing with Dynamic Variables

Generate dynamic test data for each request:

// Pre-script: generate unique test data
const uniqueId = nova.uuid().substring(0, 8);

nova.request.setJsonBody({
  name: "Test User " + uniqueId,
  email: "test-" + uniqueId + "@example.com",
  external_id: uniqueId,
  created_at: nova.isoDate()
});

Error Handling in Scripts

Write resilient scripts that handle unexpected responses gracefully:

nova.test("Response is parseable", function() {
  let data;
  try {
    data = nova.response.json();
  } catch (e) {
    nova.log("Failed to parse response as JSON: " + e.message);
    nova.expect(true).toBe(false); // Force test failure
    return;
  }

  nova.expect(data).toBeDefined();
});

// Handle different status codes
if (nova.response.status === 200) {
  const data = nova.response.json();
  nova.variable.set("result_id", String(data.id));
} else if (nova.response.status === 429) {
  nova.log("Rate limited. Retry after: " +
    nova.response.headers.get("Retry-After"));
} else if (nova.response.status >= 500) {
  nova.log("Server error: " + nova.response.status);
  const error = nova.response.json();
  nova.log("Error details: " + JSON.stringify(error));
}

Extracting and Transforming Data

Process response data before storing it:

// Extract the latest order from a list
const orders = nova.response.json().data;
const latestOrder = orders.sort(function(a, b) {
  return new Date(b.created_at) - new Date(a.created_at);
})[0];

nova.variable.set("latest_order_id", String(latestOrder.id));
nova.variable.set("latest_order_status", latestOrder.status);
nova.variable.set("latest_order_total", String(latestOrder.total));

Nova vs Postman Scripts: A Comparison

If you are migrating from Postman or evaluating alternatives, here is how the two scripting frameworks compare:

FeatureNova (nova.*)Postman (pm.*)
Variable get/setnova.variable.get() / .set()pm.variables.get() / .set()
Environment accessnova.environment.get() / .set()pm.environment.get() / .set()
Test assertionsnova.test() + nova.expect()pm.test() + pm.expect()
Response accessnova.response.json()pm.response.json()
Response statusnova.response.statuspm.response.code
Response timenova.response.responseTimepm.response.responseTime
Request modificationnova.request.headers.add()pm.request.headers.add()
Set bearer tokennova.request.setBearerToken()Manual header manipulation
Set JSON bodynova.request.setJsonBody()Manual body string manipulation
UUID generationnova.uuid()Requires external library or $guid
Crypto/HMACnova.crypto.hmacSha256()Requires CryptoJS import
Timestampnova.timestamp()Date.now() / 1000
Collection authnova.collectionAuth.setBearerToken()Not available
Folder authnova.folderAuth.setBearerToken()Not available
Script executionLocalCloud-connected runtime
PrivacyAll data stays localData may sync to cloud

Key differences in practice:

  • Nova has convenience methods like setBearerToken(), setJsonBody(), and setBasicAuth() that reduce boilerplate. In Postman, you manually construct header strings.
  • Nova includes built-in crypto without needing to import external libraries. Postman requires importing CryptoJS and managing library dependencies.
  • Nova supports parent auth manipulation. You can set authentication on the collection or folder from within a script using nova.collectionAuth and nova.folderAuth. This is useful for OAuth token refresh flows.
  • Nova scripts run entirely offline. No cloud connection is required for script execution, variable resolution, or test assertions.

Migrating Postman Scripts

If you import a Postman collection into RESTK, pm.* scripts are automatically preserved and remain functional. You do not need to rewrite them immediately. Over time, you can migrate to nova.* syntax to take advantage of the additional convenience methods and built-in capabilities.

How MCP Can Generate Nova Scripts for You

RESTK's local MCP integration adds another dimension to Nova scripting: AI-assisted script generation. Instead of writing every assertion by hand, you can describe what you want in natural language and let your local AI model generate the Nova script.

For example, after receiving a response, you can ask:

"Write tests that verify the response is a paginated list, each item has an id and email, the total count matches the array length, and the response time is under 500ms."

The AI, running locally through MCP, generates:

nova.test("Status is 200", function() {
  nova.expect(nova.response.status).toBe(200);
});

nova.test("Response is paginated", function() {
  const data = nova.response.json();
  nova.expect(data).toHaveProperty("data");
  nova.expect(data).toHaveProperty("total");
  nova.expect(data).toHaveProperty("page");
});

nova.test("All items have required fields", function() {
  const items = nova.response.json().data;
  items.forEach(function(item) {
    nova.expect(item).toHaveProperty("id");
    nova.expect(item).toHaveProperty("email");
  });
});

nova.test("Total matches array length", function() {
  const data = nova.response.json();
  nova.expect(data.total).toBe(data.data.length);
});

nova.test("Response time under 500ms", function() {
  nova.expect(nova.response.responseTime).toBeLessThan(500);
});

Because MCP runs locally, the AI sees your actual response data and generates assertions tailored to the real payload structure -- not generic boilerplate. And none of your data leaves your machine during this process.

This is particularly valuable when onboarding to a new API. Import the OpenAPI spec, run a few requests, and let MCP generate a comprehensive test suite based on the actual responses. You can then refine and extend the generated scripts as needed.

Putting It All Together

Here is a complete example of an automated workflow that demonstrates the patterns covered in this guide. This collection tests a user management API:

Collection pre-script (runs before every request):

// Attach auth token to all requests
const token = nova.variable.get("access_token");
if (token) {
  nova.request.setBearerToken(token);
}

// Add correlation ID for distributed tracing
nova.request.headers.add({
  key: "X-Correlation-ID",
  value: nova.uuid()
});

Request 1: Login (POST /auth/login) -- post-script:

nova.test("Login successful", function() {
  nova.expect(nova.response.status).toBe(200);
  nova.expect(nova.response.json()).toHaveProperty("access_token");
});

const auth = nova.response.json();
nova.variable.set("access_token", auth.access_token);

Request 2: Create User (POST /users) -- pre-script:

const uniqueId = nova.uuid().substring(0, 8);
nova.request.setJsonBody({
  name: "Test User " + uniqueId,
  email: "test-" + uniqueId + "@example.com"
});
nova.variable.set("test_email", "test-" + uniqueId + "@example.com");

Request 2: Create User -- post-script:

nova.test("User created", function() {
  nova.expect(nova.response.status).toBe(201);
});

const user = nova.response.json();
nova.variable.set("created_user_id", String(user.id));

Request 3: Get User (GET /users/{{created_user_id}}) -- post-script:

nova.test("Returns correct user", function() {
  const user = nova.response.json();
  nova.expect(user.email).toBe(nova.variable.get("test_email"));
});

Request 4: Delete User (DELETE /users/{{created_user_id}}) -- post-script:

nova.test("User deleted", function() {
  nova.expect(nova.response.status).toBe(204);
});

Run this entire collection using RESTK's Collection Runner, and all four requests execute in sequence with variables chaining automatically between them. You get a clear test results summary showing which assertions passed and which failed.

Next Steps

This guide covers the core of what Nova scripting offers for API automation testing. To go deeper:

Download RESTK and start automating your API tests today.


Related reading:

Questions about Nova scripting? Join our Discord community or email [email protected] and we will help you get set up.