Skip to content

.http files

Every request is a separate file with the .http extension. The layout follows plain HTTP:

api/users/create.http
# Create a user. Comments start with # or //.
POST {{base}}/users
Authorization: Bearer {{token}}
Content-Type: application/json
{
"name": "Viktor",
"role": "admin"
}
> save user_id = body.id
> assert status == 201
  1. Comments — lines starting with # or //, before the request line or between headers.
  2. Request line — METHOD URL. The method may be omitted (then it is GET); a trailing HTTP/1.1 is accepted and ignored. Spaces inside the URL must be encoded.
  3. Headers — Name: value, one per line, until the first empty line.
  4. Body — everything after the empty line. Empty lines inside the body are kept.
  5. Directives — lines starting with > at the very end of the file: > save and > assert. See Chaining requests and assertions.

{{name}} placeholders work in the URL, header names and values, and the body — see Variables and environments.

  • If the body starts with { or [ and there is no Content-Type header, Outry adds Content-Type: application/json.
  • After substitution the URL must be absolute (http:// or https://). A relative URL almost always means a missing {{base}}.
  • Windows line endings (CRLF) are fine.
  • Every request has a 30 second timeout (outry run --timeout changes it) and the User-Agent: outry/<version> header unless you set your own.

Folders are just folders. A typical layout groups requests by resource:

api/
├── env.toml
├── auth/
│ ├── 1-login.http
│ └── 2-refresh.http
└── users/
├── create.http
├── get.http
└── list.http

outry run api runs files in alphabetical order of their paths, so numeric prefixes are a simple way to order a chain. Hidden folders and node_modules, target, vendor, dist are skipped.

Terminal window
outry convert api/ --dry-run # print the result
outry convert api/ # write a .outry next to every .http
outry convert api/ --rm # … and delete the .http files
api/auth/login.http
# Login
POST {{base}}/auth/login
X-Request-Id: {{$uuid}}
{"email": "{{email}}", "password": "{{password}}", "remember": true}
> save token = body.access_token
> assert status == 200
> assert body.user.id exists
api/auth/login.outry
Login: POST /auth/login {
headers { X-Request-Id: uuid() }
body { email, password, remember: true }
expect {
status == 200
body.user.id != null
}
save token = body.access_token
}
  • The first comment line becomes the request’s name (Login: POST …) if it is short (up to four words); otherwise the name is made from the file name and the comment becomes the description.
  • {{base}}/… becomes a path, {{x}} in it a parameter {x}; other URLs stay as they are.
  • A JSON body stays JSON: "{{x}}" becomes the variable x, {{x}} outside quotes becomes number(x). Other bodies become strings.
  • > assert turns into expect; exists into != null, contains into .contains(). .outry compares without type coercion, so a header compared with a number is wrapped in number().
  • {{$uuid}}, {{$timestamp}} and {{$randomInt 1 10}} become uuid(), now() and randomInt(1, 9) — the upper bound is inclusive in .outry.

An existing .outry file is never overwritten. The full mapping is in the format reference.