Sankh collection format, v1
Status: frozen for v1 (format = 1 in sankh.toml). New annotations are
added only through the annotation reference, one at a time.
Folder layout
my-api/
sankh.toml # optional
.env.example
.gitignore # includes .env.local
environments/
dev.env
prod.env
auth/
01-login.sh
users/
01-list.sh
02-create.sh- The collection root is the nearest folder containing
sankh.tomlorenvironments/. Without either, the folder passed to Sankh is the root. - Request files end in
.sh. Hidden entries and the root-levelenvironments/,node_modules/andtarget/folders are ignored. - Folders with no request files are not shown.
sankh.toml
All keys are optional.
name = "petstore" # display name; default: folder name
format = 1 # collection format version
default_env = "dev" # environment used when --env is not given
timeout = 30 # per-request limit in seconds (curl --max-time)
[order] # explicit ordering per folder ("" is the root)
"" = ["auth", "users"]
"users" = ["02-create.sh", "01-list.sh"]Ordering
Entries with a numeric prefix (01-, 2_, 10.) come first, sorted by the
number; the rest follow alphabetically (case-insensitive). Entries listed in
[order] for a folder come before all others, in the listed order.
Request file
#!/usr/bin/env bash
# @name Create user
# @tags smoke
# @capture USER_ID=.data.id
# @expect status 201
# @expect json .data.name == "test"
curl -sS -X POST "$BASE_URL/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"test"}'Header block
- The header is the contiguous run of
#lines at the top of the file. A shebang may come first. A blank line or any non-comment line ends it. - An annotation is a header line of the form
# @<name> <arguments>; the space after#is optional. One annotation per line. - Other comment lines are preserved verbatim.
- Annotations after the header are ignored with a warning.
- Unknown annotations produce a warning. Malformed known annotations are errors reported with the line number; the request does not run.
Body
The body is everything after the header. A file is in form mode when the
body is exactly one plain curl command (line continuations allowed) using
only quoting and $VAR / ${VAR} expansion. Anything else (pipes, ;,
redirection, command substitution, several commands) is raw mode: it is
edited as plain text and still runs, subject to trust.
Interpreter
Files whose shebang names bash (or zsh) run with that shell; all others run
with sh. Files are sourced from the collection root as the working directory.
Annotations (v1)
| Annotation | Repeatable | Meaning |
|---|---|---|
@name <text> |
no | Display name. Default: file name without number prefix and extension (02-create-order.sh → "create order"). |
@description <text> |
no | Notes shown in the UI. No effect on execution. |
@tags <tag>... |
yes (merged) | Labels for --tag. Lowercase letters, digits, -, _. |
@expect status <codes> |
yes (all must pass) | 200, 2xx, or 200|201|204. |
@expect json <jq> <op> <value> |
yes (all evaluated) | Assertion on the JSON body. |
@expect json <jq> exists |
yes | The expression yields a non-null value. |
@capture NAME=<source> |
yes | Store a response value for later requests in the run. |
@expect status
Without any @expect status, a request passes on any 2xx status. Write an
explicit expectation for tests that expect errors, e.g. @expect status 404.
@expect json
| Operator | Meaning |
|---|---|
==, != |
JSON value equality (numbers compare numerically) |
>, >=, <, <= |
Numeric comparison; the value must be a number |
exists |
Non-null result |
matches "<regex>" |
String matches the regex |
contains <value> |
String contains substring, or array contains element |
- The value is a JSON literal:
"text",42,true,null, arrays or objects. Bare words are an error (did you mean "word"?). - The line is split from the right: the value is the trailing JSON literal and
the operator is the token just before it, so the jq expression may itself
contain operators and pipes (
.items | length > 0). - Strings may embed variables:
@expect json .owner == "$USER_ID". - The expression must produce exactly one value (except for
exists). - A non-JSON response fails the assertion with a clear message.
@capture
| Source | Meaning |
|---|---|
<jq expression> (starts with .) |
Evaluated against the JSON body |
header <Name> |
Response header (case-insensitive) |
status |
HTTP status code |
NAMEmatches[A-Z_][A-Z0-9_]*.null, no value or several values fail the request.- Strings are stored unquoted; numbers and booleans as text; objects and arrays as compact JSON.
- Captures run only when every assertion passed, live only for the current run, and are never written to disk. Later captures override earlier ones.
Environments
Lowest to highest priority:
environments/<name>.env.env.local(gitignored)- The process environment (CI secrets)
- Values captured earlier in the run
Env files use dotenv syntax. Missing variables referenced by a request produce a warning.
Variables and quoting
$VAR and ${VAR} expand at run time. In the form view and the parsed model,
a literal dollar sign is written \$ (for example, a $ inside single quotes
in the file).
Later annotations
@expect header, @expect time, @expect body, @require, @secret,
@timeout, @retry, @skip, @delay and @depends are proposed for v1.x. They are recognised and ignored with a
warning by v1 binaries, so collections stay forward compatible.