TRAM (Test Runner for Assertion Manifests) is a framework for creating executable behavioral models of HTTP APIs. It validates those models and gathers evidence from running APIs to verify that observable behavior matches the intended design.
|
Rather than focusing on implementation details, TRAM focuses on what can be observed at the API surface: the resources, actions, workflows, and rules that define how a system behaves. Assertions are organized into progressively richer layers, moving from endpoint availability and response structure to business behavior, workflows, and governance constraints. This allows teams to express operational intent as a durable behavioral model that remains valuable even as implementations evolve. |
|
TRAM combines:
Manifest version 0.2 introduces response capture.
Capture allows a test to extract values from an HTTP response (such as resource identifiers or hypermedia links) and reuse those values in subsequent requests. This makes it possible to write behavioral tests for APIs that generate identifiers dynamically or expose navigational affordances.
Earlier (0.1) manifests remain supported and continue to execute without modification.
A TRAM run can produce several complementary artifacts.
| Artifact | Purpose |
|---|---|
| Manifest | Defines the expected API behavior. |
| HTTP Transcript | Records the observed HTTP request/response conversation. |
| Report | Evaluates the observed behavior against the manifest. |
| Evidence | The complete collection of artifacts from a test run. |
The transcript and report serve different purposes.
The HTTP Transcript records what happened during execution. The Report evaluates whether the observed behavior satisfied the behavioral expectations expressed in the manifest.
{
"name": "Smallest TRAM manifest",
"config": {
"baseUrl": "http://localhost:3000"
},
"tests": [
{
"name": "GET /tasks returns 200",
"method": "GET",
"path": "/tasks",
"expect": {
"status": 200
}
}
]
}
This is the smallest useful complete TRAM manifest:
The capture property records values observed in an HTTP response and makes them available to later requests.
{
"id": "task-create",
"method": "POST",
"path": "/tasks",
"bodyType": "json",
"body": "$data.task.capture.valid",
"expect": {
"status": 201
},
"capture": {
"createdTaskId": "body.id"
}
}
Later tests can reference the captured value:
{
"id": "task-get",
"method": "GET",
"path": "/tasks/${capture.createdTaskId}",
"expect": {
"status": 200
}
}
Capture works with response bodies, headers, and other observable response values. See the Manifest Specification for the complete syntax.
TRAM explores a narrow problem:
How do we make behavioral expectations directly visible, portable, executable, and reviewable?
The core artifact is the manifest:
The manifest defines:
Assertions become directly inspectable operational statements.
Simple behavioral assertion:
{
"path": "$.status",
"equals": "active"
}
Meaning:
The resource status must be "active".
Optional property assertion for evolving representations:
{
"path": "$",
"each": {
"property": "description",
"optional": true,
"type": "string"
}
}
Meaning:
"description" may be absent.
If present, it must be a string.
Hypermedia affordance assertion:
{
"path": "$._links",
"eachProperty": {
"hasProperties": ["href", "method"]
}
}
Meaning:
Every affordance must define both a target URL and an HTTP method.
Collection behavioral assertion:
{
"path": "$",
"each": {
"property": "status",
"oneOf": ["active", "pending", "completed"]
}
}
Meaning:
Every returned resource must have a recognized workflow state.
Nested affordance traversal assertion:
{
"path": "$",
"each": {
"path": "$._links",
"eachProperty": {
"hasProperties": ["href", "method"]
}
}
}
Meaning:
Every returned resource must expose affordances
that define both a target URL and an HTTP method.
TRAM supports partial and evolving representations while preserving explicit behavioral validation.
TRAM organizes behavioral testing into six progressive layers.
| Level | Focus | Question |
|---|---|---|
| 0 | Surface | Can the API be reached? |
| 1 | Shape | Do resources and affordances appear correctly? |
| 2 | Safe behavior | Do navigation, lookup, filtering, and query interactions behave correctly? |
| 3 | Unsafe behavior | Do isolated state-changing actions behave correctly? |
| 4 | Workflow | Can meaningful operational narratives be completed successfully? |
| 5 | Governance | Are policies, constraints, and semantic rules enforced correctly? |
The layers are additive rather than replacement-oriented. Each layer narrows debugging scope while preserving readable behavioral intent.
TRAM is designed around several principles:
The long-term direction is an AI Coach that helps users learn behavioral API testing while collaboratively constructing executable manifests.
Current implementation includes:
api-tests.json)each)eachProperty)type)optional)range)${data.*})$data.*).
├── README.md
├── package.json
├── api-tests.json
├── bin/
│ └── tram
├── lib/
│ └── assertions.js
├── docs/
└── sample-api/
tram <manifest-file> [options]
Options:
-v, --verbose Print passing assertion details
-r, --report <file> Write behavioral report (JSON)
-t, --transcript <file> Write HTTP transcript
--validate Validate the manifest without making HTTP requests
-h, --help Show help
Clone the repository:
git clone https://github.com/mamund/2026-05-tram.git
cd 2026-05-tram
macOS/Linux:
chmod +x bin/tram
npm link
Windows:
npm link
Then run:
tram api-tests.json
Validate the manifest without contacting the sample API:
tram api-tests.json --validate
This performs structural validation of the manifest and exits without sending any HTTP requests.
Tests are defined declaratively in a manifest:
{
"name": "Create task",
"method": "POST",
"path": "/tasks/${data.stableId}",
"body": "$data.task.valid",
"expect": {
"status": 201,
"body": [
{
"path": "$.status",
"equals": "active"
}
]
}
}
The manifest acts as both:
The data section stores reusable request and runtime values.
Example:
{
"data": {
"stableId": "${randomId}"
}
}
The generated value remains stable throughout the current test run.
Later requests can reference the same value:
{
"path": "/tasks/${data.stableId}"
}
TRAM also supports response capture.
Values observed in one response may be reused later in the same test run.
Example:
"capture": {
"taskId": "body.id"
}
Later requests can reference the captured value:
"path": "/tasks/${capture.taskId}"
This enables coordinated multi-step behavioral flows without introducing custom scripting.
Use:
"$data.someObject"
when injecting structured runtime objects.
Use:
"${data.someValue}"
when interpolating values inside strings.
Examples:
Correct object injection:
"body": "$data.createTask"
Correct string interpolation:
"path": "/tasks/${data.knownTaskId}"
Captured response values use:
"${capture.taskId}"
These values are populated during test execution from earlier HTTP responses.
The repository includes a dedicated capture example that demonstrates creating a resource, capturing values from the response, and using those values in subsequent requests.
The repository includes a dedicated capture example:
examples/api-tests-capture.json
The example demonstrates:
The assertion library currently supports:
exists
equals
contains
oneOf
type
range
isArray
hasProperties
length
minLength (deprecated)
each
eachProperty
Native type assertions support:
string
number
boolean
array
object
null
Example native type assertion:
{
"path": "$.priority",
"type": "number"
}
Example optional property assertion:
{
"path": "$",
"each": {
"property": "description",
"optional": true,
"type": "string"
}
}
This assertion means:
"description" may be absent
if present, it must still validate as a string
TRAM distinguishes between arrays and object maps.
Use:
each for arrayseachProperty for object mapsExamples:
[
{...},
{...}
]
=> each
{
"self": {...},
"edit": {...}
}
=> eachProperty
TRAM also distinguishes between:
path for structural traversalproperty for scalar leaf checksExample structural traversal:
{
"path": "$",
"each": {
"path": "$._links",
"eachProperty": {
"hasProperties": ["href", "method"]
}
}
}
Example scalar leaf assertion:
{
"path": "$",
"each": {
"property": "status",
"equals": "active"
}
}
The assertion model supports:
while remaining declarative and inspectable.
TRAM intentionally limits type assertions to native value categories.
The following are currently out of scope:
uuid
email
uri
date-time
schema validation
TRAM manifests can model operational workflows rather than isolated endpoint checks.
TRAM models workflows through declarative sequencing rather than embedded scripting.
A workflow manifest may:
This allows manifests to function as executable operational narratives.
Example workflow sequence:
create
read after create
edit
update status
assign user
set due date
read final accumulated state
Header assertions use:
{
"name": "content-type",
"contains": "application/json"
}
Do not use path for header assertions.
TRAM supports multiple request body encodings:
json
form
text
Example:
{
"method": "PUT",
"path": "/tasks/task-1/status",
"bodyType": "form",
"body": "$data.task.updateStatus"
}
Start the sample API:
node sample-api/index.js
Run the test suite:
tram api-tests.json
Verbose mode:
tram api-tests.json --verbose
Generate an HTTP transcript:
tram api-tests.json --transcript transcript.http
Generate a machine-readable report:
tram api-tests.json --report report.json
Generate both artifacts:
tram api-tests.json \
--report report.json \
--transcript transcript.http
Authoritative executable manifest model.
Defines:
Architectural discussion of:
TRAM validates manifests before executing HTTP requests. Validation may also be invoked directly from the command line using the --validate option.
Validation currently includes:
Example:
tram api-tests.json --validate
If the manifest is valid, TRAM reports success and exits without executing any requests. If validation fails, TRAM reports the validation errors and exits with a non-zero status.
Invalid manifests fail before execution begins.
TRAM reports multiple manifest validation problems in a single pass when possible.
TRAM distinguishes between:
TRAM can produce several complementary views of a test run.
The transcript records the observed HTTP conversation.
The report evaluates that conversation against the behavioral expectations expressed in the manifest.
Manifest validation is treated as a first-class operation, allowing behavioral models to be reviewed independently of execution.
TRAM is intentionally conservative.
Current releases avoid:
The current emphasis is:
The AI Coaching direction includes:
The eventual AI Coach layer will:
server.js and/or API Story documentsThe goal is not automatic test generation alone.
The goal is helping users understand behavioral API testing while collaboratively constructing executable manifests.
Typical TRAM progression:
Level 0 — endpoint availability
Level 1 — representation structure
Level 2 — lookup and filtering behavior
Level 3 — isolated mutation behavior
Level 4 — workflow continuity
Level 5 — governance and constraints
TRAM draws inspiration from:
Early experimental project.
Interfaces and manifest formats will evolve during v0.x development.
Project repository:
https://github.com/mamund/tram
110 commits
JavaScript
100.0%
TRAM (Test Runner for Assertion Manifests) is a framework for creating executable behavioral models of HTTP APIs. It validates those models and gathers evidence from running APIs to verify that observable behavior matches the intended design.
|
Rather than focusing on implementation details, TRAM focuses on what can be observed at the API surface: the resources, actions, workflows, and rules that define how a system behaves. Assertions are organized into progressively richer layers, moving from endpoint availability and response structure to business behavior, workflows, and governance constraints. This allows teams to express operational intent as a durable behavioral model that remains valuable even as implementations evolve. |
|
TRAM combines:
Manifest version 0.2 introduces response capture.
Capture allows a test to extract values from an HTTP response (such as resource identifiers or hypermedia links) and reuse those values in subsequent requests. This makes it possible to write behavioral tests for APIs that generate identifiers dynamically or expose navigational affordances.
Earlier (0.1) manifests remain supported and continue to execute without modification.
A TRAM run can produce several complementary artifacts.
| Artifact | Purpose |
|---|---|
| Manifest | Defines the expected API behavior. |
| HTTP Transcript | Records the observed HTTP request/response conversation. |
| Report | Evaluates the observed behavior against the manifest. |
| Evidence | The complete collection of artifacts from a test run. |
The transcript and report serve different purposes.
The HTTP Transcript records what happened during execution. The Report evaluates whether the observed behavior satisfied the behavioral expectations expressed in the manifest.
{
"name": "Smallest TRAM manifest",
"config": {
"baseUrl": "http://localhost:3000"
},
"tests": [
{
"name": "GET /tasks returns 200",
"method": "GET",
"path": "/tasks",
"expect": {
"status": 200
}
}
]
}
This is the smallest useful complete TRAM manifest:
The capture property records values observed in an HTTP response and makes them available to later requests.
{
"id": "task-create",
"method": "POST",
"path": "/tasks",
"bodyType": "json",
"body": "$data.task.capture.valid",
"expect": {
"status": 201
},
"capture": {
"createdTaskId": "body.id"
}
}
Later tests can reference the captured value:
{
"id": "task-get",
"method": "GET",
"path": "/tasks/${capture.createdTaskId}",
"expect": {
"status": 200
}
}
Capture works with response bodies, headers, and other observable response values. See the Manifest Specification for the complete syntax.
TRAM explores a narrow problem:
How do we make behavioral expectations directly visible, portable, executable, and reviewable?
The core artifact is the manifest:
The manifest defines:
Assertions become directly inspectable operational statements.
Simple behavioral assertion:
{
"path": "$.status",
"equals": "active"
}
Meaning:
The resource status must be "active".
Optional property assertion for evolving representations:
{
"path": "$",
"each": {
"property": "description",
"optional": true,
"type": "string"
}
}
Meaning:
"description" may be absent.
If present, it must be a string.
Hypermedia affordance assertion:
{
"path": "$._links",
"eachProperty": {
"hasProperties": ["href", "method"]
}
}
Meaning:
Every affordance must define both a target URL and an HTTP method.
Collection behavioral assertion:
{
"path": "$",
"each": {
"property": "status",
"oneOf": ["active", "pending", "completed"]
}
}
Meaning:
Every returned resource must have a recognized workflow state.
Nested affordance traversal assertion:
{
"path": "$",
"each": {
"path": "$._links",
"eachProperty": {
"hasProperties": ["href", "method"]
}
}
}
Meaning:
Every returned resource must expose affordances
that define both a target URL and an HTTP method.
TRAM supports partial and evolving representations while preserving explicit behavioral validation.
TRAM organizes behavioral testing into six progressive layers.
| Level | Focus | Question |
|---|---|---|
| 0 | Surface | Can the API be reached? |
| 1 | Shape | Do resources and affordances appear correctly? |
| 2 | Safe behavior | Do navigation, lookup, filtering, and query interactions behave correctly? |
| 3 | Unsafe behavior | Do isolated state-changing actions behave correctly? |
| 4 | Workflow | Can meaningful operational narratives be completed successfully? |
| 5 | Governance | Are policies, constraints, and semantic rules enforced correctly? |
The layers are additive rather than replacement-oriented. Each layer narrows debugging scope while preserving readable behavioral intent.
TRAM is designed around several principles:
The long-term direction is an AI Coach that helps users learn behavioral API testing while collaboratively constructing executable manifests.
Current implementation includes:
api-tests.json)each)eachProperty)type)optional)range)${data.*})$data.*).
├── README.md
├── package.json
├── api-tests.json
├── bin/
│ └── tram
├── lib/
│ └── assertions.js
├── docs/
└── sample-api/
tram <manifest-file> [options]
Options:
-v, --verbose Print passing assertion details
-r, --report <file> Write behavioral report (JSON)
-t, --transcript <file> Write HTTP transcript
--validate Validate the manifest without making HTTP requests
-h, --help Show help
Clone the repository:
git clone https://github.com/mamund/2026-05-tram.git
cd 2026-05-tram
macOS/Linux:
chmod +x bin/tram
npm link
Windows:
npm link
Then run:
tram api-tests.json
Validate the manifest without contacting the sample API:
tram api-tests.json --validate
This performs structural validation of the manifest and exits without sending any HTTP requests.
Tests are defined declaratively in a manifest:
{
"name": "Create task",
"method": "POST",
"path": "/tasks/${data.stableId}",
"body": "$data.task.valid",
"expect": {
"status": 201,
"body": [
{
"path": "$.status",
"equals": "active"
}
]
}
}
The manifest acts as both:
The data section stores reusable request and runtime values.
Example:
{
"data": {
"stableId": "${randomId}"
}
}
The generated value remains stable throughout the current test run.
Later requests can reference the same value:
{
"path": "/tasks/${data.stableId}"
}
TRAM also supports response capture.
Values observed in one response may be reused later in the same test run.
Example:
"capture": {
"taskId": "body.id"
}
Later requests can reference the captured value:
"path": "/tasks/${capture.taskId}"
This enables coordinated multi-step behavioral flows without introducing custom scripting.
Use:
"$data.someObject"
when injecting structured runtime objects.
Use:
"${data.someValue}"
when interpolating values inside strings.
Examples:
Correct object injection:
"body": "$data.createTask"
Correct string interpolation:
"path": "/tasks/${data.knownTaskId}"
Captured response values use:
"${capture.taskId}"
These values are populated during test execution from earlier HTTP responses.
The repository includes a dedicated capture example that demonstrates creating a resource, capturing values from the response, and using those values in subsequent requests.
The repository includes a dedicated capture example:
examples/api-tests-capture.json
The example demonstrates:
The assertion library currently supports:
exists
equals
contains
oneOf
type
range
isArray
hasProperties
length
minLength (deprecated)
each
eachProperty
Native type assertions support:
string
number
boolean
array
object
null
Example native type assertion:
{
"path": "$.priority",
"type": "number"
}
Example optional property assertion:
{
"path": "$",
"each": {
"property": "description",
"optional": true,
"type": "string"
}
}
This assertion means:
"description" may be absent
if present, it must still validate as a string
TRAM distinguishes between arrays and object maps.
Use:
each for arrayseachProperty for object mapsExamples:
[
{...},
{...}
]
=> each
{
"self": {...},
"edit": {...}
}
=> eachProperty
TRAM also distinguishes between:
path for structural traversalproperty for scalar leaf checksExample structural traversal:
{
"path": "$",
"each": {
"path": "$._links",
"eachProperty": {
"hasProperties": ["href", "method"]
}
}
}
Example scalar leaf assertion:
{
"path": "$",
"each": {
"property": "status",
"equals": "active"
}
}
The assertion model supports:
while remaining declarative and inspectable.
TRAM intentionally limits type assertions to native value categories.
The following are currently out of scope:
uuid
email
uri
date-time
schema validation
TRAM manifests can model operational workflows rather than isolated endpoint checks.
TRAM models workflows through declarative sequencing rather than embedded scripting.
A workflow manifest may:
This allows manifests to function as executable operational narratives.
Example workflow sequence:
create
read after create
edit
update status
assign user
set due date
read final accumulated state
Header assertions use:
{
"name": "content-type",
"contains": "application/json"
}
Do not use path for header assertions.
TRAM supports multiple request body encodings:
json
form
text
Example:
{
"method": "PUT",
"path": "/tasks/task-1/status",
"bodyType": "form",
"body": "$data.task.updateStatus"
}
Start the sample API:
node sample-api/index.js
Run the test suite:
tram api-tests.json
Verbose mode:
tram api-tests.json --verbose
Generate an HTTP transcript:
tram api-tests.json --transcript transcript.http
Generate a machine-readable report:
tram api-tests.json --report report.json
Generate both artifacts:
tram api-tests.json \
--report report.json \
--transcript transcript.http
Authoritative executable manifest model.
Defines:
Architectural discussion of:
TRAM validates manifests before executing HTTP requests. Validation may also be invoked directly from the command line using the --validate option.
Validation currently includes:
Example:
tram api-tests.json --validate
If the manifest is valid, TRAM reports success and exits without executing any requests. If validation fails, TRAM reports the validation errors and exits with a non-zero status.
Invalid manifests fail before execution begins.
TRAM reports multiple manifest validation problems in a single pass when possible.
TRAM distinguishes between:
TRAM can produce several complementary views of a test run.
The transcript records the observed HTTP conversation.
The report evaluates that conversation against the behavioral expectations expressed in the manifest.
Manifest validation is treated as a first-class operation, allowing behavioral models to be reviewed independently of execution.
TRAM is intentionally conservative.
Current releases avoid:
The current emphasis is:
The AI Coaching direction includes:
The eventual AI Coach layer will:
server.js and/or API Story documentsThe goal is not automatic test generation alone.
The goal is helping users understand behavioral API testing while collaboratively constructing executable manifests.
Typical TRAM progression:
Level 0 — endpoint availability
Level 1 — representation structure
Level 2 — lookup and filtering behavior
Level 3 — isolated mutation behavior
Level 4 — workflow continuity
Level 5 — governance and constraints
TRAM draws inspiration from:
Early experimental project.
Interfaces and manifest formats will evolve during v0.x development.
Project repository:
https://github.com/mamund/tram
110 commits
JavaScript
100.0%