Fast, file-based API tests for smoke checks, simple workflows, and CI. Write them in JSON or YAML and run them with one command.
Write tests in .tat.json or .tat.yml files. Human-readable, version-controllable, no code required.
Assert status codes, fields, strings, ranges, null checks, and response times with expressive operators.
Extract values from responses and use them in subsequent requests. Build full API flows with ease.
Tag suites, filter by tag, or run specific suites and individual tests. Run only what matters.
Static tokens, OAuth via setup scripts, or --env-cmd for CI. Interactive 2FA prompts supported.
Console, JSON, or JUnit XML. Integrate with GitHub Actions, Jenkins, and any CI system.
Use the tat-create skill to turn API specs, OpenAPI docs, and endpoint notes into runnable tat files faster.
Run tat suites from Test Explorer, use CodeLens in the editor, and keep API tests close to the files you are editing.
Install the CLI + the agent skill
Works with Claude Code, Copilot CLI, Gemini CLI, and more
That's it. Your agent writes the tests, you run them.
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:
- ...
| Field | Type | Required | Description |
|---|---|---|---|
$schema | string | No | JSON schema URL for editor autocomplete |
env | object | string | No | Inline env variables or path to a JSON file |
setup | string | No | Shell command run before tests; JSON stdout merged into env |
timeout | number | No | Default request timeout in ms for all tests |
suites | Suite[] | Yes | List of test suites |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Suite name |
tags | string[] | No | Tags used to filter suites via --tag |
skip | boolean | No | Skip all tests in this suite |
tests | Test[] | Yes | List of tests |
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Test name |
method | string | Yes | HTTP method: GET, POST, PUT, PATCH, DELETE, HEAD |
url | string | Yes | Request URL, supports {{variable}} interpolation |
headers | object | No | HTTP headers as key-value pairs |
body | any | No | Request body, objects auto-serialized as JSON |
assert | string[] | No | Assertion expressions evaluated against the response |
capture | object | No | Extract response values for use in later tests |
response | true | object | No | Include response body/headers in output |
skip | boolean | No | Skip this test without failing |
timeout | number | No | Per-test timeout in ms (highest priority) |
Each assertion is evaluated against the response context. Body fields from object responses are spread at the root for easy access.
// 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
| Operator | Example | Description |
|---|---|---|
== != | name == 'Alice' | Equals / not equals |
> < >= <= | age >= 18 | Numeric comparison |
is null | deletedAt is null | Value is null or missing |
is not null | id is not null | Value exists |
contains | bio contains 'engineer' | String contains substring |
startswith | name startswith 'Ali' | String starts with |
endswith | email endswith '.com' | String ends with |
like | name like 'Ali*' | Wildcard match (* = any) |
in / not in | role in 'admin,user' | Value in comma-separated list |
between | age between 18 and 65 | Value in range (inclusive) |
&& / || | status == 'ok' && code == 0 | Logical AND / OR |
Use $duration to assert response time:
"assert": ["$status == 200", "$duration < 500"]
assert:
- "$status == 200"
- "$duration < 500"
Use capture to extract values from a response and reuse them in later tests with {{variable}} interpolation.
// 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'"
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
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
Run tests from a JSON/YAML file, or from a directory (discovers all .tat.json, .tat.yml, .tat.yaml files recursively).
| Option | Description |
|---|---|
--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 |
--bail | Stop on first failure |
--env-cmd <cmd> | Run command; JSON stdout merged into env |
--timeout <ms> | Global request timeout in ms |
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
| Code | Meaning |
|---|---|
0 | All tests passed |
1 | One or more tests failed |
2 | Configuration error (bad JSON/YAML, schema error, missing file) |
Highest to lowest: per-test timeout field → --timeout CLI flag → file-level timeout → no timeout.
For APIs where you already have a token โ put it in your env:
{
"env": { "token": "eyJhbG..." }
}
env:
token: eyJhbG...
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 }));
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}))\""
| Scenario | Approach |
|---|---|
| Local dev, token known | Paste into env.local.json (gitignored) |
| Local dev, OAuth + 2FA | setup script with interactive prompt |
| CI, OAuth client credentials | --env-cmd with non-interactive script |
| CI, long-lived API key | Inject via environment variable or --env-cmd |
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
Get Test Explorer integration, CodeLens, and editor support:
$ code --install-extension nanotiny.tat-test-runner
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.