Skip to content

Calling requests and checks

api/users/create.outry
CreateUser: POST /users {
body { name: "Viktor" }
expect {
status == 201
body.name == "Viktor"
body.id matches /^u_\d+$/
headers.content-type.contains("json")
duration < 500
body matches { id: string, name: string, tags?: [string] }
}
}

One check per line; each must be true. All checks run, even after a failure, and a failed one shows why:

✗ api/users/create.outry CreateUser POST http://localhost:8080/users 201 Created 9ms 211B
✓ status == 201
✗ body.name == "Viktor" — body.name is "viktor"
✗ body matches { id: string, name: string, tags?: [string] } — body.id: expected string, got 42

Comparisons don’t coerce types ("1" != 1), a missing field is null instead of an error, and matches takes a regular expression, an inline shape, a named shape or a JSON Schema file. All operators and methods: Expressions.

A request is a function: call it by name, and the result is its response.

api/auth/login.outry
Login: POST /auth/login {
params {
email: "dev@example.com"
password
}
body { email, password }
expect { status == 200 }
}
api/users/me.outry
Me: GET /me {
headers { Authorization: "Bearer ${Login().body.token}" }
expect { status == 200 }
}
Terminal window
outry run api/users/me.outry # logs in first, by itself
✓ api/users/me.outry Me GET http://localhost:8080/me 200 OK 8ms 96B
↳ Login 200 31ms
✓ status == 200
  • No run order to maintain. Me works alone — it calls Login when it needs a token.

  • Sent once per run. Ten requests calling Login() log in once; the rest reuse the response (↳ Login cached). fresh Login() always sends a new one. cache: 30m on Login keeps its response between runs.

  • Arguments fill the callee’s params and {path} parameters: GetUser(id: 7), Login(email: "admin@example.com").

  • Failures propagate. If Login fails its expect, Me fails too, with the chain:

    ✗ api/users/me.outry Me
    Me → Login: status == 200 — status is 401
  • Cookies from Set-Cookie are kept for the whole run, like in a browser.

Request names are unique in the project; when two folders have a Create, call users.Create(). outry check reports unknown names, wrong arguments and call cycles.

api/flows/checkout.outry
// Checkout
flow Checkout {
user = CreateUser(name: "Bob")
order = CreateOrder(customer: user.body.id)
Pay(order: order.body.id)
expect { GetOrder(id: order.body.id).body.status == "paid" }
}

Steps run top to bottom and the flow stops at the first failure. In the app the flow shows a trace of every request it called. Run it by name: outry run Checkout.

save order_id = body.id

A saved value is a variable for later requests in the run and is remembered between runs, per project and environment — so after one outry run CreateOrder, GetOrder alone still knows order_id. Values are stored outside the repository; see Saved values. Prefer calls for things like tokens: they don’t depend on what ran before.

outry run exits with:

  • 0 — every request was sent and every check and save succeeded;
  • 1 — at least one request failed (a check, a save, a network error, a missing variable);
  • 2 — Outry could not start: invalid arguments, unknown environment, broken env.toml.

Use --fail-fast to stop at the first failed request.