TINY API TEST

Test API flows,
not just endpoints.

tat chains dependent HTTP requests โ€” authenticate, capture values, reuse them in the next call โ€” so you verify whole integration flows in JSON or YAML, from the CLI.

Get Started Try in StackBlitz
@latest · MIT License · Node ≥ 18
Why tat?
Everything you need to test your APIs, nothing you don't.
๐Ÿ“

JSON or YAML

Write tests in .tat.json or .tat.yml files. Human-readable, version-controllable, no code required.

โšก

Powerful Assertions

Assert status codes, fields, strings, ranges, null checks, and response times with expressive operators.

๐Ÿ”—

Capture & Chain

Extract values from responses and use them in subsequent requests. For isolated test runs, pass missing values explicitly with --variables.

๐Ÿท๏ธ

Tags & Filtering

Tag suites, filter by tag, or run specific suites and individual tests. Run only what matters.

๐Ÿ”

Auth & Setup Hooks

Static tokens, OAuth via setup scripts, or --env-cmd for CI. Interactive 2FA prompts supported.

๐Ÿ“Š

Multiple Outputs

Console, JSON, or JUnit XML. Integrate with GitHub Actions, Jenkins, and any CI system.

๐Ÿค–

Agent-Friendly

Use the tat-create skill to turn API specs, OpenAPI docs, and endpoint notes into runnable tat files faster.

๐Ÿงช

VS Code Test Runner

Run tat suites from Test Explorer, use CodeLens in the editor, and get prompted for capture-derived values when running a single test.

Flow-based API testing
In real integrations, nothing happens one endpoint at a time — everything is connected.

Most tools push you to test endpoints in isolation. But real integration work is a sequence of dependent steps — data from one response flows into the next request, and the whole chain represents one business scenario.

tat is designed around that shape. Define a flow as an ordered list of tests, capture values from each response, and feed them straight into the next call. One file, one command, the whole journey verified.

Verify the flow first, then build with confidence.

  1. 1Authenticate — get an access token via setup or login request
  2. 2Call API A — list workspaces the user can access
  3. 3Capture — extract the workspace id from the response
  4. 4Call API B — fetch related data using {{workspaceId}}
  5. 5Assert — validate status, fields, and the final result
See it in action
A complete test file and its output โ€” that's all there is to it.
tests.tat.json
{ "env": { "baseUrl": "https://api.example.com" }, "suites": [{ "name": "Users", "tags": ["smoke"], "tests": [ { "name": "Create user", "method": "POST", "url": "{{baseUrl}}/users", "body": { "name": "Alice" }, "assert": ["$status == 201", "id is not null"], "capture": { "userId": "id" } }, { "name": "Get user", "method": "GET", "url": "{{baseUrl}}/users/{{userId}}", "assert": ["$status == 200", "name == 'Alice'"] }, { "name": "Delete user", "method": "DELETE", "url": "{{baseUrl}}/users/{{userId}}", "assert": ["$status == 204"] } ] }] }
env: baseUrl: https://api.example.com suites: - name: Users tags: [smoke] tests: - name: Create user method: POST url: "{{baseUrl}}/users" body: name: Alice assert: - "$status == 201" - "id is not null" capture: userId: id - name: Get user method: GET url: "{{baseUrl}}/users/{{userId}}" assert: - "$status == 200" - "name == 'Alice'" - name: Delete user method: DELETE url: "{{baseUrl}}/users/{{userId}}" assert: - "$status == 204"
Terminal output
$ tat run tests.tat.json Users [smoke] โœ” Create user (142ms) Captured: userId: 42 โœ” Get user (89ms) โœ” Delete user (67ms) 3 passed ยท 0 failed ยท 298ms
Get started in 3 steps
From zero to running tests in under a minute.
1

Install

npm i -g @nanotiny/tiny-api-test@latest
npx skills add https://github.com/nanotinydev/tat-skills

Install the CLI + the agent skill

2

Ask your AI agent

Use tat-create to generate a tat test file from this API specification. Include login, get user profile, and create order flows. Use YAML format and add assertions for status codes and key response fields. If authentication is required, create a setup hook and reuse the token in the test requests.

Works with Claude Code, Copilot CLI, Gemini CLI, and more

3

Run it

tat run tests.tat.json

That's it. Your agent writes the tests, you run them.

Try tat in your browser — no install

Open the sample API project in StackBlitz, run tat against a live sample API, and see a real flow pass in seconds.

Open in StackBlitz

Test File Structure

A test file has a simple structure: environment variables, optional setup, and an array of test suites.

{
  "$schema": "https://unpkg.com/@nanotiny/tiny-api-test/schema.json",
  "env": { "baseUrl": "https://api.example.com" },
  "setup": "node scripts/get-token.js",
  "timeout": 5000,
  "suites": [ ... ]
}
env:
  baseUrl: https://api.example.com

setup: node scripts/get-token.js
timeout: 5000

suites:
  - ...

Top-Level Fields

FieldTypeRequiredDescription
$schemastringNoJSON schema URL for editor autocomplete
envobject | stringNoInline env variables or path to a JSON file
setupstringNoShell command run before tests; JSON stdout merged into env
timeoutnumberNoDefault request timeout in ms for all tests
suitesSuite[]YesList of test suites

Suite Fields

FieldTypeRequiredDescription
namestringYesSuite name
tagsstring[]NoTags used to filter suites via --tag
skipbooleanNoSkip all tests in this suite
testsTest[]YesList of tests

Test Fields

FieldTypeRequiredDescription
namestringYesTest name
methodstringYesHTTP method: GET, POST, PUT, PATCH, DELETE, HEAD
urlstringYesRequest URL, supports {{variable}} interpolation
headersobjectNoHTTP headers as key-value pairs
bodyanyNoRequest body, objects auto-serialized as JSON
assertstring[]NoAssertion expressions evaluated against the response
captureobjectNoExtract response values for use in later tests
responsetrue | objectNoInclude response status, body, and/or headers in output
skipbooleanNoSkip this test without failing
timeoutnumberNoPer-test timeout in ms (highest priority)

Assertions

Each assertion is evaluated against the response context. Body fields from object responses are spread at the root for easy access.

Response Context

// Response: { status: 200, body: { id: 1, name: "Alice" } }
// Available in assertions:
$status   = 200            // HTTP status code
$headers  = { ... }         // Response headers
$body     = { id: 1, ... }  // Full response body
$duration = 142            // Request time in ms
id        = 1              // Body fields spread at root
name      = "Alice"        // Body fields spread at root

Operators

OperatorExampleDescription
== !=name == 'Alice'Equals / not equals
> < >= <=age >= 18Numeric comparison
is nulldeletedAt is nullValue is null or missing
is not nullid is not nullValue exists
containsbio contains 'engineer'String contains substring
startswithname startswith 'Ali'String starts with
endswithemail endswith '.com'String ends with
likename like 'Ali*'Wildcard match (* = any)
in / not inrole in 'admin,user'Value in comma-separated list
betweenage between 18 and 65Value in range (inclusive)
&& / ||status == 'ok' && code == 0Logical AND / OR

Duration Assertions

Use $duration to assert response time:

"assert": ["$status == 200", "$duration < 500"]
assert:
  - "$status == 200"
  - "$duration < 500"

Failed Assertion Output

When an assertion fails, tat prints the referenced actual values on the same line so you can see what the API returned without rerunning in a debugger.

$ tat run tests.tat.yml

Users
  โœ˜ Get user (86ms)
    โœ˜ $status == 200 (actual: $status=500)
    โœ˜ name == "Alice" (actual: name="Bob")

Response Output

Use response on a test when you want to print response details in the test output. response: true keeps the compact shortcut for body and headers; use the object form to request status, body, and headers individually.

  • response: true includes body and headers.
  • response: { status: true } includes only the response status code.
  • response: { body: true } includes only the response body.
  • response: { headers: true } includes only the response headers.
{
  "name": "Inspect status",
  "method": "GET",
  "url": "{{baseUrl}}/users/1",
  "response": { "status": true },
  "assert": ["$status == 200"]
}
- name: Inspect status
  method: GET
  url: "{{baseUrl}}/users/1"
  response:
    status: true
  assert:
    - "$status == 200"
Users
  โœ” Inspect status (72ms)
    Response status: 200

Capture & Variables

Use capture to extract values from a response and reuse them in later tests with {{variable}} interpolation.

Basic Capture

// Create a user and capture the ID
{
  "name": "Create user",
  "method": "POST",
  "url": "{{baseUrl}}/users",
  "body": { "name": "Alice" },
  "assert": ["$status == 201"],
  "capture": { "userId": "id" }
}

// Use the captured ID in a later test
{
  "name": "Get user",
  "method": "GET",
  "url": "{{baseUrl}}/users/{{userId}}",
  "assert": ["$status == 200", "name == 'Alice'"]
}
# Create a user and capture the ID
- name: Create user
  method: POST
  url: "{{baseUrl}}/users"
  body:
    name: Alice
  assert:
    - "$status == 201"
  capture:
    userId: id

# Use the captured ID in a later test
- name: Get user
  method: GET
  url: "{{baseUrl}}/users/{{userId}}"
  assert:
    - "$status == 200"
    - "name == 'Alice'"

Advanced Capture Expressions

Capture uses @nanotiny/json-expression query syntax โ€” filter arrays, use indexes, traverse nested paths:

"capture": {
  "companyName": "companies.name | id == 'c2'",       // filter by field
  "thirdId":     "companies.id | $index == 2",         // by array index
  "firstType":   "types.type.name",                    // nested path
  "hpStat":      "stats.base_stat | $index == 0"       // indexed nested
}
capture:
  companyName: "companies.name | id == 'c2'"       # filter by field
  thirdId:     "companies.id | $index == 2"         # by array index
  firstType:   types.type.name                    # nested path
  hpStat:      "stats.base_stat | $index == 0"       # indexed nested

Environment Variables

Define variables in the env field (inline or external file). Variables are interpolated in url, headers, and body:

{
  "env": { "baseUrl": "https://api.example.com", "token": "abc123" },
  // or load from file:
  "env": "./env.local.json"
}
env:
  baseUrl: https://api.example.com
  token: abc123

# or load from file:
env: ./env.local.json

Single Test Runs

When you run a single test with --suite and --test, tat keeps that run isolated. Earlier tests are not executed automatically, so capture-derived values must come from env, setup, --env-cmd, or manual --variables input.

$ tat run tests.tat.yml --suite "Workspace flow" --test "Create project" --variables workspaceId=ws-123

CLI Reference

tat run <file>

Run tests from a JSON/YAML file, or from a directory (discovers all .tat.json, .tat.yml, .tat.yaml files recursively).

OptionDescription
--tag <tags>Filter suites by tag (comma-separated, OR logic)
--suite <name>Run a single suite by name
--test <name>Run a single test by name (requires --suite)
--variables <key=value>Supply a manual variable value for the run; repeat to pass more than one
--output <format>console (default), json, or junit
--out <file>Write output to a file
--bailStop on first failure
--env-cmd <cmd>Run command; JSON stdout merged into env
--timeout <ms>Global request timeout in ms
--insecureDisable TLS certificate verification for trusted non-local development endpoints; localhost HTTPS is handled automatically

tat validate <file>

Check syntax, schema validity, and warn about undefined {{variables}} without running tests.

$ tat validate tests.tat.json
tests.tat.json: valid

$ tat validate tests.tat.json
tests.tat.json: valid (1 warning)
  [warn] test "Get user": variable "{{token}}" is not defined

Exit Codes

CodeMeaning
0All tests passed
1One or more tests failed
2Configuration error (bad JSON/YAML, schema error, missing file)

Timeout Priority

Highest to lowest: per-test timeout field → --timeout CLI flag → file-level timeout → no timeout.

Authentication

Static Token

For APIs where you already have a token โ€” put it in your env:

{
  "env": { "token": "eyJhbG..." }
}
env:
  token: eyJhbG...

Setup Hook (OAuth / 2FA)

The setup command runs before tests. Its JSON stdout merges into env. Interactive prompts work because stdin/stderr are inherited:

{
  "setup": "node scripts/get-token.js",
  "suites": [...]
}
setup: node scripts/get-token.js

suites:
  - ...
// scripts/get-token.js โ€” print JSON to stdout
// ... do OAuth flow, prompt for 2FA ...
console.log(JSON.stringify({ token: access_token }));

CI / --env-cmd

Same as setup but from the command line โ€” useful when auth differs per environment:

$ tat run tests.json --env-cmd "node scripts/ci-token.js"

# Or inline for simple cases:
$ tat run tests.json --env-cmd \
    "node -e \"console.log(JSON.stringify({token:process.env.API_TOKEN}))\""

Strategy by Scenario

ScenarioApproach
Local dev, token knownPaste into env.local.json (gitignored)
Local dev, OAuth + 2FAsetup script with interactive prompt
CI, OAuth client credentials--env-cmd with non-interactive script
CI, long-lived API keyInject via environment variable or --env-cmd

CI Integration

Use JUnit output for CI systems. Here's a GitHub Actions example:

name: API Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18
      - run: npm i -g @nanotiny/tiny-api-test@latest
      - run: tat run tests.tat.json --output junit --out results.xml
      - uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: results.xml

VS Code Extension

Get Test Explorer integration, CodeLens, editor support, and prompts for missing capture-derived values during isolated single-test runs:

$ code --install-extension nanotiny.tat-test-runner

Agent Skill (tat-create)

tat is agent-friendly. Install the tat-create skill to let AI coding assistants like Claude Code, Copilot CLI, or Gemini CLI generate .tat.json and .tat.yml files from your API specs, OpenAPI/Swagger docs, endpoint descriptions, or request/response examples:

$ npx skills add https://github.com/nanotinydev/tat-skills

Once installed, prompt your assistant with something like:

# Example prompt for your AI assistant:
Use tat-create to generate a tat test file from this API specification.
Include login, get user profile, and create order flows.
Use YAML format and add assertions for status codes and key response fields.
If authentication is required, create a setup hook and reuse the token.

The skill handles suites, requests, assertions, captures, and authentication flows โ€” turning your API spec into runnable tests in seconds.

Copied!