Body assertion operators
Keyed by JSONPath in assert.body. Multiple operators on the same path combine with AND.
| Operator | Description | Example |
|---|---|---|
eq |
Exact equality (same as bare literal) | { eq: "Alice" } |
not_eq |
Not equal | { not_eq: "Bob" } |
type |
Assert JSON type: string, number, boolean, array, object, null |
{ type: string } |
contains |
Substring in strings, element in arrays | { contains: "sub" } |
not_contains |
Inverse of contains |
{ not_contains: "x" } |
starts_with |
String prefix | { starts_with: "usr_" } |
ends_with |
String suffix | { ends_with: ".com" } |
matches |
Regex match | { matches: "^[a-z]+$" } |
exists |
Assert field presence or absence | { exists: true } |
not_empty |
Non-empty string, array, or object | { not_empty: true } |
empty / is_empty |
Value is empty (empty strings, arrays, objects, null) | { empty: true } |
length |
Exact length of string or array | { length: 5 } |
length_gt |
Length greater than | { length_gt: 2 } |
length_gte |
Length greater than or equal | { length_gte: 3 } |
length_lte |
Length less than or equal | { length_lte: 10 } |
gt |
Greater than (numeric) | { gt: 0 } |
gte |
Greater than or equal | { gte: 100 } |
lt |
Less than (numeric) | { lt: 1000 } |
lte |
Less than or equal | { lte: 500 } |
is_uuid |
Valid UUID (any version) | { is_uuid: true } |
is_uuid_v4 |
Valid UUID v4 | { is_uuid_v4: true } |
is_uuid_v7 |
Valid UUID v7 | { is_uuid_v7: true } |
is_date |
Valid ISO date or datetime string | { is_date: true } |
is_ipv4 |
Valid IPv4 address | { is_ipv4: true } |
is_ipv6 |
Valid IPv6 address | { is_ipv6: true } |
bytes |
Raw response byte length (for "$" path) |
{ bytes: 1024 } |
sha256 |
SHA-256 hex digest of response body | { sha256: "abc..." } |
md5 |
MD5 hex digest of response body | { md5: "def..." } |
exists_where / contains_object |
Array contains at least one object matching a predicate | { exists_where: { "name": "Alice" } } |
not_exists_where |
Array contains no object matching a predicate | { not_exists_where: { "status": "deleted" } } |
Status assertions
| Form | Example | Description |
|---|---|---|
| Exact integer | status: 200 |
Match a single status code |
| Shorthand range | status: "2xx" |
Match any 2xx, 4xx, or 5xx status |
| List | status: { in: [200, 201, 204] } |
Match any of the listed codes |
| Range | status: { gte: 200, lt: 300 } |
Numeric comparison operators (gte, gt, lte, lt, in) |
Header assertions
| Form | Example | Description |
|---|---|---|
| Exact | content-type: "application/json" |
Match header value exactly |
| Substring | content-type: { contains: "json" } |
Substring match |
| Regex | x-request-id: { matches: "^[a-f0-9-]+$" } |
Regex match |
Duration assertions
| Example | Description |
|---|---|
duration: "< 500ms" |
Step completed in under 500ms |
duration: "<= 1s" |
Step completed in at most 1 second |
duration: "> 200ms" |
Step took more than 200ms |
Redirect assertions
| Field | Example | Description |
|---|---|---|
redirect.url |
redirect.url: "https://final.example.com" |
Expected final URL after redirects |
redirect.count |
redirect.count: 2 |
Expected number of redirects followed |
Plain text / HTML responses
Use the root path "$" to assert on the entire response body as a string:
assert: status: 200 body: "$": "expected plain text response"
Non-JSON responses (plain text, HTML) are preserved as JSON strings in structured output.
Multiple operators (AND)
Operators on the same JSONPath combine with AND:
assert: body: "$.email": type: string contains: "@" not_empty: true matches: "^[a-z@.]+$"
Literal exact match
A bare value (not wrapped in an operator map) is treated as eq:
# These are equivalent: "$.name": "Alice" "$.name": { eq: "Alice" }