File structure
A project is a directory containing reqloom.yaml. Everything else is optional
and glued on with imports:.
Single file
Section titled “Single file”Fine for a handful of operations, and what the importer’s output starts as:
version: 1name: MyAPIdefault_environment: local
environment: baseUrl: http://localhost:3000 api_token: !secret API_TOKEN
actors: user: auth: strategy: bearer token: "{{env.api_token}}"
resources: order: operations: create: method: POST path: /orders actor: user expect_status: 201 extract: order_id: $.data.idMulti-file
Section titled “Multi-file”Past about five resources, split it. This is the layout the sample project and the importer both use:
my-api/├── reqloom.yaml version, name, imports├── environments/│ ├── local.yaml│ └── staging.yaml├── actors/│ ├── admin.yaml│ ├── vendor.yaml│ └── customer.yaml└── resources/ ├── products.yaml ├── orders.yaml └── refunds.yamlversion: 1name: MarketplaceAPIdefault_environment: localimports: - environments/*.yaml - actors/*.yaml - resources/*.yamlSplitting keeps diffs readable and means two people editing different resources don’t conflict.
How imports resolve
Section titled “How imports resolve”Three rules here are easy to get wrong, and all three fail silently.
Directory names are load-bearing
Section titled “Directory names are load-bearing”A file’s relative path prefix decides how it’s parsed. Only three prefixes are recognised:
| Prefix | Parsed as |
|---|---|
actors/ | an actor |
resources/ | a resource |
environments/ | an environment |
A file imported from anywhere else is read and then silently discarded. So this fails quietly:
imports: - shared/common-actors.yaml # loaded, then thrown awayPut it in actors/, or don’t import it.
Globs are narrow
Section titled “Globs are narrow”Only a trailing *.yaml or *.yml expands. Anything else is treated as a
literal filename:
imports: - resources/*.yaml # expands, sorted alphabetically - resources/*.yml # expands - resources/orders.yaml # a single literal file - resources/**/*.yaml # NOT recursive — treated as a literal path - resources/order?.yaml # NOT a pattern — literalThere is no **, no ?, no character classes. Nested subdirectories need an
explicit line each:
imports: - resources/*.yaml - resources/admin/*.yamlimports: also accepts a map, whose values are collapsed into one list — useful
only as documentation:
imports: environments: [environments/*.yaml] actors: [actors/*.yaml] resources: [resources/*.yaml]Two accepted file shapes
Section titled “Two accepted file shapes”Actor, resource, and environment files each accept two forms. The sample project uses the flat form throughout.
Actors and resources
Section titled “Actors and resources”name: orderdescription: Customer ordersoperations: create: method: POST path: /api/v1/ordersorder: description: Customer orders operations: create: method: POST path: /api/v1/ordersBoth give a resource with id order. With neither form, the filename stem is
used — so orders.yaml would give you orders.
Environments
Section titled “Environments”name: localvariables: baseUrl: http://localhost:3000 admin_password: !secret ADMIN_PASSWORDtransport: connect_timeout: 2sbaseUrl: http://localhost:3000admin_password: !secret ADMIN_PASSWORDIn the flat form, name and transport are treated as structure, not variables.
Without name:, the filename stem is the environment name — so staging.yaml
becomes staging, which is what --env staging selects.
Environments are files, not a root block
Section titled “Environments are files, not a root block”There is no environments: (plural) root key. The singular root
environment: map defines exactly one environment — the one named by
default_environment:
default_environment: localenvironment: # becomes the `local` environment baseUrl: http://localhost:3000Additional environments only exist as files under environments/. So a
multi-environment project always uses imports:
imports: - environments/*.yamlreqloom run order.create --env stagingA --env name with no matching environment doesn’t error — the run proceeds with
zero variables. See pitfalls.
Root-level keys
Section titled “Root-level keys”| Key | Type | Default | Notes |
|---|---|---|---|
version | integer | — | Required. Must be 1–3 |
name | string | Unnamed Project | Shown in output and the desktop |
default_environment | string | local | Used when --env is absent |
imports | sequence or map | — | Glob patterns |
environment | map | — | Variables for the default environment |
actors | map | — | Inline actors |
resources | map | — | Inline resources |
auth | map | — | Project default for auth: { type: inherit } |
transport | map | — | Applies to the default environment only |
latency_slo | { p95_ms } | — | Surfaced on the desktop latency chart |
Inline actors: / resources: and imported files coexist — imports are
processed first, so an inline definition with the same id wins.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Schema file size | 8 MiB per file |
| YAML nesting depth in a body | 64 |
| Hook script size | 1 MiB |
| Upload file size | 50 MiB |
Where to put hooks and fixtures
Section titled “Where to put hooks and fixtures”Hook scripts must be relative and inside the project root:
my-api/├── reqloom.yaml├── hooks/│ └── sign-request.js└── fixtures/ └── avatar.png pre_request: ./hooks/sign-request.js body_form: avatar: "@./fixtures/avatar.png"An absolute or escaping hook path is rejected at load with E_SCHEMA_INVALID.
Upload paths are not sandboxed — see
uploads.
Version control
Section titled “Version control”Commit everything except secrets, which never touch the filesystem anyway:
# nothing schema-related to ignore — secrets live in the OS keychainKeep environment files committed with non-sensitive values and !secret tags for
the rest. That way a new contributor clones, adds their keychain entries, and
runs.
- Authoring guide — build a project up from scratch
- Secrets, TLS & timeouts — the
!secrettag - Common pitfalls — silent failures in this area