TINY API TEST

API testing in JSON/YAML.
Run in one command.

Fast, file-based API tests for smoke checks, simple workflows, and CI. Write them in JSON or YAML and run them with one command.

Get Started
@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. Build full API flows with ease.

๐Ÿท๏ธ

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 keep API tests close to the files you are editing.

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.

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 body/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"

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

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)
--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

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, and editor support:

$ 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!