Skip to main content

API Client

@testplanit/api is the official JavaScript/TypeScript API client for TestPlanIt. It provides a type-safe interface for interacting with the TestPlanIt API.

Installation​

npm install @testplanit/api
# or
pnpm add @testplanit/api
# or
yarn add @testplanit/api

Quick Start​

import { TestPlanItClient } from '@testplanit/api';

const client = new TestPlanItClient({
baseUrl: 'https://testplanit.example.com',
apiToken: 'tpi_your_token_here',
});

// Create a test run (find your project ID on the [Project Overview](../user-guide/project-overview.md) page)
const testRun = await client.createTestRun({
projectId: 1,
name: 'Automated Test Run',
});

// Get status ID for "passed"
const passedStatusId = await client.getStatusId(1, 'passed');

// Add a test result
await client.createTestResult({
testRunId: testRun.id,
testRunCaseId: 123,
statusId: passedStatusId,
elapsed: 15, // seconds
});

// Complete the test run
await client.completeTestRun(testRun.id);

Configuration​

const client = new TestPlanItClient({
// Required
baseUrl: 'https://testplanit.example.com',
apiToken: 'tpi_your_token_here',

// Optional
timeout: 30000, // Request timeout in ms (default: 30000)
maxRetries: 3, // Number of retries for failed requests (default: 3)
retryDelay: 1000, // Delay between retries in ms (default: 1000)
headers: {}, // Custom headers to include in all requests
});
OptionTypeRequiredDefaultDescription
baseUrlstringYes-Base URL of your TestPlanIt instance
apiTokenstringYes-API token for authentication (starts with tpi_)
timeoutnumberNo30000Request timeout in milliseconds
maxRetriesnumberNo3Number of retries for failed requests
retryDelaynumberNo1000Delay between retries in milliseconds
headersobjectNoCustom headers to include in all requests

Authentication​

Generate an API token from your TestPlanIt instance:

  1. Go to Settings > API Tokens
  2. Click Generate New Token
  3. Copy the token (it starts with tpi_)

Store the token securely using environment variables:

const client = new TestPlanItClient({
baseUrl: process.env.TESTPLANIT_URL,
apiToken: process.env.TESTPLANIT_API_TOKEN,
});

API Reference​

Projects​

// Get a project by ID
const project = await client.getProject(1);

// List all accessible projects
const projects = await client.listProjects();

Test Runs​

// Create a new test run
const testRun = await client.createTestRun({
projectId: 1,
name: 'My Test Run',
testRunType: 'REGULAR', // Optional: 'REGULAR', 'JUNIT', 'TESTNG', etc.
configId: 1, // Optional: Configuration ID
milestoneId: 1, // Optional: Milestone ID
stateId: 1, // Optional: Workflow state ID
});

// Get a test run by ID
const testRun = await client.getTestRun(123);

// Update a test run
await client.updateTestRun(123, {
name: 'Updated Name',
isCompleted: true,
});

// Complete a test run (mark as finished)
await client.completeTestRun(123);

// List test runs with pagination
const { data, totalCount, pageCount } = await client.listTestRuns({
projectId: 1,
page: 1,
pageSize: 25,
search: 'smoke', // Optional: search term
runType: 'automated', // Optional: 'manual', 'automated', or 'both'
});

Test Cases​

// Create a test case
const testCase = await client.createTestCase({
projectId: 1,
folderId: 1,
templateId: 1,
name: 'Login should work',
className: 'AuthTests', // Optional: suite/class name
source: 'API', // Optional: 'MANUAL', 'JUNIT', 'API', etc.
automated: true, // Optional: whether case is automated
});

// Create many test cases in one request (with steps, tags, and custom fields).
// Far faster than createTestCase per case; returns a per-case result so
// partial failures are visible. Requires a TestPlanIt instance (app v0.39.0+).
const bulk = await client.createTestCases({
projectId: 1,
folderId: 1, // Batch default; each case may override folderId
templateId: 1, // Optional: defaults to the project's Default template
cases: [
{
name: 'Login with valid credentials',
steps: [{ text: 'Open /login', expectedResult: 'Form renders' }],
tags: ['regression', 5], // Tag names (created if missing) or IDs
customFields: { Priority: 'High' }, // Validated against the template
},
{ name: 'Login with invalid credentials' },
],
});
// bulk.results: [{ id, name, status: 'success', caseId } | { ..., status: 'error', error }]
console.log(`${bulk.importedCount} created, ${bulk.failedCount} failed`);

// Get a test case by ID
const testCase = await client.getTestCase(456);

// Find test cases by criteria
const cases = await client.findTestCases({
projectId: 1,
name: 'Login', // Optional: filter by name
className: 'AuthTests', // Optional: filter by class name
source: 'API', // Optional: filter by source
});

// Find or create a test case (useful for auto-creating cases)
const testCase = await client.findOrCreateTestCase({
projectId: 1,
folderId: 1,
templateId: 1,
name: 'Login should work',
className: 'AuthTests',
});

QuickScript Generation​

Generate an AI-authored automation script from one or more stored test cases. QuickScript must be enabled for the project (Project Settings → QuickScript), and an AI provider must be configured. When a code repository is connected to the project, generation follows the repo's existing framework, fixtures, and page objects — this takes precedence over the export template's framework. When no AI provider is configured or generation fails, each file falls back to the deterministic template render (generatedBy: 'template').

const result = await client.generateQuickScript({
projectId: 1,
caseIds: [456, 457],
templateId: 3, // Optional: defaults to the project's default/assigned template
outputMode: 'combined', // 'combined' (default): one file for all cases; 'perCase': one file per case
});

console.log(result.framework, result.language, result.fileExtension);
for (const file of result.results) {
// file.code — the generated script; file.generatedBy — 'ai' | 'template'
// file.caseName — for naming; file.contextFiles — repo files used as context
console.log(file.caseName, file.generatedBy);
}

Test Run Cases​

Link test cases to test runs:

// Add a test case to a run
const testRunCase = await client.addTestCaseToRun({
testRunId: 123,
repositoryCaseId: 456,
assignedToId: 'user-id', // Optional: assign to user
});

// Get all test cases in a run
const cases = await client.getTestRunCases(123);

// Find a specific test run case
const testRunCase = await client.findTestRunCase(123, 456);

// Find or add a test case to a run (avoids duplicates)
const testRunCase = await client.findOrAddTestCaseToRun({
testRunId: 123,
repositoryCaseId: 456,
});

Test Results​

// Create a test result
const result = await client.createTestResult({
testRunId: 123,
testRunCaseId: 456,
statusId: 1, // Use getStatusId() to get the correct ID
elapsed: 15, // Optional: duration in seconds
notes: 'Test passed successfully', // Optional: plain text, or a Tiptap document object
evidence: { // Optional: test evidence/logs
logs: ['Step 1 completed', 'Step 2 completed']
},
attempt: 1, // Optional: retry attempt number
});

// Get all results for a test run
const results = await client.getTestResults(123);

Status Mappings​

TestPlanIt has configurable statuses per project. Use these methods to work with statuses:

// Get all statuses for a project
const statuses = await client.getStatuses(projectId);

// Get status ID by normalized name
// Supports: 'passed', 'failed', 'skipped', 'blocked', 'pending'
const passedId = await client.getStatusId(projectId, 'passed');
const failedId = await client.getStatusId(projectId, 'failed');
const skippedId = await client.getStatusId(projectId, 'skipped');

// Clear status cache (if statuses are updated)
client.clearStatusCache();

The getStatusId method automatically matches:

  • System names (e.g., passed, failed)
  • Display names (e.g., Passed, Failed)
  • Aliases configured in TestPlanIt

Attachments​

// Upload an attachment to a test result
const attachment = await client.uploadAttachment(
testRunResultId, // Test result ID
fileBuffer, // Buffer or Blob
'screenshot.png', // File name
'image/png' // MIME type (optional)
);

Run-Level Attachments & Metadata​

Attach links, files, and key/value metadata to the test run itself (not to an individual result) — they render on the run detail page:

// Attach an external link (e.g. the CI build that produced the run).
// Rendered as a clickable link attachment.
await client.addTestRunLink(
testRunId,
'https://ci.example.com/job/42', // URL
'CI Build #42', // Display name (optional, defaults to URL)
);

// Attach a file to the run
await client.uploadTestRunAttachment(
testRunId,
reportBuffer, // Buffer or Blob
'report.html', // File name
'text/html' // MIME type (optional)
);

// Set key/value metadata — rendered as "**key:** value" lines in the
// run's documentation field. Repeated calls merge: existing keys are
// updated, new keys appended, other documentation content is preserved.
await client.setTestRunMetadata(testRunId, {
version: '1.2.3',
triggeredBy: 'jenkins',
retries: 2,
});

// Read the metadata back (values are always strings)
const metadata = await client.getTestRunMetadata(testRunId);

The attachment's creator is resolved server-side from the API token — no user ID needs to be supplied.

tip

Using a test framework reporter? Both expose all of this without importing the client — WebdriverIO declaratively and via browser.testplanit (Run Attachments & Metadata), Playwright declaratively and via attachToRun/setRunMetadata helpers (Run Attachments & Metadata).

Bulk Import​

Import test results from JUnit, TestNG, xUnit, NUnit, MSTest, Mocha, or Cucumber format:

const { testRunId } = await client.importTestResults(
{
projectId: 1,
files: [junitXmlFile], // File[] or Blob[]
format: 'auto', // 'auto', 'junit', 'testng', etc.
name: 'CI Build #123', // Optional: run name
testRunId: 456, // Optional: existing run to append to
configId: 1, // Optional
milestoneId: 2, // Optional
stateId: 3, // Optional
parentFolderId: 10, // Optional
templateId: 1, // Optional
tagIds: [1, 2], // Optional
},
(event) => {
// Progress callback
console.log(`${event.progress}%: ${event.status}`);
if (event.complete) {
console.log('Import complete! Test run ID:', event.testRunId);
}
}
);

Connection Testing​

// Test the API connection
const isConnected = await client.testConnection();
if (!isConnected) {
console.error('Failed to connect to TestPlanIt');
}

// Get the configured base URL
const baseUrl = client.getBaseUrl();

Error Handling​

import { TestPlanItClient, TestPlanItError } from '@testplanit/api';

try {
await client.createTestRun({ projectId: 999, name: 'Test' });
} catch (error) {
if (error instanceof TestPlanItError) {
console.error('API Error:', error.message);
console.error('Status Code:', error.statusCode);
console.error('Error Code:', error.code);
console.error('Details:', error.details);
}
}

Common Error Codes​

Status CodeDescription
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing API token
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
429Too Many Requests - Rate limited
500Internal Server Error

Retry Logic​

The client automatically retries failed requests:

  • Retries on: 5xx server errors, 429 rate limiting, network errors
  • Does not retry on: 4xx client errors (except 429)
  • Default: 3 retries with exponential backoff

Configure retry behavior:

const client = new TestPlanItClient({
baseUrl: 'https://testplanit.example.com',
apiToken: 'tpi_your_token',
maxRetries: 5, // More retries
retryDelay: 2000, // Longer delay between retries
});

TypeScript Support​

The package includes full TypeScript definitions. All types are exported:

import type {
TestPlanItClientConfig,
TestRun,
TestRunType,
RepositoryCase,
RepositoryCaseSource,
TestRunCase,
TestRunResult,
Status,
NormalizedStatus,
CreateTestRunOptions,
CreateTestResultOptions,
// ... and more
} from '@testplanit/api';

Complete Example​

Here's a complete example of creating a test run and reporting results:

import { TestPlanItClient, TestPlanItError } from '@testplanit/api';

async function reportTestResults() {
const client = new TestPlanItClient({
baseUrl: process.env.TESTPLANIT_URL,
apiToken: process.env.TESTPLANIT_API_TOKEN,
});

try {
// Create a new test run
const testRun = await client.createTestRun({
projectId: 1,
name: `CI Build #${process.env.CI_BUILD_NUMBER}`,
configId: 2, // e.g., "Chrome/macOS"
milestoneId: 5, // e.g., "Sprint 10"
});

console.log(`Created test run: ${testRun.id}`);

// Get status IDs
const passedStatus = await client.getStatusId(1, 'passed');
const failedStatus = await client.getStatusId(1, 'failed');

// Example test results
const results = [
{ caseId: 101, status: 'passed', duration: 1200 },
{ caseId: 102, status: 'failed', duration: 3500, error: 'Assertion failed' },
{ caseId: 103, status: 'passed', duration: 800 },
];

// Report each result
for (const result of results) {
// Add test case to run
const testRunCase = await client.findOrAddTestCaseToRun({
testRunId: testRun.id,
repositoryCaseId: result.caseId,
});

// Create the result
await client.createTestResult({
testRunId: testRun.id,
testRunCaseId: testRunCase.id,
statusId: result.status === 'passed' ? passedStatus : failedStatus,
elapsed: Math.round(result.duration / 1000), // Playwright reports ms; TestPlanIt stores seconds
notes: result.error ? String(result.error) : undefined,
});
}

// Complete the test run
await client.completeTestRun(testRun.id);

console.log(`Test run completed: ${client.getBaseUrl()}/test-runs/${testRun.id}`);
} catch (error) {
if (error instanceof TestPlanItError) {
console.error(`TestPlanIt error: ${error.message} (${error.statusCode})`);
} else {
throw error;
}
}
}

reportTestResults();