Configuration File
This page is the canonical reference for Thymian configuration.
Where configuration is loaded from
Section titled “Where configuration is loaded from”Thymian resolves configuration in this order:
- If you pass
--config <path>, Thymian uses that file. - Otherwise, Thymian looks for
thymian.config.yamlin--cwd(or your current directory). - If no file is found, Thymian uses built-in defaults.
Full configuration schema
Section titled “Full configuration schema”See Configuration Schema for the complete list of supported keys, types, defaults, and descriptions.
Precedence and overrides
Section titled “Precedence and overrides”After loading config, CLI flags can override parts of it. Common overrides include:
--spec <type:path>overridesspecifications--traffic <type:path>overridestraffic--report <type:path>overridesreports(used bythymian report convert)--target-url <url>overridestargetUrl--rule-set <name>extendsruleSetsat runtime (appends and deduplicates)--rule-severity <off|error|warn|hint>overridesruleSeverity--log-level <trace|debug|info|warn|error|silent>overrideslogLevel--debugeffectively overrideslogLeveltodebug--verboseeffectively overrideslogLeveltoinfo--autoload/--no-autoloadcontrols plugin autoloading
Generated baseline config
Section titled “Generated baseline config”thymian generate config creates a config based on built-in defaults plus your selected specification(s).
Typical generated baseline:
specifications: - type: openapi location: ./openapi.yaml
traffic: []
ruleSets: - '@thymian/rules-rfc-9110' - '@thymian/rules-api-description-validation'
ruleSeverity: errorrules: {}
plugins: '@thymian/plugin-http-linter': {} '@thymian/plugin-openapi': {} '@thymian/plugin-request-dispatcher': {} '@thymian/plugin-sampler': {} '@thymian/plugin-reporter': options: formatters: markdown: {} '@thymian/plugin-http-tester': {} '@thymian/plugin-http-analyzer': {} '@thymian/plugin-har': {} '@thymian/plugin-spectral': {}Workflow-specific examples
Section titled “Workflow-specific examples”Static linting only
Section titled “Static linting only”specifications: - type: openapi location: ./openapi.yaml
ruleSets: - '@thymian/rules-rfc-9110'
plugins: '@thymian/plugin-http-linter': {} '@thymian/plugin-openapi': {} '@thymian/plugin-reporter': options: formatters: markdown: {}Live API testing
Section titled “Live API testing”specifications: - type: openapi location: ./openapi.yaml
targetUrl: http://localhost:3000
ruleSets: - '@thymian/rules-rfc-9110'
plugins: '@thymian/plugin-openapi': {} '@thymian/plugin-request-dispatcher': {} '@thymian/plugin-sampler': {} '@thymian/plugin-http-tester': {} '@thymian/plugin-reporter': options: formatters: markdown: {}Convert external reports
Section titled “Convert external reports”reports declares the pre-generated external reports that thymian report convert imports; the optional specifications entries let the converter map findings onto your API description. --report flags replace the reports entries, exactly like --spec replaces specifications.
reports: - type: spectral location: ./report.json
specifications: - type: openapi location: ./openapi.yaml
plugins: '@thymian/plugin-openapi': {} '@thymian/plugin-spectral': {} '@thymian/plugin-reporter': options: formatters: markdown: {}See Importing Spectral results for the full flow.
Analyze recorded traffic
Section titled “Analyze recorded traffic”traffic: - type: fixture location: static
ruleSets: - '@thymian/rules-rfc-9110'
plugins: '@thymian/plugin-http-analyzer': {} '@thymian/plugin-reporter': options: formatters: markdown: {}Report formatters
Section titled “Report formatters”@thymian/plugin-reporter writes one file per configured formatter. Where those files land is a
plugin-level setting, not a per-formatter one: reportsDir names the base directory — a relative
path is resolved against --cwd, an absolute path is used as-is — and it defaults to
.thymian/reports. Leave it out to take the default; an empty or whitespace-only reportsDir fails validation
rather than scattering run directories through the working directory.
Every report gets its own directory beneath that base, and the files inside keep a stable name, so consecutive runs never overwrite each other:
<reportsDir>/<created-at>-<report-id>/report.<ext>.thymian/reports/2026-08-25T10-30-00-123Z-a1b2c3d4/report.md<created-at> is the report’s creation timestamp with : and . replaced by - (no colons, so
the name is valid on Windows too), and <report-id> is the first eight characters of the report
id. Every formatter in the same run sees the same report, so the markdown, CSV, and JSON output of
one run lands side by side in that one directory as report.md, report.csv and report.json —
correlating them is opening the directory.
The rule holds everywhere, including thymian serve: one reporter instance serves the whole
session, but each workflow emits its own report, so each workflow gets its own run directory.
plugins: '@thymian/plugin-reporter': options: # Optional. Defaults to .thymian/reports. reportsDir: build/reports formatters: markdown: {} # build/reports/<created-at>-<report-id>/report.md csv: {} # build/reports/<created-at>-<report-id>/report.csv json: {} # build/reports/<created-at>-<report-id>/report.json| Formatter | Output file | Use it for |
|---|---|---|
markdown |
<reportsDir>/<created-at>-<report-id>/report.md |
Human review, PR comments, CI job summaries |
csv |
<reportsDir>/<created-at>-<report-id>/report.csv |
Tabular analysis in spreadsheets or BI tools |
json |
<reportsDir>/<created-at>-<report-id>/report.json |
Automation and post-processing of the full report data |
Formatters take no options of their own — markdown: {} selects the formatter, nothing more. There
is no way to pin one fixed output file, because a fixed file is exactly what makes a second run
destroy the first one’s report.
A downstream step therefore matches a pattern rather than a path: a CI job that uploads or reads
the output should glob <reportsDir>/*/report.<ext> — for example .thymian/reports/*/report.md.
# Upload every run directory the job produced.- uses: actions/upload-artifact@v7 with: name: thymian-reports path: .thymian/reports/*/report.*A step that needs exactly one file — posting the Markdown as a PR comment, piping the JSON into a script — has two options. Either take the newest run: a run directory name starts with the report’s creation timestamp, so the names sort chronologically and the last one is the latest run.
report="$(ls -1d .thymian/reports/*/ | tail -1)report.md"Or point reportsDir at a directory that is empty when the run starts (reportsDir: build/reports
in a fresh workspace, or a step that clears it first), so the glob can only ever match once. In a
CI job with a cached or reused workspace, clearing it is a step of its own.
Migrating from the per-formatter path
Section titled “Migrating from the per-formatter path”The per-formatter path option was removed. A formatter that still sets it fails config
validation, because formatter options take no properties at all. To migrate:
- Delete the
pathkey —markdown: {}is the whole formatter configuration now. - If it pointed somewhere other than the default, set the plugin-level
reportsDirto that directory instead. It is one base for every formatter, not a per-formatter file. - Where a pipeline read the fixed file, glob
<reportsDir>/*/report.<ext>instead.
plugins: '@thymian/plugin-reporter': options: formatters: markdown: path: build/reports/report.md # ❌ beforeplugins: '@thymian/plugin-reporter': options: reportsDir: build/reports # ✅ after formatters: markdown: {} # build/reports/<created-at>-<report-id>/report.mdThe reporter also checks that reportsDir can be created before the run starts, whenever at least
one formatter is configured. A base directory it cannot create fails the run with
UnusableReportsDirectoryError rather than
letting a green run produce no files. With formatters: {} nothing is checked and no directory is
created.
A failure while a report is being written is deliberately different: it is logged as an error on stderr and leaves that one file missing, but it does not fail the run. The findings already exist by then, so an incomplete report beats losing them.
When to use the Markdown formatter
Section titled “When to use the Markdown formatter”Pick markdown when a person or AI agent reads the result — in a pull request, a CI job summary, or locally:
- Review in GitHub: the document uses inline HTML (
<details>blocks, a<sub>legend, colored<span>counts), so it pastes straight into a PR comment or$GITHUB_STEP_SUMMARY. - Triage at a glance: a severity roll-up, a per-run outcome table, and lint findings grouped under the endpoint they belong to.
- Failures only: passed test cases and passed executions without findings are left out, so the document stays focused on what needs attention.
Locations are resolved to the transaction’s Selector, such as
POST /orders (application/json) -> 201, and the raw request/response of a failed test step
is tucked into a nested <details> block. Table cells are single-line, so multi-line messages are
flattened, and expected/actual values are folded into the message rather than kept as fields.
When to use the CSV formatter
Section titled “When to use the CSV formatter”Pick csv when you want to sort, filter, or pivot results in a spreadsheet or BI tool:
- Tabular analysis: thirteen fixed columns, one record per line, nothing nested to unwrap.
- Complete outcome tally: unlike markdown, every execution gets a row — passed, failed, and skipped alike — so you can compute pass rates instead of only counting failures.
- Two row types: a
row_type=executionrow carries the outcome (statusand resolvedseverity), and eachrow_type=findingrow beneath it carries one assertion or informational detail.
The columns are run_id, run_type, tool, rule_id, location, row_type, status,
severity, finding_kind, finding_id, title, message, and detail. Structured values are
flattened into detail as key=value pairs (expected=200; actual=404). HTTP transactions are not
included.
When to use the JSON formatter
Section titled “When to use the JSON formatter”Pick json when code — rather than a reader — consumes the output, and you need the complete
report:
- Post-processing and integrations that would otherwise have to parse markdown or CSV.
- Archiving a run so its full result set can be re-examined later.
- Detail the other formatters omit, such as passed executions, finding identifiers, run-level artifacts and invocations, and the serialized Thymian format graph that locations reference.
The file contains the canonical report payload exactly as Thymian emitted it on the core.report
event, with no presentation transforms applied. The top level is always a JSON array of report
objects, holding the one report that run directory belongs to — a session that produces several
reports produces several directories, each with its own one-element array.
Locations are stored as references, not rendered endpoint strings: a thymianFormat location
carries elementId/elementType that you resolve against the run’s entry in the report’s
thymianFormat map (selected by ToolRun.thymianFormatVersion).
Validation behavior and failure modes
Section titled “Validation behavior and failure modes”If config parsing or schema validation fails, the CLI exits with code 2 and prints an error.
Common issues:
- Unsupported file extension when loading with
--config - Invalid YAML/JSON syntax
- Invalid value types (for example non-array
ruleSets) - Unknown or misspelled top-level keys — the schema only allows the documented keys and rejects anything else
- Missing specification with commands that require one
Practical tips
Section titled “Practical tips”- Prefer
thymian generate configto bootstrap a correct baseline. - Keep plugin configuration minimal first, then add plugin
optionsincrementally. - Use
--specfor one-off checks without editing files. - Keep
ruleSeverity: errorin CI unless you explicitly want warnings/hints to gate builds.
Related pages
Section titled “Related pages”- Understanding the Configuration File — quick overview in the Getting Started guide
- Use
thymian lintworkflow - Use
thymian testworkflow - Use
thymian analyzeworkflow - Importing Spectral results