This is the full developer documentation for Thymian # Thymian > Write API rules once. Lint specs, test endpoints, and analyze traffic — all with the same rules, across every stage of your API lifecycle. Open source. AGPL v3 licensed. ## Try it now One command. No config required. Terminal ``` $ npx thymian lint --spec openapi:openapi.yaml ``` ## Thymian Across the API Lifecycle From specification to production — see where Thymian lints, tests, and analyzes your APIs. LintStatic analysis TestLive endpoint testing AnalyzeTraffic analysis | Context | 📐API Design / Spec | 💻Development | 🔀CI | 🚀Dev / Staging | 🧪QA | 🌐Production | | ------- | ------------------- | ------------- | ---- | --------------- | ---- | ------------ | | Lint | | | | — | — | — | | Test | — | | | | | — | | Analyze | — | | — | — | | | ### 📐API Design / Spec Lint ### 💻Development LintTestAnalyze ### 🔀CI LintTest ### 🚀Dev / Staging Test ### 🧪QA TestAnalyze ### 🌐Production Analyze ## Built for Developer & AI Workflows Thymian fits naturally into the development loop — whether you're coding by hand or an AI agent is driving. Generate, validate, fix, repeat. 🏗️ ### Generate Developer or AI Agent Write or generate an OpenAPI spec or API implementation based on requirements. 🔍 ### Validate Thymian CLI Lint specs, test live endpoints, or analyze traffic — same rules, every context. 🔧 ### Fix Developer or AI Agent Rich error context with rule IDs, RFC references, and exact locations enables targeted fixes. Terminal ``` $ npx thymian lint --spec openapi:openapi.yaml GET /api/v1/users/{id} → 404 NOT FOUND - application/json ✕ error: Origin servers with a clock MUST generate Date header in 2xx, 3xx, and 4xx responses. rfc9110/origin-server-with-clock-must-generate-date-for-2xx-3xx-4xx GET /api/v1/users/{id} → 401 UNAUTHORIZED - application/json ✕ error: Server MUST send WWW-Authenticate header for 401 response. rfc9110/server-must-send-www-authenticate-header-for-401-response GET /api/v1/users → 200 OK - application/json ⚠ warn: Origin servers SHOULD send ETag for selected representation. rfc9110/origin-server-should-send-etag ⚠ warn: Origin servers SHOULD send Last-Modified for selected representation. rfc9110/origin-server-should-send-last-modified Found 2 errors, 2 warnings and 0 hints. ``` ### Why It Works Designed from the ground up for both human developers and AI agents. 🛡️ ### Security Conformance Catch missing authentication headers, incorrect CORS configurations, and non-standard error disclosure before they reach production. ⚡ ### Performance Validation Enforce Cache-Control policies, validate content encoding headers, and verify response payload conformance against your specifications. 🔒 ### Reliability Guarantees Ensure consistent error formats (RFC 9457), proper status codes, and specification compliance across all environments — from staging to production. 🧠 ### Rich Error Context Violations include rule names, RFC references, severity levels, and exact locations — giving any AI agent or developer all the context needed to fix issues. 🔁 ### Deterministic Feedback Loops Non-interactive mode with explicit flags ensures reproducible results — perfect for agentic retry-and-fix workflows. 🧩 ### Zero Ambiguity Exit codes, structured results, and typed schemas mean agents never have to parse prose or guess what went wrong. ## One Rule. Every Stage. Zero Drift. Write validation logic once — Thymian applies it across your entire API lifecycle, catching inconsistencies before they reach users. error-responses.rule.ts ``` export default httpRule('consistent-error-responses') .severity('error') .type('static', 'test', 'analytics') .description('Error responses must use problem+json') .rule(ctx => ctx.validateCommonHttpTransactions({ appliesTo: statusCodeRange(400, 599), violatedWhen: not(responseMediaType('application/problem+json')) }) ) .done(); ``` Same logic. Three contexts. Automatic adaptation. 📐 ### API Design & CI Lint Validates API specifications before any code is written. Data SourceOpenAPI Specifications CatchesMissing headers, wrong status codes, incomplete schemas Spec violation? Caught here. 💻 ### Dev & Staging Test Actively tests running endpoints against the same rule. Data SourceLive API Endpoints CatchesImplementation diverges from spec, runtime errors Implementation diverges? Caught here. 🌐 ### QA & Production Analyze Analyzes real traffic to verify compliance passively. Data SourceRecorded HTTP Traffic CatchesReal-world violations, undocumented behavior, regressions Production regression? Caught here. ### Sharable Rule Sets Package rules as npm modules and share them across teams and projects. Use built-in sets like `@thymian/rules-rfc-9110` or create your own organizational standards. `@thymian/rules-rfc-9110``@thymian/rules-api-description-validation``@your-org/api-standards` ## Built to Be Extended Virtually everything in Thymian is a plugin. A reactive event bus connects them all — swap, extend, or build your own without touching a single line of framework code or even JavaScript! 🔌 ### Everything Is a Plugin Core features, integrations, and extensions are all plugins — the framework imposes no hard-coded behavior. 📡 ### Event-Driven Architecture Plugins communicate through a reactive event bus with typed events, actions, and error flows. 🔗 ### Loose Coupling Plugins interact through well-defined APIs and events. Add, remove, or swap plugins without touching others. 🌐 ### Remote Plugins via WebSocket Connect plugins running in any language or process over WebSockets — perfect for polyglot environments. 📦 ### Shareable as npm Packages Publish plugins and rule sets as npm modules. Share validation logic across teams and projects. 🛡️ ### Schema-Validated Config Plugin options are validated at startup via JSON Schema — catch misconfigurations before they cause failures. my-custom-plugin.ts ``` import type { ThymianPlugin } from '@thymian/core'; export const myPlugin: ThymianPlugin = { name: 'my-custom-plugin', version: '*', async plugin(bus, logger, options) { bus.onAction('core.ready', async (payload, ctx) => { logger.info('Plugin is ready!'); ctx.reply(); }); bus.onAction('core.lint', async (payload, ctx) => { // Your custom validation logic here ctx.reply({ results: [] }); }); } }; ``` Typed interface. Event-driven. Zero boilerplate. ### Official Plugin Ecosystem Ready-to-use plugins covering every stage of the API lifecycle — or build your own with the same powerful APIs. 📐 #### HTTP Linter `@thymian/plugin-http-linter` Official Static analysis of OpenAPI specs and HTTP definitions. 🧪 #### HTTP Tester `@thymian/plugin-http-tester` Official Live endpoint testing against your rule definitions. 📊 #### HTTP Analyzer `@thymian/plugin-http-analyzer` Official Passive analysis of recorded HTTP traffic for compliance. 📄 #### OpenAPI `@thymian/plugin-openapi` Official Load, parse, and validate OpenAPI specifications. 📋 #### Reporter `@thymian/plugin-reporter` Official Collect and format validation results into reports. 🎯 #### Sampler `@thymian/plugin-sampler` Official File-based HTTP request sampling for reproducible tests. 🔗 #### WebSocket Proxy `@thymian/plugin-websocket-proxy` Official Connect remote plugins over WebSocket for distributed setups. 🚀 #### Request Dispatcher `@thymian/plugin-request-dispatcher` Official Dispatch HTTP requests to live endpoints for testing. ### Build Your Own in Minutes Implement the `ThymianPlugin` interface, hook into lifecycle events, and publish as an npm package. Local plugins, remote WebSocket plugins, and shared rule sets all use the same unified API. [Plugin Developer Guide](/guides/plugin-developers-guide/)[Remote Plugins Guide](/guides/remote-plugins/how-to-connect-remote-plugins/) ## How Thymian Is Different Other tools handle one piece of the puzzle. Thymian unifies spec linting, endpoint testing, and traffic analysis under a single rule engine. ✓ SupportedPlanned— Not supported | Capability | Spectral + Prism | Schemathesis | Bruno | REDbot | Thymian | | ------------------------ | ---------------- | ------------ | ----- | ------ | ------- | | Static Spec Linting | | — | — | — | | | Live Endpoint Testing | | | | | | | Traffic Analysis | | — | — | | | | Proxy Mode | | — | — | — | Planned | | HTTP Conformance | — | — | — | | | | Interactive API Client | — | — | | — | — | | Mock Server Generation | | — | — | — | — | | Write Rules Once | — | — | — | — | | | Extensible Plugin System | | — | — | — | | | AI-Optimized Output | — | — | — | — | | | Sharable Rule Packages | | — | — | — | | ### Static Spec Linting Spectral + Prism Schemathesis— Bruno— REDbot— Thymian ### Live Endpoint Testing Spectral + Prism Schemathesis Bruno REDbot Thymian ### Traffic Analysis Spectral + Prism Schemathesis— Bruno— REDbot Thymian ### Proxy Mode Spectral + Prism Schemathesis— Bruno— REDbot— ThymianPlanned ### HTTP Conformance Spectral + Prism— Schemathesis— Bruno— REDbot Thymian ### Interactive API Client Spectral + Prism— Schemathesis— Bruno REDbot— Thymian— ### Mock Server Generation Spectral + Prism Schemathesis— Bruno— REDbot— Thymian— ### Write Rules Once Spectral + Prism— Schemathesis— Bruno— REDbot— Thymian ### Extensible Plugin System Spectral + Prism Schemathesis— Bruno— REDbot— Thymian ### AI-Optimized Output Spectral + Prism— Schemathesis— Bruno— REDbot— Thymian ### Sharable Rule Packages Spectral + Prism Schemathesis— Bruno— REDbot— Thymian ## Latest activity * Paper ### [WEBIST: Research Paper](/events/) Angers, FranceOctober 27, 2026 View event * Recorded Talk ### [Should I GET or Should I POST — The Clash With HTTP Conformance](/resources/) [Guest of My Coding Zone on YouTube](https://www.youtube.com/watch?v=1IvUSEnGkZ8)View resource [Get Started](/introduction/getting-started/) [Enterprise](/enterprise/) # Introducing Thymian: HTTP Conformance for the Entire API Lifecycle Today, we are excited to announce the release of **Thymian**! Most API tools answer one question: does the response match the schema? Almost none ask a more fundamental one: does this API correctly implement HTTP? APIs routinely violate protocol requirements around caching headers, conditional requests, content negotiation, status codes, and method semantics. These violations can cause performance, interoperability and security issues such as broken CDN Caching, HTTP Request Smuggling, Cache Poisoned Denial-of-Service, Web Cache Deception — that surface not in development but in production, when the cost is highest. The few tools that do check HTTP conformance, such as REDbot or h2spec, operate on a single URL at a time, reference outdated specifications, and offer no integration into modern development workflows. None of them consider the complete API, its resources, their relations, or the complex flows between them. **So, we built Thymian to close this gap.** ## What Is Thymian? [Section titled “What Is Thymian?”](#what-is-thymian) Thymian is an open-source, language-agnostic HTTP conformance and API governance engine. It validates your APIs against RFC standards and your OpenAPI specification — statically, against live endpoints, and in recorded production traffic. The project originated as a master’s thesis at the University of Würzburg and Technical University of Applied Sciences Würzburg-Schweinfurt, developed with academic rigor and a clear goal: build the HTTP conformance tool that the ecosystem is missing. It is now available as **v0.1** on [npm](https://www.npmjs.com/package/thymian) and [GitHub](https://github.com/thymianofficial/thymian), built and maintained by [qupaya](https://qupaya.com). ## Three Modes, One Rule [Section titled “Three Modes, One Rule”](#three-modes-one-rule) Thymian operates in three distinct execution modes; and the same rule works in all of them: * **`thymian lint`** — Static analysis of OpenAPI specifications. No running server required. Catch issues before a single line of code is written. * **`thymian test`** — Conformance testing against live API endpoints. Send real HTTP requests and validate the responses against RFC rules. * **`thymian analyze`** — Analysis of recorded HTTP traffic. Validate captured production traffic against the same rules, without modifying your infrastructure. Thymian adapts the execution context, while the rule logic remains the same. Just write your logic once, and validate it everywhere. Thymian ships with **399 rules implementing RFC 9110** (HTTP Semantics), covering authentication, conditional requests, content negotiation, header field semantics, methods, status codes, range requests, and more. Each rule carries rich metadata: severity, RFC section reference, explanation, recommendation, and the specific HTTP participant it applies to — client, server, proxy, cache, or intermediary. ## API Drift — Beyond Schema Comparison [Section titled “API Drift — Beyond Schema Comparison”](#api-drift--beyond-schema-comparison) API drift is typically understood as the gap between what an API’s specification describes and what the implementation actually does. Tools like Spectral, Dredd, Schemathesis, and Pact address this — but each covers only a single stage of the lifecycle, with its own rule format and its own scope: | Stage | Typical Tools | Rule Format | | ---------- | ------------------------- | ------------------------ | | Design | Spectral, Bump.sh, Optic | `.spectral.yml` rulesets | | Dev/CI | Dredd, Schemathesis, Pact | Test code, contracts | | Production | Treblle | Dashboard config | When a validation passes at design time but the same behavior breaks in production, there is no connection between the tools that could have caught it. Each stage is a silo. Thymian eliminates this fragmentation. One rule set, applied consistently from specification review through live testing to production traffic analysis. If a rule exists, it can be enforced at every stage without re-implementation, format translation, or coverage gaps between tools. But Thymian also goes further. The entire landscape of API drift tooling focuses on **specification drift**: does the response body match the documented schema? None of these tools check **protocol semantics drift**: does this API correctly implement HTTP as defined in RFCs 9110 and 9111? No existing tool validates whether a `HEAD` response carries the same headers as `GET` without a body. Whether a `405 Method Not Allowed` includes the required `Allow` header. Whether `ETag` and `If-None-Match` produce a correct `304 Not Modified` flow. Whether `Cache-Control` directives are semantically valid. Whether method safety and idempotency guarantees are upheld. Thymian is currently the only tool that detects protocol-level drift, and it does so across the entire API lifecycle. ## API Governance Across the Lifecycle [Section titled “API Governance Across the Lifecycle”](#api-governance-across-the-lifecycle) HTTP conformance rules are governance rules. When an organization defines that all APIs must correctly implement conditional requests, or that caching headers must follow RFC 9111, those requirements apply at every stage — not just in a CI linter that runs once before merge. Thymian enables this by treating rules as the single source of truth for API governance: | Lifecycle Stage | Thymian Mode | What It Catches | | ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------------- | | **API Design / Spec Review** | `lint` | Missing headers, incomplete schemas, wrong status codes in the OpenAPI document | | **Development / CI** | `test` | Implementation drift — the live server deviates from specification and RFC requirements | | **Staging / QA** | `test` + `analyze` | Integration drift — behavior changes when deployed behind proxies, load balancers, or CDNs | | **Production** | `analyze` | Production regressions — real traffic reveals violations that never appeared in test environments | Rules are distributable as npm packages. `@thymian/rules-rfc-9110` ships with Thymian. Organizations can publish their own rule sets — `@your-org/api-standards` — containing company-specific governance rules that are enforced at every stage, in every team, with zero drift between what is checked in CI and what is validated in production. **One rule. Every stage. Zero drift.** ## Built for Developer and AI Workflows [Section titled “Built for Developer and AI Workflows”](#built-for-developer-and-ai-workflows) Thymian is CLI-first, designed for both human developers and AI agents: * **`--no-interactive`** mode for deterministic, non-interactive execution in CI and agent workflows * **Rich error context** — every violation includes the rule name, RFC reference, severity, exact location, explanation, and recommendation * **Exit codes** and structured results for composable pipelines Whether you work with GitHub Copilot, Cursor, Aider, or a local model, any tool that reads structured CLI output can consume Thymian’s results and act on them. ## Everything Is a Plugin [Section titled “Everything Is a Plugin”](#everything-is-a-plugin) Thymian follows a microkernel architecture. The core provides the event bus, plugin lifecycle, and rule engine. Everything else, like OpenAPI parsing, HTTP linting, live testing, traffic analysis, report formatting, request dispatching, is a plugin. Eight official plugins ship with Thymian. Custom plugins can be written in TypeScript or, via the WebSocket proxy plugin, in any programming language. Rules are authored with a type-safe fluent builder and can be shared as npm packages. Plugin communication is event-driven and loosely coupled. You can extend or replace any component without modifying framework code. ## Get Started [Section titled “Get Started”](#get-started) Run your first conformance check. No installation required: ```bash npx thymian lint --spec openapi:openapi.yaml ``` Thymian will validate your OpenAPI specification against RFC 9110 rules and report any conformance violations with explanations, RFC references, and recommendations for fixing them. ## What’s Next [Section titled “What’s Next”](#whats-next) Thymian v0.1.x is the foundation. Upcoming work includes expanded RFC coverage (RFC 9111 caching semantics and beyond), more integrations, and continued growth of the rule and plugin ecosystem. * [Documentation](https://thymian.dev) * [GitHub](https://github.com/thymianofficial/thymian) * [Discord](https://discord.gg/TRSwCxbz9f) — join the community * [Enterprise Consulting](https://thymian.dev/enterprise) — professional support from the creators of Thymian # What are the files under generated/? > What the committed type surface is, why it is committed, and what it protects you from. `thymian sampler init` writes two files and asks you to commit them: ```plaintext .thymian/sampler/generated/ request-types.d.ts Endpoints, keyed by Selector, plus the spec-derived unions hooks-api.d.ts the @thymian/hooks surface, typed against them ``` They are generated, they are committed, and they are not to be edited. ## What they give you [Section titled “What they give you”](#what-they-give-you) * **Autocomplete for Selectors.** `beforeEach('` and your editor offers every Transaction in your API, so you can discover them without reading the description. * **Compile-time checking of everything a hook targets.** A Selector, a filter value, a path — all closed unions taken from your description. * **Typed request and response bodies**, including the parsed body a `utils.request` call returns. * **A drift oracle.** Every way an API change can invalidate a hook becomes a TypeScript error at that hook’s own line. ## What is in `request-types.d.ts` [Section titled “What is in request-types.d.ts”](#what-is-in-request-typesdts) ```typescript export type Method = "DELETE" | "GET" | "POST" | "PUT"; export type Status = 200 | 201 | 204 | 400 | 401 | 404; export type StatusClass = "2XX" | "4XX"; export type Path = "/astronauts" | "/astronauts/{id}" | "/launches"; export type RequestMediaType = "application/json"; export type ResponseMediaType = "application/json"; export type Endpoints = { "GET /astronauts -> 200 (application/json)": { method: "GET"; path: "/astronauts"; status: 200; requestMediaType: ""; responseMediaType: "application/json"; authorize: boolean; req: { … }; res: { statusCode: 200; headers: { … }; body: … }; }; … }; export type Selector = keyof Endpoints; export type Responses = { "GET /astronauts": | TransactionResponse<"GET /astronauts -> 200 (application/json)"> | TransactionResponse<"GET /astronauts -> 401">; … }; ``` One key is exactly one Transaction, so a key carries its own status and media types — nothing about a *target* has to be narrowed. What the server answers with is a different question, and `Responses` answers it: keyed by the request half of a Selector — the operation — it is the union `utils.request` returns, because initiating a Transaction is not the same as getting one. ### Examples in your description become open literal unions [Section titled “Examples in your description become open literal unions”](#examples-in-your-description-become-open-literal-unions) If your description gives examples for a property, they show up in the type: ```typescript id: 'a1' | 'a2' | (string & {}); ``` The literals autocomplete; the `& {}` keeps the type open, so any other string is still allowed. This is done **property by property**. An object example becomes examples on its properties, one level down, recursively — never a union of whole example objects. That is deliberate: hooks mutate the request in place, and on a union TypeScript checks a property *write* against the intersection of the members, so a union of closed example objects would make ordinary mutation a compile error. `enum` and `const` are already closed and are left alone. ## Why they are committed [Section titled “Why they are committed”](#why-they-are-committed) Because they are the record of what your API description said, and that record is what makes staleness detectable. There is no lock file: the types *are* the lock file. `thymian sampler validate` regenerates the surface in memory and compares it with what you committed — so it can tell “you have not run `sync`” apart from “your hooks no longer fit the API”. `thymian sampler sync --check` is the same comparison as a CI gate. The comparison ignores comments and formatting, so a description-only edit, a document reordering and a reformat are not drift. See [Upgrading Thymian](/guides/sampler/upgrading-thymian/) for the one ritual this implies. ## Should I commit them? [Section titled “Should I commit them?”](#should-i-commit-them) **Yes.** Everyone on the project then shares the same baseline, the same autocomplete and the same gate — and a pull request that changes the API shows the type diff next to it. ## Regenerating [Section titled “Regenerating”](#regenerating) ```bash npx thymian sampler sync ``` `generated/` is rewritten wholesale, so a file left over from a shape your API no longer has cannot survive. Nothing outside `generated/` is touched — in particular not `tsconfig.json`, which `init` scaffolds once and is yours from then on. ## Do I need them? [Section titled “Do I need them?”](#do-i-need-them) No. `@thymian/hooks` resolves through the plugin itself at run time, so `thymian test` runs your hooks with nothing generated and nothing committed. What `init` adds is editor support and a type gate — real value, and still optional. # How the Hook System Works > How hooks are discovered, resolved and executed — and why discovery is export-based rather than path-based. You do not need this page to use hooks. It is here for when you want to know why the design is the way it is. ## The problem it solves [Section titled “The problem it solves”](#the-problem-it-solves) Request samples are a projection of your API description, rebuilt on every run. Nothing about a request is stored, so nothing about a request can go stale. Hooks are different: they are yours, they are the investment, and they have to survive the description changing underneath them. That makes one question central — **what is a hook’s identity?** In the first version of the sampler, a hook’s identity was its **location**: a file at `…/launches/[id]/@GET/auth.authorize.ts` applied to whatever sat below that directory. That is discoverable and requires no naming, and it has one fatal property: rename a path in the API description and the hook is orphaned at a dead directory, silently doing nothing. The tree could not tell you, because as far as the filesystem was concerned nothing had happened. So a hook’s identity is now a **Selector** — the fully-qualified address of a Transaction — written in the code. A Selector that stops existing is a value that stops existing, and that is something a compiler and a loader can both see. A Selector is also the only way Thymian ever writes a Transaction down. Check lines, test-case names, rule headings, report locations and error messages all print it verbatim, so any transaction you read in the terminal can be pasted straight into a hook as its target: ```console ✖ POST /launches/{id}/crew-members (application/json) -> 409 ``` ```ts beforeEach( 'POST /launches/{id}/crew-members (application/json) -> 409', (request) => { // … }, ); ``` ## Discovery [Section titled “Discovery”](#discovery) * .thymian/sampler/hooks/ * auth.ts * launches/ * seed.ts The loader walks `hooks/` recursively and, for each file: 1. **Keeps** `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, `.cjs`. Skips declaration files and dot-directories, so an editor’s cache inside the tree is not scanned. 2. **Imports it** through [`jiti`](https://github.com/unjs/jiti), which transpiles TypeScript on the fly. The bare specifier `@thymian/hooks` is aliased to a module inside the plugin, which is why a hook runs with nothing generated and nothing installed beside it. 3. **Collects every export** — named or default — that is a registration, or an array of them. A registration is a value, produced by calling `beforeEach`, `afterEach`, `defineSample`, `authorize`, `beforeAll` or `afterAll`. Nothing is invoked to find out whether an export is a hook: the check is structural, so a function you export for your own reasons is never called by accident. Exports only A registration you build but never export is invisible. `beforeEach(sel, fn);` on its own line does nothing at all. The directory layout carries no meaning. Moving a hook file is a non-event. ### Why registrations are returned values [Section titled “Why registrations are returned values”](#why-registrations-are-returned-values) The obvious alternative is an ambient registry — `beforeEach(…)` pushing into a module-level list the plugin then reads. It cannot work here. jiti evaluates the aliased runtime in its own module registry, so the plugin and the hook file hold *different instances* of that module: the list the plugin reads is never the list the hook wrote to. Returning the registration sidesteps the realm boundary entirely, and export-based discovery is what makes the returned value reachable. ## Order [Section titled “Order”](#order) Two hooks of the same kind on one Transaction both run, in **registration order**: file order on the outside — hooks-directory-relative, sorted, so it does not depend on the filesystem — and source order inside each file. Source order matters because an ES module namespace exposes its keys *sorted*, so export order is not registration order. Each registration is stamped with a counter as it is created, and the loader assigns one scan-wide sequence number once the file order is settled. ## Resolution [Section titled “Resolution”](#resolution) Once the description is loaded, the sampler builds a **transaction catalog**: a bijection between every Transaction and its Selector, sorted by Selector. Then each registration’s target is resolved against it: | Target | Resolved by | | ------------------- | --------------------------------------- | | a Selector | a map lookup | | a list of Selectors | a lookup each | | a filter | evaluating it against every Transaction | Four things make a hook unresolvable, and each gets its own diagnostic naming the file and the export: a Selector that names nothing, a value that is not a legal filter value, a path or glob that names nothing, and a filter whose legal values intersect nothing. An unresolved hook **fails the run** — before the first request is sent, so nothing has happened yet. It fails when the *run* starts rather than when the description is published, because `thymian sampler validate` has to survive publishing in order to report every unresolved hook at once instead of dying on the first. A hook is never re-bound to a nearby Transaction. That would be the silent staleness the whole design exists to remove. ## Execution [Section titled “Execution”](#execution) ```plaintext core.request.sample: generate the request → defineSample http-testing.beforeRequest: [first request ever → beforeAll] → beforeEach … http-testing.authorize: if the run option and request.authorize agree → authorize → the request is sent → http-testing.afterResponse: afterEach … core.close: [if a request was sent] afterAll + cleanups, reversed ``` Each of those is an action on Thymian’s event bus, so the sampler plugs into the same seams any plugin can. There is no sampler-specific machinery in the core. ### Mutate in place [Section titled “Mutate in place”](#mutate-in-place) Every hook returns nothing. A hook receives the request or response object and changes it; the return value is discarded. That is not a style preference. If a return value were honoured, the most ordinary shorthand there is — ```typescript beforeEach(sel, (request) => (request.headers['x-trace'] = 'yes')); ``` — evaluates to the string `'yes'`, and the whole request would be replaced by it. ### `beforeAll` and the latch [Section titled “beforeAll and the latch”](#beforeall-and-the-latch) “Once before the run” has no meaning until something decides when the run starts, and the sampler’s first sight of a run is its first request. So a latch is armed by the first `beforeRequest`, ahead of that Transaction’s own `beforeEach` hooks. The same latch gates teardown, which is why `thymian sampler show` and `thymian sampler init` never run somebody’s `afterAll`. The latch is armed *before* the `beforeAll` callbacks run, so a `beforeAll` that threw still gets its teardown, and a second request cannot re-run setup that half succeeded. ### Teardown [Section titled “Teardown”](#teardown) The cleanups `beforeAll` returned and the `afterAll` hooks are **one list**, run in reverse order of registration. So an `afterAll` registered between two `beforeAll` hooks runs between their cleanups — grouping the two kinds apart would put it at one end or the other, which is not what “reverse order” means. Teardown is best-effort: `core.close` happens after the report is written, so a failure there cannot be acted on. An error is logged as a warning and the rest of the teardown still runs, because one leaking fixture must not strand the others. ## Cross-endpoint requests [Section titled “Cross-endpoint requests”](#cross-endpoint-requests) `utils.request` is keyed by Selector and resolved through the same catalog, so the strings the committed types carry and the strings resolved at run time are the same strings by construction — there is no second key language to keep in step. By default a nested request runs the target’s own pipeline, so seeding behaves like the real run. The active Selector chain travels with the hook context, and re-entering a Selector already on it fails loudly with the chain printed. The chain travels in the *context* rather than living in the runner so that it describes this call’s ancestry rather than whatever the runner did last. ## Further reading [Section titled “Further reading”](#further-reading) * [Hooks API Reference](/references/plugins/sampler/hooks-api/) * [How to write a Hook](/guides/hooks/write-hooks/) * [Migrating from the samples tree](/guides/sampler/migrating-from-the-samples-tree/) # Rule Types > Deep dive into lint, test, and analyze validation contexts Thymian provides three validation contexts, each designed for a specific stage in the API development lifecycle. Understanding when and how to use each context helps you create effective validation rules. ## Context Overview [Section titled “Context Overview”](#context-overview) ``` flowchart TD A[Rule Definition] --> B{Rule Types} B -->|. type'static'| C[LintContext] B -->|. type'test'| D[TestContext] B -->|. type'analytics'| E[AnalyzeContext] C --> F[ThymianFormat
OpenAPI Spec] D --> G[Live API
HTTP Requests] E --> H[SQLite DB
Recorded Traffic] F --> I[Fast
Design-time] G --> J[Active
Integration] H --> K[Passive
Monitoring] ``` Each context provides different capabilities and trade-offs: | Context | Speed | Network | Data Source | Use Case | | ----------- | ------------- | ------- | ---------------- | --------------------- | | **Lint** | ⚡ Very Fast | No | OpenAPI spec | Design validation | | **Test** | ⏱️ Variable\* | Yes | Live API | Integration testing | | **Analyze** | ⚡ Fast | No | Recorded traffic | Production monitoring | *\*Test speed depends on API endpoint performance and network conditions* ## Lint Context [Section titled “Lint Context”](#lint-context) ### What It Does [Section titled “What It Does”](#what-it-does) The **lint context** validates API specifications without making any HTTP calls. It analyzes the Thymian format (derived from OpenAPI documents) to check that your API design follows rules. ### When to Use [Section titled “When to Use”](#when-to-use) * **During design phase** — Catch issues before writing code * **In CI/CD pipelines** — Fast feedback on spec changes * **Design-first workflows** — Validate specs as you create them * **API governance** — Enforce organizational standards ### Advantages [Section titled “Advantages”](#advantages) * ✅ **Very fast** — No network latency, validates in milliseconds * ✅ **No dependencies** — Doesn’t require a running API * ✅ **Comprehensive coverage** — Checks all defined endpoints * ✅ **Early feedback** — Catches issues before implementation ### Limitations [Section titled “Limitations”](#limitations) * ❌ **Spec-only** — Can’t validate actual API behavior * ❌ **No runtime checks** — Can’t detect implementation issues * ❌ **Limited to definitions** — Only validates what’s documented ### Example Usage [Section titled “Example Usage”](#example-usage) ```typescript import { httpRule } from '@thymian/core'; import { statusCode, not, responseHeader } from '@thymian/core'; export default httpRule('require-rate-limit-headers') .severity('warn') .type('static') // Lint context only .description('All responses must define rate limiting headers') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(200), violatedWhen: not(responseHeader('x-ratelimit-limit')) })) .done(); ``` ### What Gets Validated [Section titled “What Gets Validated”](#what-gets-validated) The lint context checks: * Request/response definitions in OpenAPI specs * Header presence and schemas * Status code definitions * Media type declarations * Path parameter definitions * Query parameter schemas ### Common Use Cases [Section titled “Common Use Cases”](#common-use-cases) **1. Enforce header requirements:** ```typescript // Ensure all error responses define Content-Type . type('static') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCodeRange(400, 599), violatedWhen: not(responseHeader('content-type')), }) ) ``` **2. Validate status codes:** ```typescript // Check that DELETE operations define 204 responses . type('static') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: method('DELETE'), violatedWhen: not(statusCode(204)), }) ) ``` **3. Check request bodies:** ```typescript // POST requests should define request bodies . type('static') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: method('POST'), violatedWhen: not(hasRequestBody()), }) ) ``` ## Test Context [Section titled “Test Context”](#test-context) ### What It Does [Section titled “What It Does”](#what-it-does-1) The **test context** generates and executes HTTP requests against a live API. It actively tests endpoints to validate that the implementation follows rules. ### When to Use [Section titled “When to Use”](#when-to-use-1) * **Integration testing** — Validate API implementation * **CI/CD pipelines** — Test changes before deployment * **Continuous validation** — Ensure API compliance * **Behavior verification** — Test actual server responses ### Advantages [Section titled “Advantages”](#advantages-1) * ✅ **Tests real implementation** — Validates actual behavior * ✅ **Discovers runtime issues** — Finds problems specs don’t catch * ✅ **Verifies compliance** — Confirms servers follow standards * ✅ **Active probing** — Tests edge cases and error scenarios ### Limitations [Section titled “Limitations”](#limitations-1) * ❌ **Speed depends on API** — Performance varies based on endpoint response times and network conditions * ❌ **Requires running API** — Can’t test without deployment * ❌ **May affect state** — Tests can modify data * ❌ **Limited coverage** — Only tests what you configure ### Example Usage [Section titled “Example Usage”](#example-usage-1) ```typescript import { httpRule } from '@thymian/core'; import { constant, not, responseHeader } from '@thymian/core'; export default httpRule('test-request-id-propagation') .severity('error') .type('test') // Test context only .description('Live API must return X-Request-ID for request tracking') .appliesTo('server') .rule((ctx) => ctx.validateHttpTransactions({ appliesTo: constant(true), violatedWhen: not(responseHeader('x-request-id')) })) .done(); ``` ### What Gets Validated [Section titled “What Gets Validated”](#what-gets-validated-1) The test context checks: * Actual HTTP responses from live endpoints * Header values in real responses * Status codes returned by servers * Response bodies and formats * Server behavior under specific conditions A rule’s `appliesTo` is checked twice in the test context: against the specification, to choose which requests to send, and again against the response that came back. A rule for 405 responses therefore stays silent when the server answers 204. `violatedWhen` runs only on responses `appliesTo` accepts, so put a fact only live traffic carries, such as a `Content-Length` header, there rather than in `appliesTo`. ### Advanced: Custom Test Logic [Section titled “Advanced: Custom Test Logic”](#advanced-custom-test-logic) For complex scenarios, override the default test behavior: ```typescript import { and, method, not, statusCode, singleTestCase, httpRule } from '@thymian/core'; export default httpRule('server-supports-get-head') .severity('error') .type('test') .description('Server must support GET and HEAD methods') .appliesTo('server') .overrideTest((ctx) => ctx.httpTest( singleTestCase() .forTransactionsWith(method('GET')) .run({ checkResponse: false }) .expectForTransactions(not(statusCode(501))) .done(), ), ) .done(); ``` ### Test Options [Section titled “Test Options”](#test-options) The test context supports options for controlling test behavior: ```typescript type Options = { testAllEndpoints?: boolean; }; const schema: JSONSchemaType = { type: 'object', properties: { testAllEndpoints: { type: 'boolean', nullable: true, default: false, }, }, }; export default httpRule('configurable-test') .options(schema) .type('test') .overrideTest((ctx, options) => { // Use options.testAllEndpoints in test logic }) .done(); ``` ## Analyze Context [Section titled “Analyze Context”](#analyze-context) ### What It Does [Section titled “What It Does”](#what-it-does-2) The **analyze context** validates recorded HTTP transactions stored in a SQLite database. It analyzes real traffic that has already occurred. ### When to Use [Section titled “When to Use”](#when-to-use-2) * **Production monitoring** — Validate live traffic patterns * **Post-deployment analysis** — Discover issues in production * **Traffic auditing** — Ensure compliance with policies * **Debugging** — Analyze problematic transactions ### Advantages [Section titled “Advantages”](#advantages-2) * ✅ **Real-world data** — Validates actual usage patterns * ✅ **No impact** — Passive analysis, doesn’t affect services * ✅ **Historical analysis** — Can analyze past traffic * ✅ **SQL optimization** — Efficient queries on large datasets ### Limitations [Section titled “Limitations”](#limitations-2) * ❌ **Requires recorded data** — Need traffic collection * ❌ **Passive only** — Can’t test, only analyze * ❌ **Coverage depends on traffic** — Only checks what was recorded * ❌ **After the fact** — Discovers issues after they occurred ### Example Usage [Section titled “Example Usage”](#example-usage-2) ```typescript import { httpRule } from '@thymian/core'; import { statusCode, not, responseHeader } from '@thymian/core'; export default httpRule('analyze-401-responses') .severity('error') .type('analytics') // Analyze context only .description('Recorded 401 responses must include WWW-Authenticate') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(401), violatedWhen: not(responseHeader('www-authenticate')) })) .done(); ``` ### What Gets Validated [Section titled “What Gets Validated”](#what-gets-validated-2) The analyze context checks: * Recorded HTTP request/response pairs * Header values from real traffic * Status code patterns * Response formats and content * Transaction sequences and flows ### SQL Optimization [Section titled “SQL Optimization”](#sql-optimization) The analyze context automatically compiles filter expressions to SQL for efficiency. `appliesTo` and an expression `violatedWhen` are combined into one query; a function `violatedWhen` is called only on the rows `appliesTo` selects: ```typescript // These filters... ctx.validateCommonHttpTransactions({ appliesTo: constant(true), violatedWhen: not(responseHeader('x-correlation-id')) }); // ...is compiled to SQL like: // SELECT * FROM http_transactions // WHERE NOT EXISTS ( // SELECT 1 FROM request_headers // WHERE header_name = 'x-correlation-id' // ) ``` This makes analytics rules perform well even on large datasets. ### Advanced: Custom SQL Queries [Section titled “Advanced: Custom SQL Queries”](#advanced-custom-sql-queries) For complex analysis, write custom SQL: ```typescript export default httpRule('analyze-auth-flow') .severity('warn') .type('analytics') .description('Validate authentication flow patterns') .appliesTo('server') .overrideAnalyticsRule((ctx) => { const db = ctx.repository.db; // Custom SQL for complex analysis const query = ` SELECT t1.id, t2.id FROM http_transactions t1 JOIN http_transactions t2 ON t1.request_url = t2.request_url WHERE t1.response_status = 401 AND t2.response_status = 200 AND t2.timestamp > t1.timestamp `; const violations = []; const stmt = db.prepare(query); for (const row of stmt.iterate()) { const [req, res] = ctx.repository.readTransactionById(row.id); if (needsValidation(req, res)) { violations.push({ location: { elementType: 'edge', elementId: row.id }, }); } } return violations; }) .done(); ``` ## Choosing the Right Context [Section titled “Choosing the Right Context”](#choosing-the-right-context) ### Decision Tree [Section titled “Decision Tree”](#decision-tree) ``` flowchart TD Start[Need to validate...] --> Q1{Spec or behavior?} Q1 -->|Spec| Lint[Use Lint] Q1 -->|Behavior| Q2{Have running API?} Q2 -->|Yes| Q3{Active or passive?} Q2 -->|No| Traffic{Have traffic data?} Q3 -->|Active testing| Test[Use Test] Q3 -->|Passive analysis| Traffic Traffic -->|Yes| Analyze[Use Analyze] Traffic -->|No| Wait[Wait for traffic
or deploy API] Lint --> Consider1[Consider adding Test
to prevent drift] Test --> Consider2[Consider adding Analyze
for production monitoring] Analyze --> Consider3[Consider adding Lint
for design validation] ``` ### Common Patterns [Section titled “Common Patterns”](#common-patterns) **Pattern 1: Design + Implementation** ```typescript . type('static', 'test') // Catch drift between spec and code ``` **Pattern 2: Implementation + Production** ```typescript . type('test', 'analytics') // Validate both testing and live ``` **Pattern 3: Complete Coverage** ```typescript . type('static', 'test', 'analytics') // Validate everywhere ``` ## Next Steps [Section titled “Next Steps”](#next-steps) * Learn about [combining rule types](/guides/http-rules/combining-types/) for hybrid validation * Explore [how to use rules](/guides/http-rules/how-to-use-rules/) in your projects # Virtual Request Samples > Why the sampler generates every request in memory instead of writing them to disk, and what that buys you. To test an API, Thymian needs a request to send for every Transaction it is going to check — and Thymian checks the whole description, not a handful of happy paths. A `404`, a `403`, a `409`: each is a Transaction with its own expectations, and each needs a request that provokes it. That is a lot of requests. The sampler’s answer is to **never store any of them**. ## The projection [Section titled “The projection”](#the-projection) On every run, the sampler reads your API description and produces one request per Transaction, in memory: * path and method from the operation * path, query, header and cookie parameters from their schemas * a body from the request schema, using the examples your description gives * an `authorize` flag from the operation’s `security` requirement, forced off for a Transaction whose declared status is `401` Nothing is written. Ask for one and you get it freshly generated: ```bash npx thymian sampler show 'GET /launches -> 200 (application/json)' ``` The consequence worth internalizing There is no artifact, so there is nothing that can disagree with your API description. A stale request sample is not a bug that got fixed — it is a state that no longer exists. ## What that removes [Section titled “What that removes”](#what-that-removes) The sampler used to materialize a tree of `request.json` files mirroring your API. It worked, and it had one structural cost: **every one of those files was a copy of something the description already said.** Any edit to the description stranded the copies, and every problem downstream of that was a workaround for the copy existing at all. | Used to happen | Now | | ---------------------------------------------------------------- | ---------------------------------------------------------- | | A renamed path stranded its generated files | Nothing to strand | | A removed operation left its files behind forever | Nothing to leave behind | | `validate` could report a stale file but not fix it | Nothing to be stale | | Upgrading Thymian broke byte-exact validation of generated files | The committed surface is types, compared canonicalized | | A hook was orphaned at a dead directory, silently | A hook is anchored by Selector, and a dead one is an error | And a smaller thing that matters for adoption: **trying Thymian costs nothing.** `thymian test` works in a clean checkout, with no setup step and nothing to commit. ## What you own instead [Section titled “What you own instead”](#what-you-own-instead) Generation is deterministic — the same description always produces the same request — which means the only thing worth keeping is the part generation cannot know: your intent. That lives in [hooks](/guides/hooks/write-hooks/): TypeScript functions anchored to a [Selector](/references/plugins/sampler/hooks-api/#selectors), the address of exactly one Transaction. A hook is the only sampler artifact you write, and the only one you commit besides the [generated types](/concepts/generated-types/). ## Where the deterministic request is not enough [Section titled “Where the deterministic request is not enough”](#where-the-deterministic-request-is-not-enough) A generated request satisfies the schema; it does not satisfy your database. A generated `id` matches no real row, so `GET /launches/{id} -> 200` gets a `404` until something creates one. That gap is exactly what hooks are for — and being explicit about it is better than the alternative, where a committed `request.json` held an `id` that happened to work on somebody’s machine in March. ```typescript export const seed = beforeEach( 'GET /launches/{id} -> 200 (application/json)', async (request, ctx, utils) => { const created = await utils.request( 'POST /launches (application/json) -> 201 (application/json)', { body: { missionName: 'Artemis II' } }, { authorize: true }, ); utils.setPathParam('id', created.body.id); }, ); ``` ## Further reading [Section titled “Further reading”](#further-reading) * [How the hook system works](/concepts/hook-system/) * [What are the files under `generated/`?](/concepts/generated-types/) * [Testing an API with the Sampler](/tutorials/sampler-getting-started/) # What is an HTTP Rule? > Understanding HTTP rules and validation contexts An HTTP rule in Thymian applies the familiar concept of linting—like ESLint for JavaScript—to HTTP APIs. Rules validate that your API follows HTTP specifications, industry best practices, or your organization’s guidelines. ## Rules vs. Traditional API Validation [Section titled “Rules vs. Traditional API Validation”](#rules-vs-traditional-api-validation) Unlike tools that only validate static OpenAPI documents (like Spectral), Thymian rules work across three contexts: * **Lint** — Checks API specifications before implementation * **Test** — Actively tests running API endpoints * **Analyze** — Validates recorded HTTP transactions This multi-context approach prevents API drift and ensures governance throughout the development lifecycle. ## Rule Anatomy [Section titled “Rule Anatomy”](#rule-anatomy) Every HTTP rule consists of several components: ```typescript httpRule('rule-name') .severity('error') // How critical is this rule? .type('static', 'test') // Where does it run? .description('...') // What does it validate? .appliesTo('server') // Who must comply? .rule((ctx) => { ... }) // How to validate? .done(); ``` ### Core Components [Section titled “Core Components”](#core-components) | Component | Purpose | Example | | ----------------------- | ---------------------------------- | -------------------------------------------------------- | | **Name** | Unique identifier | `'no-body-in-get-requests'` | | **Severity** | Impact level | `'error'`, `'warn'`, `'hint'` | | **Type** | Validation contexts | `'static'`, `'analytics'`, `'test'`, `'informational'` | | **Description** | What the rule validates | `'GET requests should not include a body'` | | **Applies To** | Target participant | `'client'`, `'server'`, `'proxy'` | | **Applicability** | Which transactions it speaks about | `appliesTo`: an expression | | **Violation Condition** | What is wrong with them | `violatedWhen`: a filter expression or a custom function | | **URL** (optional) | Reference documentation | RFC section or internal docs | ## The Three Validation Contexts [Section titled “The Three Validation Contexts”](#the-three-validation-contexts) Thymian provides three distinct contexts for running rules, each suited for different development stages: ``` flowchart TB subgraph Design["📐 Design Phase"] A[OpenAPI Spec] --> B[Lint Context] B --> C[Validates Spec Definitions] end subgraph Development["💻 Development Phase"] D[Running API] --> E[Test Context] E --> F[Generates & Runs Tests] end subgraph Production["🔍 Production Phase"] G[Recorded Traffic] --> H[Analyze Context] H --> I[Analyzes Transactions] end C --> J[Reports] F --> J I --> J ``` ### Lint Context [Section titled “Lint Context”](#lint-context) **What it validates:** API specifications (e.g. OpenAPI documents) **When to use:** During design and development to catch issues early **Advantages:** * Very fast (no network calls) * Immediate feedback during development * Prevents issues before implementation **Example:** ```typescript // Validates that paginated responses define Link header in the spec . type('static') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: and(statusCode(200), path('/api/*/list')), violatedWhen: not(responseHeader('link')), }) ) ``` ### Test Context [Section titled “Test Context”](#test-context) **What it validates:** Live API endpoints **When to use:** During integration testing and CI/CD pipelines **Advantages:** * Tests actual implementation * Discovers runtime issues * Validates server behavior **Example:** ```typescript // Actively tests that servers support both GET and HEAD methods . type('test') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: method('GET'), violatedWhen: statusCode(501), // Not Implemented }) ) ``` ### Analyze Context [Section titled “Analyze Context”](#analyze-context) **What it validates:** Recorded HTTP transactions **When to use:** For production monitoring and traffic analysis **Advantages:** * Validates real-world usage * No impact on running services * Discovers issues from actual traffic **Example:** ```typescript // Analyzes recorded traffic for missing authentication headers . type('analytics') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(401), violatedWhen: not(responseHeader('www-authenticate')), }) ) ``` ## Comparison Table [Section titled “Comparison Table”](#comparison-table) | Feature | Lint | Test | Analyze | | ----------------- | --------------------- | --------------------- | ----------------- | | **Speed** | ⚡ Very Fast | ⏱️ Variable\* | ⚡ Fast | | **Data Source** | OpenAPI spec | Live API | Recorded traffic | | **Network Calls** | No | Yes | No | | **When to Run** | Design & dev | CI/CD & dev | Production & QA | | **Catches** | Spec issues | Implementation issues | Real-world issues | | **Coverage** | All defined endpoints | Tested endpoints | Used endpoints | *\*Test speed depends on API endpoint performance and network conditions* ## The Common Interface [Section titled “The Common Interface”](#the-common-interface) To simplify rule development, Thymian provides a **common interface** that works across all three contexts: ```typescript httpRule('works-everywhere') .type('static', 'analytics', 'test') // Same rule, three contexts .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(500), violatedWhen: not(responseHeader('content-type')) })) .done(); ``` This rule automatically: * Validates spec definitions in **lint** mode * Tests live endpoints in **test** mode * Analyzes recorded traffic in **analyze** mode You write the logic once, and Thymian adapts it to each context. ## Rule Metadata [Section titled “Rule Metadata”](#rule-metadata) Rules can include additional metadata for documentation and organization: ```typescript httpRule('comprehensive-rule').severity('error').type('static', 'analytics').url('https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.1').description('500 responses must include Content-Type header').summary('Missing Content-Type on 500 errors').tags('errors', 'headers', 'http-semantics').appliesTo('server', 'proxy').done(); ``` **Use cases for metadata:** * `url` — Link to specifications or internal documentation * `summary` — Short description for search and listings * `tags` — Categorize rules for filtering * `appliesTo` — Clarify who must follow the rule ## Severity Levels [Section titled “Severity Levels”](#severity-levels) Rules have three severity levels that indicate their importance: | Severity | When to Use | Example | | --------- | ------------------------------------------ | ------------------------------------------------ | | **error** | Must comply (MUST/MUST NOT in specs) | `'401 must include WWW-Authenticate'` | | **warn** | Should comply (SHOULD/SHOULD NOT in specs) | `'POST should return 201 for created resources'` | | **hint** | Optional (MAY in specs) | `'Consider adding Cache-Control headers'` | > **Note:** Rules that describe requirements which cannot be tested automatically can use the `'informational'` **rule type** (see [The Three Validation Contexts](#the-three-validation-contexts)). This is separate from the severity level. ```typescript // Critical requirement . severity('error') // Recommended practice .severity('warn') // Optional suggestion .severity('hint') // Informational rule .severity('informational') ``` ## Preventing API Drift [Section titled “Preventing API Drift”](#preventing-api-drift) One of the most powerful features is using the same rule in multiple contexts: ```typescript httpRule('consistent-error-responses') .severity('error') .type('static', 'test') // Both design and implementation .description('Error responses must include problem details') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCodeRange(400, 599), violatedWhen: not(responseMediaType('application/problem+json')) })) .done(); ``` This rule ensures: 1. Your **spec** defines proper error responses (lint) 2. Your **implementation** actually returns them (test) If they diverge, violations are reported in one context or the other. ## Next Steps [Section titled “Next Steps”](#next-steps) Now that you understand what HTTP rules are and how they work: * Learn [how to create your own rules](/guides/http-rules/creating-new-rules/) * Explore [rule types in depth](/concepts/rule-types/) * Discover [hybrid rule patterns](/guides/http-rules/combining-types/) # Official Support > We built Thymian to solve the hardest problems in HTTP conformance and API governance. Now we bring that expertise directly to your organization — from specification to production. ## The Challenge at Scale HTTP conformance issues rarely surface during development. They emerge in production — as incidents, not as test failures. 🌐 ### Infrastructure Blind Spots Between your application and its consumers sits a chain of intermediaries — CDNs, load balancers, reverse proxies — each capable of silently altering HTTP semantics. Header rewrites, stripped encodings, and injected metadata go unnoticed by conventional testing yet directly affect reliability and security. 📡 ### Specification Drift As organizations scale, the gap between API specifications and actual protocol behavior widens. Without systematic enforcement at the HTTP layer, inconsistencies accumulate across teams and services, eroding interoperability and increasing integration costs. 🕳️ ### Undetected Conformance Gaps Functional and integration tests verify business logic — not protocol correctness. Violations of HTTP semantics such as incorrect status codes, missing headers, or improper content negotiation pass through every stage of the pipeline until they manifest as production incidents. ### Protocol Violations with Real-World Consequences 🔓 #### Request Smuggling via Content-Length 🔴 Critical When a Content-Length header does not accurately reflect the message body size, intermediaries may interpret message boundaries differently. This class of desynchronization vulnerability has been exploited in documented attacks against major platforms, enabling unauthorized access to other users' sessions. ImpactData exfiltration, authentication bypass, cache poisoning 🔀 #### Cache Poisoning via Missing Vary 🟠 High An omitted Vary header allows caching layers to serve a single cached representation regardless of request context — such as authentication state or content preferences. The result is cross-user data exposure through a mechanism that is invisible to application-level monitoring. ImpactCross-user data exposure, session leakage, regulatory non-compliance ⚠️ #### Incorrect Status Codes Breaking Client Logic 🟠 High APIs that return 200 OK for failed operations or use 400 Bad Request indiscriminately prevent clients from implementing proper error handling, retry logic, and circuit breakers. Mobile apps, SDKs, and partner integrations silently degrade because they cannot distinguish transient failures from permanent errors. ImpactBroken retry logic, poor client experience, cascading failures across integrations 💸 #### Redundant Data Transfer Inflating Cloud Costs 🟠 High APIs that omit ETag or Last-Modified headers — or ignore conditional requests like If-None-Match and If-Modified-Since — force full response payloads on every request, even when the data has not changed. At scale, this multiplies bandwidth consumption, increases compute time for serialization, and drives up egress charges across CDNs and cloud regions. ImpactExcessive egress and compute costs, degraded response times, wasted infrastructure capacity ### Built on Deep Protocol Expertise We developed Thymian's RFC 9110 rule set from the ground up, systematically mapping every**MUST**, **SHOULD**, and **MAY**requirement to automated, actionable validation. The same rules apply consistently across design-time analysis, testing, and production monitoring. This level of protocol expertise is what we bring to your organization. 399RFC rules implemented 15RFC sections covered 3Validation contexts (lint, test, analyze) [Get in Touch](mailto:support@thymian.dev?subject=Enterprise%20Consultation) ## Consulting Services Hands-on guidance from the engineers who built Thymian — tailored to your architecture, your stack, and your standards. 🔍 ### HTTP Conformance Auditing Deep conformance audits against RFC 9110 that uncover protocol violations in your specifications, implementations, and live traffic. You receive a prioritized findings report with RFC references, risk assessments, and remediation paths. 📋 ### API Governance & Organizational Standards Shareable rule sets packaged as npm modules — your own @your-org/api-standards — that validate at design time, catch issues before merge, and monitor production. You receive a ready-to-publish package and rollout plan. 📐 ### API Design Strategy We work with your architects to establish conformant-by-construction design patterns — versioning, error responses, content negotiation, and caching aligned with HTTP semantics. You receive design guidelines, OpenAPI templates, and automated checks. 🌐 ### Production Traffic Analysis We audit recorded HTTP traffic to uncover how proxies, CDNs, and load balancers alter semantics in ways your tests never see. You receive an analysis report with infrastructure-specific recommendations. 🤖 ### AI-Assisted API Development Automated conformance checks integrated into your AI-assisted development pipelines, so generated code is validated against RFC requirements before it ships. You receive a configured pipeline and integration guides. 🔄 ### Lifecycle & CI/CD Integration Conformance checks at every stage: IDE validation, automated PR checks, and production monitoring. We configure your CI/CD platform and establish feedback loops that prevent regressions. You receive pipeline configs and a failure runbook. 🔧 ### Custom Plugin & Rule Development Custom plugins and rules for proprietary protocols, internal conventions, or domain-specific constraints. Need non-TypeScript integration? We build remote plugins via WebSocket. You receive production-ready code and test suites. 🎓 ### Team Enablement Hands-on workshops covering HTTP semantics, custom rule authoring, plugin development, and traffic analysis. You receive recordings, training materials tailored to your architecture, and a long-term reference guide. ### Not Sure Where to Start? Most engagements begin with a short call. Walk us through your API landscape and pain points — we’ll tell you which services make sense and which ones you can skip. No pitch deck, no commitment. [Schedule a Consultation](mailto:support@thymian.dev?subject=Enterprise%20Consultation) ## What to Expect Every engagement follows a straightforward process — so you know exactly what you’re getting before you commit. 1 ### Discovery Call A 30-minute conversation to understand your API landscape, pain points, and goals. No pitch deck, no commitment. Free & no obligation 2 ### Scoped Proposal Based on the discovery call, we deliver a written proposal with scope, timeline, deliverables, and pricing — typically within one week. Fixed scope & timeline 3 ### Engagement We execute the agreed scope, deliver all artifacts, and conduct a handoff session to ensure your team can maintain everything independently. Deliverables you own ## Who’s Behind Thymian Every engagement is led directly by the engineers who designed and built Thymian — not outsourced, not delegated. ![](/team/matthias-keckl.webp) ### Matthias Keckl Core Member [](https://www.linkedin.com/in/matthias-keckl/) Drives research initiatives and engineering excellence. Bridges academic rigor with practical consulting to shape Thymian’s rule engine and validation approach. ![](/team/peter-muller.webp) ### Peter Müller Core Member [](https://www.linkedin.com/in/muellerpeter/) Leads community engagement and core engineering. Focuses on developer experience, open-source collaboration, and helping teams adopt API conformance practices. ![](/team/markus-ende.webp) ### Markus Ende Core Member [](https://www.linkedin.com/in/markus-ende/) Seasoned engineer and consultant specializing in API quality. Brings deep expertise in HTTP standards and hands-on experience guiding enterprise integrations. ![](/team/andreas-tennert.webp) ### Andreas Tennert Core Member [](https://www.linkedin.com/in/andreas-tennert/) Architects the plugin system and tooling integrations. Ensures Thymian fits seamlessly into existing CI/CD pipelines and developer workflows. ### Work Directly With the Core Team * **Prevention** — We designed the rule engine and know exactly how to integrate it into your CI pipeline to catch conformance issues before they ship. * **API Governance** — As the architects of the plugin system, we help you build custom rule sets and shared policies tailored to your organization. * **Live Incidents** — When production breaks, the people who wrote the analyzer and understand HTTP at the RFC level are the fastest path to a root cause. * **After the Engagement** — You keep everything: documented rule sets, pipeline configurations, training materials, and the knowledge to maintain it all independently. Optional ongoing support is available if you need it. [Schedule a Consultation](mailto:support@thymian.dev?subject=Enterprise%20Consultation) Thymian is built and maintained by[qupaya technologies GmbH](https://qupaya.com)— based in Nuremberg, Germany. ## Latest activity * Paper ### [WEBIST: Research Paper](/events/) Angers, FranceOctober 27, 2026 View event * Recorded Talk ### [Should I GET or Should I POST — The Clash With HTTP Conformance](/resources/) [Guest of My Coding Zone on YouTube](https://www.youtube.com/watch?v=1IvUSEnGkZ8)View resource # How to make Requests in Hooks > Use utils.request to seed data across endpoints, addressed by Selector, with the target's own hooks running. Some Transactions need a resource that already exists. `utils.request` calls another Transaction to create one. A request a hook makes this way is a **Seed**. ## Addressing the call [Section titled “Addressing the call”](#addressing-the-call) `utils.request` is keyed by **Selector** — the same strings your hooks are anchored to — and resolved through the same catalog, so the keys your editor completes and the strings resolved at run time are the same strings by construction. ```typescript utils.request( selector, // the Transaction to initiate args?, // body, headers, query, path, cookies — overlaid on the generated request options?, // runHooks, authorize ); ``` There is no `forStatusCode`: the Selector already carries the status you asked for. ## The Selector says what you asked for, not what you get [Section titled “The Selector says what you asked for, not what you get”](#the-selector-says-what-you-asked-for-not-what-you-get) A Selector names the Transaction you want to **initiate**. Whether it happens is the server’s decision, so the answer is a **union of every response the operation declares** — narrowed by `statusCode`, and by `mediaType` where one status declares several. ```typescript import { beforeEach } from '@thymian/hooks'; export const seedLaunch = beforeEach( 'GET /launches/{id} -> 200 (application/json)', async (request, ctx, utils) => { const created = await utils.request( 'POST /launches (application/json) -> 201 (application/json)', { body: { missionName: 'Apollo 11' } }, { authorize: true }, ); if (created.statusCode !== 201) { utils.skip('could not create a launch to read back'); } // Narrowed to the 201 body, so `id` is checked — and a field that stops // existing in the description is a compile error here. utils.setPathParam('id', created.body.id); }, ); ``` Branching is how you get at a body: outside a narrowing check, `created.body` is the union of every declared body, and only what they all have is readable. Two ways to end a transaction deliberately `utils.skip(message)` says *this Transaction could not be executed as described* — which is what a Seed answering differently usually means. `utils.fail(message)` says *it ran and the result is wrong*. Both are control flow, not errors; see [Skip and fail tests](/guides/hooks/skip-and-fail-tests/). ## Handling nothing is allowed [Section titled “Handling nothing is allowed”](#handling-nothing-is-allowed) A hook that ignores the response still compiles and still runs. Nothing crashes, and nothing is silently wrong either — the run keeps its own account of what happened: * If the Seed was answered with **another declared status**, the value comes back truthfully and your hook carries on with it. If the Transaction then cannot be executed — usually because the id you needed was never created — `thymian sampler check` reports it as **skipped** and names the Seed and the status it actually got. * If the Seed was answered with a status the description **never declares**, `utils.request` throws `UndeclaredResponseError` — there is no union member for it to be. You may catch it; if you do not, the Transaction is skipped, again naming the Seed, and the run continues. ```plaintext ↷ GET /launches/{id} -> 200 (application/json) The seed "POST /launches (application/json) -> 201 (application/json)" was answered with 500, which the specification does not declare for that operation. Branch on the seed's status code, or call utils.skip to end this transaction deliberately. ``` To react to an off-spec answer yourself, catch it: ```typescript import { beforeEach, UndeclaredResponseError } from '@thymian/hooks'; export const seedLaunch = beforeEach( 'GET /launches/{id} -> 200 (application/json)', async (request, ctx, utils) => { try { const created = await utils.request( 'POST /launches (application/json) -> 201 (application/json)', { body: { missionName: 'Apollo 11' } }, ); utils.setPathParam('id', created.body.id); } catch (error) { if (!(error instanceof UndeclaredResponseError)) { throw error; } utils.skip(`the API answered ${error.statusCode} while seeding`); } }, ); ``` `UndeclaredResponseError` carries `selector`, `statusCode`, `headers` and the parsed `body`, so an off-spec answer is inspectable rather than merely fatal. ## Arguments overlay the generated request [Section titled “Arguments overlay the generated request”](#arguments-overlay-the-generated-request) The generated sample plus the target’s own hooks already produce a complete request. `args` is an **overlay** on it, so you write only what has to differ: * **Parameter groups merge per key.** A header you set replaces that header; every other header the description produced survives. * **The body deep-merges.** Objects recurse, arrays and primitives replace whole, and `null` is a value rather than an absence. ```typescript // The generated body is { missionName: 'Generated', crew: ['A', 'B'], pad: { id: 1, name: 'LC-39A' } } await utils.request(selector, { body: { crew: ['Ada'], pad: { name: 'SLC-40' } }, }); // Sent: { missionName: 'Generated', crew: ['Ada'], pad: { id: 1, name: 'SLC-40' } } ``` Everything is optional, so `utils.request(selector)` sends the generated request unchanged. ## The target’s own hooks run [Section titled “The target’s own hooks run”](#the-targets-own-hooks-run) By default a nested request runs the target’s own `beforeEach → authorize → afterEach` pipeline, so seeding behaves like the real run rather than like a second, quieter client. Your `authorize` hook applies to it; so does any `beforeEach` that shapes it. It is also sent to the origin the run is talking to, so a `--target-url` covers your Seeds too. ```typescript await utils.request(selector, args, { runHooks: false }); ``` `runHooks: false` sends the generated request as-is. Use it when you want the raw request, and when you need to break a cycle. Authorization is per Transaction A nested request carries **its own** target’s `authorize` flag, not the caller’s. Seeding a secured endpoint from an unsecured one needs `{ authorize: true }`; and forcing it on still requires a registered `authorize` hook to supply the credentials. ## Cycles fail loudly [Section titled “Cycles fail loudly”](#cycles-fail-loudly) Two hooks that seed each other would recurse until the stack gave out. Instead the sampler refuses, and prints the chain: ```plaintext A cross-endpoint request would re-enter "GET /launches/{id} -> 200 (application/json)", which is already running. Try this: * The chain is: "GET /launches/{id} -> 200 (application/json)" → "POST /launches (application/json) -> 201 (application/json)" → "GET /launches/{id} -> 200 (application/json)" * Break the cycle, or pass `{ runHooks: false }` to seed from the raw request without running the target's hooks. ``` The chain is printed because `A → B → A` is usually a mistake three files apart, and the middle of it is where the fix goes. Nothing is sent before the refusal. ## Seeding once for the whole run [Section titled “Seeding once for the whole run”](#seeding-once-for-the-whole-run) If one resource is shared by every Transaction, `beforeAll` is cheaper than seeding per request — and its returned closure is how that resource gets cleaned up. ```typescript import { beforeAll } from '@thymian/hooks'; export const seedOnce = beforeAll(async (utils) => { const created = await utils.request( 'POST /launches (application/json) -> 201 (application/json)', { body: { missionName: 'Shared launch' } }, { authorize: true }, ); if (created.statusCode !== 201) { // A throw in `beforeAll` ends the run: without this resource, nothing that // depends on it can be executed as described. throw new Error('the shared launch could not be created'); } const id = created.body.id; return async () => { await utils.request( 'DELETE /launches/{id} -> 204', { path: { id } }, { authorize: true }, ); }; }); ``` # How to skip or fail Tests in Hooks > Skipping and failing tests in hooks Problem You need to skip tests under certain conditions or explicitly fail tests when requirements aren’t met. Solution Use `utils.skip()` and `utils.fail()` in your hooks. ## Skipping Tests [Section titled “Skipping Tests”](#skipping-tests) Use `utils.skip()` when a test cannot run due to missing preconditions. ### Reason to Skip [Section titled “Reason to Skip”](#reason-to-skip) * Required resources cannot be created * Prerequisites are not met * Test is not applicable for certain conditions ### Basic Usage [Section titled “Basic Usage”](#basic-usage) ```typescript import { beforeEach } from '@thymian/hooks'; export const seed = beforeEach( 'GET /launches/{id} -> 200 (application/json)', async (request, ctx, utils) => { const response = await utils.request( 'POST /launches (application/json) -> 201 (application/json)', { body: { missionName: utils.randomString(10), launchDate: '2026-12-31T00:00:00Z', rocketType: 'Falcon9', }, headers: { 'content-type': 'application/json' }, }, ); // Skip if resource creation failed if (response.statusCode !== 201) { utils.skip('Cannot create required launch resource'); } request.pathParameters.id = response.body.id; }, ); ``` ### Skip with Details [Section titled “Skip with Details”](#skip-with-details) Provide detailed information about why the test was skipped: ```typescript if (response.statusCode === 503) { utils.skip( `Service unavailable (status ${response.statusCode}). Cannot proceed with test.`, ); } ``` ## Failing Tests [Section titled “Failing Tests”](#failing-tests) Use `utils.fail()` when a test should explicitly fail due to invalid conditions. ### When to Fail [Section titled “When to Fail”](#when-to-fail) * Setup produces unexpected results * Resources are in an invalid state * Critical preconditions are violated * Data validation fails ### Basic Usage [Section titled “Basic Usage”](#basic-usage-1) ```typescript import { beforeEach } from '@thymian/hooks'; export const seed = beforeEach( 'GET /launches/{id} -> 200 (application/json)', async (request, ctx, utils) => { const response = await utils.request( 'POST /launches (application/json) -> 201 (application/json)', { body: { missionName: utils.randomString(10), launchDate: '2026-12-31T00:00:00Z', rocketType: 'Falcon9', }, headers: { 'content-type': 'application/json' }, }, ); // Fail if creation returned a status other than the one declared here. // Branch on the declared status and narrow before touching `body` — the // type of `response` is a union of every response the operation // declares, so a status that operation never declares (like `200` for // an endpoint that only declares `201`) is a compile error, and `body` // is only the 201 shape once the branch has narrowed to it. if (response.statusCode !== 201) { utils.fail(`Expected 201 Created, got ${response.statusCode}`); } else if (!response.body.id) { utils.fail('Created launch has no ID field'); } else { request.pathParameters.id = response.body.id; } }, ); ``` ## What Happens When You Skip or Fail? [Section titled “What Happens When You Skip or Fail?”](#what-happens-when-you-skip-or-fail) ### Skip [Section titled “Skip”](#skip) ```typescript utils.skip('Resource unavailable'); ``` **Result:** * Test is marked as **skipped** * Remaining hooks do not execute * Test does not count as failure * Test report shows skip reason **Output:** ```plaintext GET /launches/123 ⊘ Skipped: Resource unavailable ``` ### Fail [Section titled “Fail”](#fail) ```typescript utils.fail('Invalid response structure'); ``` **Result:** * Test is marked as **failed** * Remaining hooks do not execute * Test counts as failure in report * Test report shows failure reason **Output:** ```plaintext GET /launches/123 ✗ Failed: Invalid response structure ``` ## Technical Details [Section titled “Technical Details”](#technical-details) Both `skip()` and `fail()` throw special errors that are caught by the hook runner: ```typescript // These throw errors internally utils.skip('message'); // Throws SkipError utils.fail('message'); // Throws FailError ``` Note These methods never return. TypeScript recognizes them as `never` return type. ## Alternatives [Section titled “Alternatives”](#alternatives) ### Narrowing the target instead [Section titled “Narrowing the target instead”](#narrowing-the-target-instead) ```typescript export const seed = beforeEach( { path: '/launches/{id}', statusClass: '2XX' }, async (request, ctx, utils) => { // Only the successful cases need a resource to exist. }, ); ``` Narrowing the target is almost always better than branching inside the hook: the hook simply never runs where it does not apply, and a filter value that stops existing is a compile error, whereas a branch is just code that quietly stops matching. ### Using Assertions [Section titled “Using Assertions”](#using-assertions) For validation in afterEach hooks, consider using `utils.assertionFailure()` instead of `utils.fail()`: ```typescript export const assertShape = afterEach( 'GET /launches/{id} -> 200 (application/json)', (response, ctx, utils) => { const body = JSON.parse(response.body ?? '{}'); if (!body.id) { utils.assertionFailure('Missing ID field', { expected: 'id field present', actual: 'id field missing', }); } }, ); ``` **Difference:** `assertionFailure()` records a failed assertion but allows other validations to continue. `fail()` stops execution immediately. # How to write a Hook > Guide to writing selector-anchored hooks for the sampler plugin A hook is a TypeScript function you own, anchored to the Transaction — or the set of Transactions — it applies to. ## Where hooks live [Section titled “Where hooks live”](#where-hooks-live) ```bash mkdir -p .thymian/sampler/hooks ``` Any file under `hooks/` is scanned, at any depth, named anything you like: * .thymian/sampler/hooks/ * auth.ts * launches/ * seed.ts * assertions.ts The layout carries no meaning — it is for your benefit. What decides where a hook applies is its **target**, in the code. ## State is shared within a run [Section titled “State is shared within a run”](#state-is-shared-within-a-run) Hook files are evaluated once per scan, and so is every module they import: a module two hook files share is one instance, and a value one hook writes to it is the value another reads. ```typescript // .thymian/sampler/hooks/state.ts — one copy, shared by every hook file export const fixtures: { userId?: number } = {}; ``` So a `beforeAll` in `auth.ts` that writes `fixtures.userId` hands it to a `beforeEach` in `seed.ts` that reads it — provided the `beforeAll` has run, which the [lifecycle](/concepts/hook-system/#order) guarantees before any request-scoped hook. What the plugin does **not** carry over is the process: `thymian serve` re-evaluates the hook files, and the modules they import, on every scan, so state is one run’s and never the next’s. Prefer [`utils.request`](/guides/hooks/make-requests-in-hooks/) for anything that has to exist on the server; keep module state for what only the hooks need. No setup needed `@thymian/hooks` resolves through the sampler plugin itself, so a hook file runs with nothing generated and nothing installed beside it. Run [`thymian sampler init`](/references/plugins/sampler/hooks-api/#thymian-sampler-init) when you want your editor to complete Selectors and the compiler to check them. ## The six hook kinds [Section titled “The six hook kinds”](#the-six-hook-kinds) | Kind | Runs | Use it for | | -------------- | --------------------------------------- | ------------------------------------------- | | `defineSample` | when the request is generated | bodies, fixture files, the `authorize` flag | | `beforeEach` | before each request | headers, path parameters, seeding | | `afterEach` | after each response | assertions, observations | | `authorize` | before a request that wants credentials | credentials, and nothing else | | `beforeAll` | once, before the first request | run-wide setup | | `afterAll` | once, when the run closes | run-wide teardown | Every hook **returns nothing and mutates in place**, and every registration is an export: ```typescript import { beforeEach } from '@thymian/hooks'; export const addTraceId = beforeEach( 'GET /launches -> 200 (application/json)', (request) => { request.headers['x-trace-id'] = 'abc'; }, ); ``` Export it A registration you build but never export does nothing. Discovery is export-based, so `beforeEach(sel, fn);` on its own line is silently inert. ## Choosing a target [Section titled “Choosing a target”](#choosing-a-target) ### One Transaction [Section titled “One Transaction”](#one-transaction) Pass its Selector. This is the tightest and the most precisely typed option — inside the callback, the request and response types are that Transaction’s. ```typescript export const shape = beforeEach( 'POST /launches (application/json) -> 201 (application/json)', (request) => { request.body = { missionName: 'Artemis II' }; }, ); ``` ### A few Transactions [Section titled “A few Transactions”](#a-few-transactions) Pass a list of Selectors. ```typescript export const shared = beforeEach( [ 'GET /launches -> 200 (application/json)', 'GET /astronauts -> 200 (application/json)', ], (request) => { request.headers['x-shared'] = 'yes'; }, ); ``` ### A set of Transactions [Section titled “A set of Transactions”](#a-set-of-transactions) Pass a filter. Fields AND-combine, a list within a field OR-combines, and `not` excludes. ```typescript export const negativeCases = afterEach( { statusClass: '4XX', not: { status: 401 } }, (response, ctx, utils) => { utils.info(`negative case responded ${response.statusCode}`); }, ); ``` ### A whole subtree [Section titled “A whole subtree”](#a-whole-subtree) Use a path glob. `*` is exactly one segment; a trailing `**` is one or more. ```typescript export const admin = beforeEach({ path: '/admin/**' }, (request) => { request.headers['x-admin'] = 'yes'; }); ``` `/admin/**` covers everything *under* `/admin`, including paths added to your API description later — but not `/admin` itself. See the [filter reference](/references/plugins/sampler/hooks-api/#filters) for every field and the full glob grammar. ## Finding a Selector [Section titled “Finding a Selector”](#finding-a-selector) ```bash npx thymian sampler show 'GET /launches -> 200 (application/json)' ``` It prints the request that Transaction would send. Get the Selector slightly wrong and it prints the nearest matches, which is the fastest way to find the exact spelling. ## Composing several hooks [Section titled “Composing several hooks”](#composing-several-hooks) Several hooks of the same kind on one Transaction all run, in registration order: file order on the outside, and source order within each file. `defineSample` is the exception — at most one per Transaction, and a second is reported as a conflict when the hooks load. ## When a hook stops matching [Section titled “When a hook stops matching”](#when-a-hook-stops-matching) A Selector that names nothing, a glob that matches nothing, and a filter whose values intersect nothing all **fail the run**, naming the hook file and the export: ```plaintext 1 sampler hook does not resolve against the loaded API description. Try this: * seed.ts (export "seedAstronaut"): beforeEach targets the selector "GET /astronauts/{id} -> 200 (application/json)", which names no transaction in the loaded API description * Did you mean one of these selectors? * "GET /astronauts/{astronautId} -> 200 (application/json)" ``` A hook is never quietly re-bound to a nearby Transaction. That is the point of anchoring to a fully-qualified Selector. ## Next [Section titled “Next”](#next) * [Make requests inside hooks](/guides/hooks/make-requests-in-hooks/) * [Skip and fail test cases](/guides/hooks/skip-and-fail-tests/) * [Hooks API Reference](/references/plugins/sampler/hooks-api/) # Combining Different Rule Types > Writing hybrid rules that work across multiple contexts One of the most powerful features of the HTTP linter is the ability to write a single rule that works across multiple validation contexts. This prevents API drift and ensures consistent governance throughout the development lifecycle. ## The Common Interface [Section titled “The Common Interface”](#the-common-interface) The **common interface** allows you to write validation logic once and have it automatically work in lint, test, and analyze contexts: ```typescript import { httpRule, statusCode, not, responseHeader } from '@thymian/core'; export default httpRule('hybrid-rule') .severity('error') .type('static', 'analytics', 'test') // All three contexts .description('401 responses must include WWW-Authenticate header') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(401), violatedWhen: not(responseHeader('www-authenticate')) })) .done(); ``` This single rule: * **Lint** — Validates spec definitions * **Test** — Tests live endpoints * **Analyze** — Analyzes recorded traffic ## How It Works [Section titled “How It Works”](#how-it-works) ``` flowchart LR A[Rule Definition
Common Interface] --> B{Context Type} B -->|static| C[LintContext] B -->|test| D[TestContext] B -->|analytics| E[AnalyzeContext] C --> F[Reads ThymianFormat
Validates Spec] D --> G[Generates Tests
Executes Requests] E --> H[Queries Database
Analyzes Traffic] F --> I[Reports] G --> I H --> I ``` The HTTP linter automatically adapts your rule logic to each context: 1. **In lint mode** — Extracts transactions from OpenAPI spec 2. **In test mode** — Generates and executes HTTP tests 3. **In analyze mode** — Queries database and validates transactions The two keys mean the same thing in every mode. `appliesTo` is the rule’s **Applicability**: which transactions it speaks about. `violatedWhen` is its **Violation Condition**: what is wrong with them, checked only on transactions `appliesTo` accepts. In `test`, `appliesTo` is checked twice: against the specification to choose which requests to send, then against the response that came back. A response it rejects is never a violation. A fact only live traffic carries, such as a `Content-Length` header, belongs in `violatedWhen`. ## When to Use Hybrid Rules [Section titled “When to Use Hybrid Rules”](#when-to-use-hybrid-rules) ### Use Case 1: Prevent API Drift [Section titled “Use Case 1: Prevent API Drift”](#use-case-1-prevent-api-drift) Validate that your implementation matches your specification: ```typescript import { httpRule } from '@thymian/core'; import { constant, not, requestHeader } from '@thymian/core'; export default httpRule('require-api-version-header') .severity('error') .type('static', 'test') // Spec AND implementation .description('All API requests must include X-API-Version header') .appliesTo('client') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: constant(true), violatedWhen: not(requestHeader('x-api-version')) })) .done(); ``` **Benefits:** * Catches spec issues during design (lint) * Catches implementation issues during testing (test) * Ensures spec and implementation stay in sync ### Use Case 2: Continuous Validation [Section titled “Use Case 2: Continuous Validation”](#use-case-2-continuous-validation) Validate behavior from testing through production: ```typescript import { httpRule } from '@thymian/core'; import { statusCodeRange, not, responseMediaType } from '@thymian/core'; export default httpRule('errors-use-problem-details') .severity('warn') .type('test', 'analytics') // Testing AND production .description('Error responses should use application/problem+json') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCodeRange(400, 599), violatedWhen: not(responseMediaType('application/problem+json')) })) .done(); ``` **Benefits:** * Validates during integration testing (test) * Monitors production traffic (analyze) * Catches regressions in either environment ### Use Case 3: Complete Coverage [Section titled “Use Case 3: Complete Coverage”](#use-case-3-complete-coverage) Validate at all stages of development: ```typescript import { httpRule } from '@thymian/core'; import { statusCode, not, responseHeader } from '@thymian/core'; export default httpRule('complete-coverage') .severity('error') .type('static', 'test', 'analytics') // Everywhere .description('500 responses must include Content-Type header') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(500), violatedWhen: not(responseHeader('content-type')) })) .done(); ``` **Benefits:** * Catches design issues (lint) * Validates implementation (test) * Monitors production (analyze) * Maximum confidence ## Context-Specific Overrides [Section titled “Context-Specific Overrides”](#context-specific-overrides) Sometimes the common interface isn’t sufficient and you need different logic for each context. Use override methods: ### Override Pattern [Section titled “Override Pattern”](#override-pattern) ```typescript export default httpRule('rule-with-overrides') .severity('error') .type('static', 'test', 'analytics') .description('Rule with context-specific logic') .appliesTo('server') // Common logic (optional when overriding) .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(401), violatedWhen: not(responseHeader('www-authenticate')) })) // Override for test context .overrideTest((ctx) => { // Custom test logic }) .done(); ``` ### When to Override [Section titled “When to Override”](#when-to-override) Override a specific context when: * ✅ Common interface is insufficient for that context * ✅ You need access to context-specific APIs * ✅ Performance optimization is needed * ✅ The logic fundamentally differs between contexts ### Example: Override Test Context [Section titled “Example: Override Test Context”](#example-override-test-context) ```typescript import { httpRule } from '@thymian/core'; import { and, method, not, responseHeader, statusCode } from '@thymian/core'; import { singleTestCase } from '@thymian/core'; export default httpRule('401-with-custom-test') .severity('error') .type('static', 'analytics', 'test') .description('401 responses must include WWW-Authenticate header') .appliesTo('server') // Common logic for static and analytics .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(401), violatedWhen: not(responseHeader('www-authenticate')) })) // Custom test logic .overrideTest((testContext) => testContext.httpTest( singleTestCase() .forTransactionsWith(and(not(method('HEAD')), statusCode(401))) .run({ authorize: false }) .expectForTransactions(responseHeader('www-authenticate')) .done(), ), ) .done(); ``` **Why override?** * Static and analyze only inspect responses that already exist, so the common logic is enough * Test has to provoke the 401 itself: `authorize: false` withholds credentials so the server actually answers with 401 * Selecting on `statusCode(401)` pins each test case to the transaction that declares the 401, so the harness compares the live response against 401 rather than against another documented status ## Common Hybrid Patterns [Section titled “Common Hybrid Patterns”](#common-hybrid-patterns) ### Pattern 1: Lint + Analyze [Section titled “Pattern 1: Lint + Analyze”](#pattern-1-lint--analyze) Perfect for validating design and production traffic without active testing: ```typescript .type('static', 'analytics') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: and(method('DELETE'), statusCodeRange(200, 299)), violatedWhen: not(or(statusCode(200), statusCode(204))), }) ) ``` **Use when:** * Design validation is important * Production monitoring is needed * Active testing is not feasible ### Pattern 2: Lint + Test [Section titled “Pattern 2: Lint + Test”](#pattern-2-lint--test) Ideal for preventing drift between specification and implementation: ```typescript .type('static', 'test') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: method('TRACE'), violatedWhen: hasRequestBody(), }) ) ``` **Use when:** * Specification-first workflow * Integration testing is standard * Production monitoring is not needed ### Pattern 3: Test + Analytics [Section titled “Pattern 3: Test + Analytics”](#pattern-3-test--analytics) Best for runtime validation without spec checking: ```typescript .type('test', 'analytics') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: and(statusCode(200), requestHeader('if-none-match')), violatedWhen: not(responseHeader('etag')), }) ) ``` **Use when:** * No formal specification exists * Both testing and production validation are needed * Design-time checking is not relevant ### Pattern 4: All Three Contexts [Section titled “Pattern 4: All Three Contexts”](#pattern-4-all-three-contexts) Maximum coverage across all stages: ```typescript .type('static', 'test', 'analytics') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(201), violatedWhen: not(responseHeader('location')), }) ) ``` **Use when:** * Critical requirements need full coverage * API governance is paramount * Preventing issues at any stage is important ## Advanced: Grouped Validation [Section titled “Advanced: Grouped Validation”](#advanced-grouped-validation) For rules that compare multiple transactions, use grouped validation: ```typescript import { httpRule } from '@thymian/core'; import { and, or, method, statusCode, url, equalsIgnoreCase } from '@thymian/core'; export default httpRule('head-matches-get-headers') .severity('warn') .type('static', 'analytics', 'test') .description('HEAD and GET responses should have same headers') .appliesTo('server') .rule((ctx) => ctx.validateGroupedCommonHttpTransactions({ appliesTo: and(statusCode(200), or(method('GET'), method('HEAD'))), groupBy: url(), // Group by URL violatedWhen: (_, transactions) => { const getTransaction = transactions.find(([req]) => equalsIgnoreCase(req.method, 'get')); const headTransaction = transactions.find(([req]) => equalsIgnoreCase(req.method, 'head')); if (!getTransaction || !headTransaction) { return []; } const [, getResponse] = getTransaction; const [, headResponse, headLocation] = headTransaction; if (arraysEqual(getResponse.headers, headResponse.headers)) { return []; } return [ { location: headLocation, violation: { message: 'HEAD response headers differ from GET' }, findings: [], }, ]; }, }), ) .done(); function arraysEqual(a: string[], b: string[]): boolean { return a.length === b.length && a.every((val, idx) => val === b[idx]); } ``` `appliesTo` picks the transactions that are grouped. Only those reach `violatedWhen`, which is called once per group. This pattern works across all contexts because: * **Lint** — Groups transactions defined in spec * **Test** — Generates both GET and HEAD tests, then compares * **Analyze** — Groups recorded transactions by URL ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Start with Common Interface [Section titled “1. Start with Common Interface”](#1-start-with-common-interface) Always start with the common interface: ```typescript // ✅ Good: Try common interface first .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo, violatedWhen }) ) ``` Only override when necessary: ```typescript // ✅ Good: Override only when needed .rule((ctx) => { /* common logic */ }) .overrideTest((ctx) => { /* test-specific logic */ }) ``` ### 2. Use Type Combinations Thoughtfully [Section titled “2. Use Type Combinations Thoughtfully”](#2-use-type-combinations-thoughtfully) Choose type combinations based on your needs: ```typescript // ✅ Prevents drift .type('static', 'test') // ✅ Monitors testing and production .type('test', 'analytics') // ✅ Maximum coverage .type('static', 'test', 'analytics') // ❌ Redundant (choose based on need) .type('static') // When test or analytics would add value ``` ### 3. Keep Rules Focused [Section titled “3. Keep Rules Focused”](#3-keep-rules-focused) Each rule should validate one thing: ```typescript // ✅ Good: Focused validation httpRule('require-correlation-id') .type('static', 'analytics') .rule((ctx) => { /* single concern */ }); // ❌ Avoid: Multiple concerns in one rule httpRule('request-requirements') // Too broad .rule((ctx) => { /* validates multiple things */ }); ``` ## Next Steps [Section titled “Next Steps”](#next-steps) * See [how to use rules](/guides/http-rules/how-to-use-rules/) in your projects * Explore the [CLI tools](/references/cli/) for managing rules # Creating New Rules > Step-by-step guide to writing HTTP validation rules Creating HTTP validation rules in Thymian is straightforward. You can generate a rule scaffold using the CLI or write one from scratch. This guide walks you through both approaches. ## Quick Start with CLI [Section titled “Quick Start with CLI”](#quick-start-with-cli) The fastest way to create a new rule is using the interactive CLI generator: ```bash thymian generate rule ``` This command guides you through the rule creation process: 1. **Rule name** — Unique identifier for your rule 2. **Severity** — `error`, `warn`, or `hint` 3. **URL** — Link to documentation (optional) 4. **Description** — What the rule validates 5. **Rule types** — One or more of: `static`, `analytics`, `test`, `informational` 6. **Applies to** — Target participants: `client`, `server`, `proxy`, etc. The CLI generates a rule template that you can copy into your project: ```typescript import { httpRule } from '@thymian/core'; export default httpRule('your-rule-name').severity('error').type('static', 'analytics').description('Your rule description').appliesTo('server').done(); ``` Point the CLI at the generated `.ts` file and it runs — **no build, no bundler, no Node flags.** `thymian generate rule` only ever emits a file that loads: `.ts` by default, `.cjs` under `--cjs`, and **never** `.mts`/`.cts`. If `--output` names an extension that conflicts with the mode (e.g. `--cjs --output foo.js`), the command declines rather than silently rewriting your path. Note `erasableSyntaxOnly` is **not** required of your rule code. That restriction belongs to Node’s own type-stripping feature, which Thymian’s loader does not use — loading goes through [jiti](https://github.com/unjs/jiti) instead, so `enum`, `namespace`, parameter properties, and decorators all load normally. See [Loading Rules and Plugins](/references/loading-rules-and-plugins/) for the full loading contract. ## Writing a Rule from Scratch [Section titled “Writing a Rule from Scratch”](#writing-a-rule-from-scratch) ### Step 1: Set Up Imports [Section titled “Step 1: Set Up Imports”](#step-1-set-up-imports) Start by importing the necessary components: ```typescript import { httpRule, statusCode, not, responseHeader } from '@thymian/core'; ``` The `@thymian/core` package provides both the rule builder and filter expressions for matching HTTP transactions. ### Step 2: Define Rule Metadata [Section titled “Step 2: Define Rule Metadata”](#step-2-define-rule-metadata) Create the rule with its basic metadata: ```typescript export default httpRule('ensure-location-on-201').severity('error').type('static', 'analytics', 'test').description('201 Created responses must include a Location header').appliesTo('server'); ``` **Best practices:** * Use descriptive names in kebab-case * Choose appropriate severity based on requirement criticality * Include clear descriptions that explain what is validated ### Step 3: Add Validation Logic [Section titled “Step 3: Add Validation Logic”](#step-3-add-validation-logic) Add the validation logic using the common interface. Every validation call names two roles with separate keys: ```typescript .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCode(201), violatedWhen: not(responseHeader('location')), }) ) ``` This rule: 1. Applies to all transactions with status 201 (`appliesTo`) 2. Reports a violation when the `Location` header is missing (`violatedWhen`) ### Applicability and Violation Condition [Section titled “Applicability and Violation Condition”](#applicability-and-violation-condition) * **`appliesTo`** is the rule’s **Applicability**: which transactions the rule speaks about, such as “responses with status 201”. It is required. * **`violatedWhen`** is the rule’s **Violation Condition**: what is wrong with an applicable transaction, such as “no `Location` header”. It is required too. It is either a filter expression (the rule is violated when it matches) or a function that returns results. It runs only on transactions that `appliesTo` accepts. A transaction outside the rule’s applicability is never a violation. An object that leaves out either key does not compile. Both keys are evaluated against whatever the command observes: | Command | `appliesTo` is checked against | `violatedWhen` is checked against | | --------- | -------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | `lint` | the API specification | the same described transaction | | `analyze` | the recorded request and response | the same recorded pair | | `test` | the specification, to choose which requests to send, **and then again** against the response that actually came back | the response that came back | In `test`, `appliesTo` therefore acts twice. A rule for 405 responses sends requests to operations the specification describes as answering 405, but stays silent when the server answers 204. Caution Put a fact that only live traffic carries in `violatedWhen`, not in `appliesTo`. A header such as `Content-Length` or `Content-Type` is never declared in an OpenAPI description, so a specification cannot answer `responseHeader('content-length')`. In `appliesTo` it would select nothing in `test`, and no request would be sent. Select the transactions by what the specification does know, such as status code and method, and state the live-only part in `violatedWhen`. ### Step 4: Complete the Rule [Section titled “Step 4: Complete the Rule”](#step-4-complete-the-rule) Always end with `.done()`: ```typescript .done(); ``` ## Complete Example [Section titled “Complete Example”](#complete-example) Here’s a complete rule that enforces API versioning through custom headers: ```typescript import { httpRule } from '@thymian/core'; import { constant, not, requestHeader } from '@thymian/core'; export default httpRule('require-api-version-header') .severity('error') .type('static', 'analytics', 'test') .url('https://api-guidelines.mycompany.com/versioning') .description('All API requests must include X-API-Version header') .appliesTo('client') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: constant(true), violatedWhen: not(requestHeader('x-api-version')) })) .done(); ``` ## Validation Patterns [Section titled “Validation Patterns”](#validation-patterns) There are three main patterns for writing the Violation Condition. `appliesTo` is an expression (a function in a lint-only rule, which works on the specification directly); what changes is the form of `violatedWhen`. ### Pattern 1: Expression Condition [Section titled “Pattern 1: Expression Condition”](#pattern-1-expression-condition) Use a filter expression when a simple match says what is wrong: ```typescript .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: method('DELETE'), // Select DELETE requests violatedWhen: not(statusCodeRange(200, 204)), // Flag if status not 200-204 }) ) ``` Every applicable transaction that matches `violatedWhen` produces one violation. ### Pattern 2: Function Condition [Section titled “Pattern 2: Function Condition”](#pattern-2-function-condition) Use a function for conditions an expression can’t express, such as parsing a header value, comparing dates or diffing two responses. The function runs only on transactions that `appliesTo` accepts and returns the violations it finds: ```typescript import { getHeader } from '@thymian/core'; .rule((ctx) => ctx.validateHttpTransactions({ appliesTo: statusCode(401), violatedWhen: (request, response, location) => { const authHeader = getHeader(response.headers, 'www-authenticate'); // A live-only fact, checked on the response that actually came back return typeof authHeader === 'undefined' || !isValidAuthHeader(authHeader) ? [{ location, violation: { message: 'Invalid WWW-Authenticate header' }, findings: [] }] : []; }, }) ) ``` ### Pattern 3: Grouped Transactions [Section titled “Pattern 3: Grouped Transactions”](#pattern-3-grouped-transactions) Use the grouped variant when a violation depends on several transactions together, such as the same URL answered to `GET` and `HEAD`. It takes the same `appliesTo` and `violatedWhen` keys, plus a `groupBy` expression. `violatedWhen` is a function that receives each group, and only applicable transactions reach it: ```typescript .rule((ctx) => ctx.validateGroupedCommonHttpTransactions({ appliesTo: and(statusCode(200), or(method('GET'), method('HEAD'))), groupBy: url(), violatedWhen: (_group, transactions) => { const head = transactions.find(([req]) => equalsIgnoreCase(req.method, 'head')); const get = transactions.find(([req]) => equalsIgnoreCase(req.method, 'get')); if (!head || !get) return []; const [, headResponse, location] = head; const [, getResponse] = get; return headResponse.headers.length < getResponse.headers.length ? [{ location, violation: { message: 'HEAD response is missing headers that GET carries' }, findings: [] }] : []; }, }) ) ``` ## Common Filter Expressions [Section titled “Common Filter Expressions”](#common-filter-expressions) Filter expressions from `@thymian/core` let you declaratively match HTTP transactions: ### Request Filters [Section titled “Request Filters”](#request-filters) ```typescript method('GET'); // Match HTTP method requestHeader('content-type'); // Match header presence hasRequestBody(); // Has request body requestMediaType('application/json'); // Match content type authorization(); // Has authorization ``` ### Response Filters [Section titled “Response Filters”](#response-filters) ```typescript statusCode(404); // Match status code statusCodeRange(400, 499); // Match status range responseHeader('location'); // Match header presence hasResponseBody(); // Has response body responseMediaType('application/json'); // Match content type ``` ### Logical Operators [Section titled “Logical Operators”](#logical-operators) ```typescript and(method('POST'), statusCode(201)); // Both must match or(statusCode(301), statusCode(302)); // Either can match not(responseHeader('location')); // Must NOT match xor(...); ``` ## Real-World Examples [Section titled “Real-World Examples”](#real-world-examples) ### Example 1: Enforce Consistent Error Format [Section titled “Example 1: Enforce Consistent Error Format”](#example-1-enforce-consistent-error-format) Ensure all error responses use Problem Details format: ```typescript import { httpRule } from '@thymian/core'; import { statusCodeRange, not, responseMediaType } from '@thymian/core'; export default httpRule('errors-use-problem-details') .severity('warn') .type('static', 'analytics') .description('Error responses should use application/problem+json format') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: statusCodeRange(400, 599), violatedWhen: not(responseMediaType('application/problem+json')) })) .done(); ``` ### Example 2: Enforce Correlation ID Tracking [Section titled “Example 2: Enforce Correlation ID Tracking”](#example-2-enforce-correlation-id-tracking) Ensure distributed tracing by requiring correlation IDs: ```typescript import { httpRule } from '@thymian/core'; import { constant, not, requestHeader } from '@thymian/core'; export default httpRule('require-correlation-id') .severity('warn') .type('static', 'analytics') .description('Requests should include X-Correlation-ID for distributed tracing') .appliesTo('client') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: constant(true), violatedWhen: not(requestHeader('x-correlation-id')) })) .done(); ``` ### Example 3: Require Deprecation Headers [Section titled “Example 3: Require Deprecation Headers”](#example-3-require-deprecation-headers) Ensure deprecated endpoints include proper sunset notices: ```typescript import { httpRule } from '@thymian/core'; import { path, not, responseHeader } from '@thymian/core'; export default httpRule('deprecated-endpoints-require-sunset') .severity('error') .type('static', 'analytics') .description('Deprecated API endpoints must include Sunset header') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: path('/api/v1/*'), violatedWhen: not(responseHeader('sunset')) })) .done(); ``` ## Advanced: Custom Validation Functions [Section titled “Advanced: Custom Validation Functions”](#advanced-custom-validation-functions) When an expression isn’t sufficient, make `violatedWhen` a function: ```typescript import { httpRule } from '@thymian/core'; import { statusCode, getHeader } from '@thymian/core'; export default httpRule('validate-cache-control-directives') .severity('warn') .type('test', 'analytics') .description('Cache-Control header must include valid directives') .appliesTo('server') .rule((ctx) => ctx.validateHttpTransactions({ appliesTo: statusCode(200), // the specification can't declare Cache-Control, so it is checked below violatedWhen: (request, response, location) => { const cacheControl = getHeader(response.headers, 'cache-control'); if (typeof cacheControl === 'undefined') return []; // Custom parsing and validation const directives = parseCacheControl(cacheControl); // Return a result for every violation detected return hasValidDirectives(directives) ? [] : [{ location, violation: { message: 'Invalid Cache-Control directives' }, findings: [] }]; }, }), ) .done(); function parseCacheControl(header: string) { // Your parsing logic } function hasValidDirectives(directives: any) { // Your validation logic } ``` ## Common Pitfalls [Section titled “Common Pitfalls”](#common-pitfalls) ### 1. Forgetting `.done()` [Section titled “1. Forgetting .done()”](#1-forgetting-done) Always end your rule definition with `.done()`: ```typescript // ❌ Missing .done() export default httpRule('my-rule') .severity('error') .type('static') .rule((ctx) => { ... }); // ✅ Correct export default httpRule('my-rule') .severity('error') .type('static') .rule((ctx) => { ... }) .done(); // Don't forget! ``` ### 2. Using Wrong Filter Combination [Section titled “2. Using Wrong Filter Combination”](#2-using-wrong-filter-combination) Ensure your filter logic matches your intent: ```typescript // ❌ This will never match (GET is not POST) and(method('GET'), method('POST')); // ✅ Use OR for alternatives or(method('GET'), method('POST')); ``` ## Next Steps [Section titled “Next Steps”](#next-steps) Now that you know how to create rules: * Explore [rule types in depth](/concepts/rule-types/) to understand context-specific features * Learn about [combining rule types](/guides/http-rules/combining-types/) for hybrid rules * See [how to use rules](/guides/http-rules/how-to-use-rules/) in your projects # How To Use Rules > Integrating and configuring HTTP linting rules in your projects Once you’ve created HTTP rules, you need to integrate them into your API projects. This guide covers loading rules, configuration, and common usage patterns. ## Loading Rules [Section titled “Loading Rules”](#loading-rules) There are two ways to load rules into Thymian: ### 1. Via Configuration File [Section titled “1. Via Configuration File”](#1-via-configuration-file) Create a `thymian.config.yaml` file in your project root: ```yaml ruleSets: - '@thymian/rules-rfc-9110' # a rule-set package (npm) - './rules/my-api.rule.ts' # a local rule file (explicit extension) plugins: '@thymian/plugin-http-linter': {} ``` A `ruleSets:` entry is a rule-set **package name** or a **local path** with an explicit extension — the same bare-vs-local contract as `--rule-set` (below). What it is **not** is a glob: `./rules/**/*.rule.ts` will not expand here. To pull in many local files at once, bundle them into a rule set that globs them through its [`pattern:` field](#rule-set-entry-point). ### 2. Via CLI flags [Section titled “2. Via CLI flags”](#2-via-cli-flags) ```bash # a published rule-set package npx thymian lint --rule-set @thymian/rules-rfc-9110 # a local rule or rule-set entry point (explicit extension, not a glob) npx thymian lint --rule-set ./rules/my-api.rule.ts ``` ## Creating Your Own Rule Set [Section titled “Creating Your Own Rule Set”](#creating-your-own-rule-set) ### File Structure [Section titled “File Structure”](#file-structure) Organize your rule set as an npm package: ```plaintext my-api-rules/ ├── package.json ├── src/ │ ├── index.ts │ └── rules/ │ ├── authentication/ │ │ └── require-bearer-token.rule.ts │ ├── headers/ │ │ └── require-correlation-id.rule.ts │ └── versioning/ │ └── check-api-version.rule.ts └── tsconfig.json ``` ### Package Definition [Section titled “Package Definition”](#package-definition) **package.json:** ```json { "name": "@mycompany/api-rules", "version": "1.0.0", "main": "./dist/index.js", "types": "./dist/index.d.ts", "files": ["dist"], "peerDependencies": { "@thymian/plugin-http-linter": "^0.0.1", "@thymian/core": "^0.0.1" } } ``` ### Rule Set Entry Point [Section titled “Rule Set Entry Point”](#rule-set-entry-point) **src/index.ts:** ```typescript import type { RuleSet } from '@thymian/core'; const myCompanyRules: RuleSet = { name: '@mycompany/api-rules', url: 'https://api-guidelines.mycompany.com', pattern: 'rules/**/*.rule.js', // Glob pattern for built rules }; export default myCompanyRules; ``` A published rule set ships **built JavaScript** — its entry point (`main` above) is resolved as a bare specifier, and a package that ships unbuilt TypeScript source is declined. The glob pattern here matches the rule set’s own compiled output under `dist/`, not `src/`. See [Loading Rules and Plugins](/references/loading-rules-and-plugins/) for the full contract. ### Using Your Rule Set [Section titled “Using Your Rule Set”](#using-your-rule-set) After publishing your rule set to npm, install it in your project: ```bash npm install @mycompany/api-rules ``` ```yaml ruleSets: - '@mycompany/api-rules' ``` ## List Loaded Rules [Section titled “List Loaded Rules”](#list-loaded-rules) See which rules are loaded: ```bash thymian rules list ``` Or with specific rule sources: ```bash thymian rules list --rule-set ./rules/my-api.rule.ts ``` ## Troubleshooting [Section titled “Troubleshooting”](#troubleshooting) ### Rules Not Loading [Section titled “Rules Not Loading”](#rules-not-loading) **Problem:** Rules aren’t being loaded **Solutions:** 1. Check the glob pattern matches your files: ```bash ls -la ./rules/**/*.rule.ts ``` 2. Verify the rule exports default: ```typescript export default httpRule('...'); // ✅ Correct export const myRule = httpRule('...'); // ❌ Wrong ``` 3. Check the specifier itself — the most common causes are: * **A missing explicit extension.** `--rule-set ./my-rule` does not resolve; write `--rule-set ./my-rule.ts`. * **A bare specifier that was meant to be local.** There is no `/` fallback — write it as a path (`./my-rule.ts`), not a bare name. * **`.mts`, `.cts`, or `.d.ts`.** None of these are loadable — use `.ts`. See [Loading Rules and Plugins](/references/loading-rules-and-plugins/) for the full contract. Note Loading never type-checks — jiti strips TypeScript types, it does not check them. A rule can load successfully and still contain type errors. Run `tsc --noEmit` in CI to catch those; it is a separate concern from whether the rule loads. ## Next Steps [Section titled “Next Steps”](#next-steps) * Explore the [CLI commands](/references/cli/) for managing rules * Learn about [creating custom rules](/guides/http-rules/creating-new-rules/) * See [combining rule types](/guides/http-rules/combining-types/) for hybrid validation # Use linting to validate your APIs > Validate HTTP APIs across design, testing, and production with reusable rules. The HTTP plugins (`@thymian/plugin-http-linter`, `@thymian/plugin-http-tester` and `@thymian/plugin-http-analyzer`) enable you to validate HTTP APIs at every stage of development—from design and implementation to testing and production monitoring. Write validation rules once and apply them across static specifications, live API tests, and recorded traffic analysis. ## Why HTTP Linting? [Section titled “Why HTTP Linting?”](#why-http-linting) Modern API development faces several challenges: * **API drift** between specification and implementation * **Inconsistent behavior** across endpoints and versions * **Compliance issues** with HTTP standards or internal guidelines * **Breaking changes** that affect clients HTTP linting addresses these challenges by providing automated validation that runs throughout your development lifecycle. ## Key Features [Section titled “Key Features”](#key-features) * **Write once, validate everywhere** — Rules work across static specs, live tests, and traffic analysis * **Powerful filter DSL** — Declaratively match HTTP requests and responses * **Type-safe** — Full TypeScript support with IDE autocomplete * **Extensible** — Create and share custom rule sets as npm packages * **CLI tools** — Generate, search, and manage rules from the command line ## Quick Start [Section titled “Quick Start”](#quick-start) Here’s a simple custom rule that enforces API versioning across your organization: ```typescript import { httpRule, not, constant } from '@thymian/core'; export default httpRule('api-must-include-version-in-path') .severity('error') .type('static', 'analytics') .description('All API endpoints must include /v{number}/ in path for versioning') .appliesTo('server') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: constant(true), // every transaction violatedWhen: (req, _res, location) => (/api\/v\d+/.test(req.path) ? [] : [{ location, violation: { message: 'Path must include a version such as /v1/' }, findings: [] }]), }), ) .done(); ``` `appliesTo` says which transactions the rule speaks about (here: all of them), and `violatedWhen` says what is wrong with them (here: a path without a version). This custom organizational rule automatically validates: * **Lint** — Checks OpenAPI definitions during design * **Analyze** — Validates recorded HTTP transactions from production ## Use Cases [Section titled “Use Cases”](#use-cases) ### Prevent API Drift [Section titled “Prevent API Drift”](#prevent-api-drift) Validate that your running API matches its specification by applying the same rules to both: ```typescript // Rule validates both spec and implementation .type('static', 'test') ``` ### Enforce API Governance [Section titled “Enforce API Governance”](#enforce-api-governance) Create organization-wide rules for consistent API behavior: ```typescript // Ensure all authenticated endpoints include rate limit headers httpRule('authenticated-endpoints-must-include-rate-limits') .type('analytics', 'test') .rule((ctx) => ctx.validateCommonHttpTransactions({ appliesTo: and(authorization(), successfulStatusCode()), violatedWhen: not(responseHeader('x-ratelimit-remaining')) })) .done(); ``` ### Validate Production Traffic [Section titled “Validate Production Traffic”](#validate-production-traffic) Analyze recorded traffic to detect issues in production: ```typescript // Analyze-only rule for production monitoring .type('analytics') ``` ## How It Works [Section titled “How It Works”](#how-it-works) ``` graph LR A[HTTP Rule] --> B[Lint Context] A --> C[Test Context] A --> D[Analyze Context] B --> E[Validates OpenAPI Spec] C --> F[Generates & Runs Tests] D --> G[Analyzes Recorded Traffic] E --> H[Reports Violations] F --> H G --> H ``` The HTTP linter provides three validation contexts, each suited for different stages of development: 1. **Lint** — Fast validation against API specifications 2. **Test** — Active testing of live endpoints 3. **Analyze** — Passive analysis of recorded HTTP traffic Rules can target one or more contexts, and the linter automatically adapts the validation logic to each context. ## Documentation [Section titled “Documentation”](#documentation) 1. [What is an HTTP Rule?](/concepts/what-is-an-http-rule/) — Core concepts and rule anatomy 2. [Creating New Rules](/guides/http-rules/creating-new-rules/) — Step-by-step guide to writing rules 3. [Rule Types](/concepts/rule-types/) — Understanding static, test, and analytics contexts 4. [Combining Different Rule Types](/guides/http-rules/combining-types/) — Writing hybrid rules 5. [How To Use Rules](/guides/http-rules/how-to-use-rules/) — Integration and configuration 6. [CLI](/references/cli/) — Command-line tools reference ## Next Steps [Section titled “Next Steps”](#next-steps) * Learn about [HTTP rule fundamentals](/concepts/what-is-an-http-rule/) * Follow the guide to [create your first rule](/guides/http-rules/creating-new-rules/) * Explore the [CLI tools](/references/cli/) for rule generation and management # How to develop a plugin? > Learn how to build plugins that extend Thymian through its event-driven architecture. ## Introduction [Section titled “Introduction”](#introduction) Thymian is an event- and action-based system that can be extended through plugins. The plugin system is based on a central **Event Emitter** through which plugins can communicate with each other. ### Core Concepts [Section titled “Core Concepts”](#core-concepts) * **Events**: Simple messages that can be sent by plugins (fire-and-forget) * **Actions**: Request-response-based communication between plugins * **Lifecycle Actions**: Special actions that control the system’s lifecycle * **Plugin Function**: The main entry point of a plugin, called at startup ## The Plugin Interface [Section titled “The Plugin Interface”](#the-plugin-interface) A Thymian plugin is a JavaScript/TypeScript object that implements the `ThymianPlugin` interface: ```typescript export type ThymianPlugin = Record> = { plugin: ThymianPluginFn; options?: JSONSchemaType; name: string; version: string; events?: ThymianPluginEvents; actions?: ThymianPluginActions; }; ``` ### Required Fields [Section titled “Required Fields”](#required-fields) * **`name`** (string): Unique name of the plugin * **`version`** (string): The version of Thymian the plugin is compatible with (semver constraint) * **`plugin`** (function): The main function executed at startup ### Optional Fields [Section titled “Optional Fields”](#optional-fields) * **`options`**: JSON schema for validating plugin configuration * **`events`**: Declaration of which events the plugin provides, sends, or receives * **`actions`**: Declaration of which actions the plugin provides, requires, or uses ### The Plugin Function [Section titled “The Plugin Function”](#the-plugin-function) The plugin function receives three parameters: ```typescript type ThymianPluginFn = (emitter: ThymianEmitter, logger: Logger, options: Options) => Promise; ``` * **`emitter`**: The event emitter for communication * **`logger`**: Logger instance for structured logging * **`options`**: Configuration options from `thymian.config.yaml`, plus `cwd` (automatically added) ## The ThymianEmitter: API Overview [Section titled “The ThymianEmitter: API Overview”](#the-thymianemitter-api-overview) The `ThymianEmitter` is the central interface for plugin communication: ### Event Methods [Section titled “Event Methods”](#event-methods) #### `on(eventName, handler)` [Section titled “on(eventName, handler)”](#oneventname-handler) Registers an event handler for a specific event. ```typescript emitter.on('core.error', async (error) => { logger.error('Error received:', error.message); }); ``` #### `emit(eventName, payload)` [Section titled “emit(eventName, payload)”](#emiteventname-payload) Sends an event to all registered listeners (fire-and-forget). ```typescript emitter.emit('core.report', { reportId: 'report-1', createdAt: new Date().toISOString(), runs: [ { runId: 'run-1', tool: { name: 'my-plugin' }, runType: 'analyze', runAt: new Date().toISOString(), executions: [ { location: { type: 'custom', value: 'GET /pets -> 200' }, findings: [ { id: 'finding-1', kind: 'rule-violation', ruleId: 'my-plugin/cache-control', title: 'Missing Cache-Control header', severity: 'warn', message: { text: 'Missing Cache-Control header' }, }, ], }, ], }, ], }); ``` #### `emitError(error, name?)` [Section titled “emitError(error, name?)”](#emiterrorerror-name) Sends an error event. ```typescript emitter.emitError(new Error('Something went wrong'), 'my-plugin'); ``` ### Action Methods [Section titled “Action Methods”](#action-methods) #### `onAction(actionName, handler)` [Section titled “onAction(actionName, handler)”](#onactionactionname-handler) Registers an action handler that responds to action requests. The handler receives the action payload and a context object with `reply()` and `error()` methods. ```typescript emitter.onAction('core.format.load', async (payload, ctx) => { // Load and parse a specification format const format = await loadMyFormat(payload.inputs); ctx.reply({ value: format, }); }); ``` #### `emitAction(actionName, payload?, options?)` [Section titled “emitAction(actionName, payload?, options?)”](#emitactionactionname-payload-options) Sends an action and waits for responses from registered handlers. ```typescript const responses = await emitter.emitAction('core.format.load', { inputs: [{ type: 'openapi', location: './openapi.yaml' }], }); ``` **Options:** * `timeout`: Maximum wait time in milliseconds (default: 1000) * `strategy`: How to collect responses: * `'collect'` (default): Array of all responses * `'first'`: Only the first response * `'deep-merge'`: All responses are deeply merged * `strict`: Whether to emit an error if no handler is registered for a non-core action (default: `true`) ### Error Methods [Section titled “Error Methods”](#error-methods) #### `onError(handler)` [Section titled “onError(handler)”](#onerrorhandler) Registers a handler for error events. ```typescript emitter.onError((errorEvent) => { logger.error('Error from', errorEvent.source, ':', errorEvent.message); }); ``` ### Additional Methods [Section titled “Additional Methods”](#additional-methods) #### `child(source)` [Section titled “child(source)”](#childsource) Creates a child emitter with its own source name for better traceability. Child emitters share the same underlying event bus. ```typescript const childEmitter = emitter.child('my-plugin:submodule'); ``` ## Lifecycle Actions [Section titled “Lifecycle Actions”](#lifecycle-actions) Thymian defines two lifecycle actions that control the system’s flow: ### `core.ready` [Section titled “core.ready”](#coreready) **Payload:** `void` **Response:** `void` **Timing:** Triggered after all plugins have been loaded and their plugin functions have been called. **Usage:** Post-initialization tasks that require all plugins to be registered (e.g., establishing connections, checking for required sibling plugins). ### `core.close` [Section titled “core.close”](#coreclose) **Payload:** `void` **Response:** `void` **Timing:** Called before the system shuts down. **Usage:** Cleanup of resources, closing connections, saving state. ### Lifecycle Sequence [Section titled “Lifecycle Sequence”](#lifecycle-sequence) ```text thymian.run(fn) | +-> ready() | +-> For each plugin: | | emit('core.register', { name, events, options }) | | plugin.plugin(childEmitter, childLogger, options) | | | +-> emitAction('core.ready') | +-> fn(emitter, logger) // user workflow (lint, test, analyze, ...) | +-> close() +-> emitAction('core.close') +-> emitter.shutdown() ``` ## Core Events [Section titled “Core Events”](#core-events) Events are fire-and-forget messages. Plugins listen with `emitter.on(...)`. | Event | Payload | Description | | --------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `core.error` | `ThymianError` | Emitted when an error occurs. Includes `name`, `message`, and optional `severity`, `exitCode`, `suggestions`, `code`. | | `core.register` | `{ name, options, events }` | Emitted once per plugin during the loading phase. Lets other plugins discover registered peers. | | `core.report` | `Report` | Structured v4 report. Contains `runs`, each with `executions` and `findings`. | | `core.exit` | `{ code? }` | Signals process exit with an optional exit code. | ## Core Actions [Section titled “Core Actions”](#core-actions) Actions are request-response interactions. Plugins listen with `emitter.onAction(...)` and must call `ctx.reply(...)` to respond. ### Specification and Traffic Loading [Section titled “Specification and Traffic Loading”](#specification-and-traffic-loading) | Action | Request Payload | Response Payload | Strategy | Description | | ------------------- | ------------------------- | --------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | | `core.format.load` | `{ inputs, options? }` | `SerializedThymianFormat` | `collect` | Load and parse API specification(s) into the internal format graph. Each plugin may return a partial graph; Thymian merges them. | | `core.format` | `SerializedThymianFormat` | `void` | `collect` | Broadcasts the finalized, merged format graph to all interested plugins. Emitted after `core.format.load` completes. | | `core.traffic.load` | `{ inputs, options? }` | `{ transactions?, traces?, metadata? }` | `collect` | Load captured HTTP traffic data. Plugins return matching transaction/trace data. | ### Validation Workflows [Section titled “Validation Workflows”](#validation-workflows) | Action | Request Payload | Response Payload | Strategy | Description | | -------------- | -------------------------------------------------------- | ---------------- | --------- | ------------------------------------------------------------------- | | `core.lint` | `{ format, rules?, rulesConfig?, options? }` | `ToolRun[]` | `collect` | Run lint rules against a format (static analysis, no live traffic). | | `core.test` | `{ format, targetUrl?, rules?, rulesConfig?, options? }` | `ToolRun[]` | `collect` | Run test rules against a live target URL. | | `core.analyze` | `{ traffic, format?, rules?, rulesConfig?, options? }` | `ToolRun[]` | `collect` | Run analysis rules against captured traffic. | Each workflow plugin replies with one or more `ToolRun` objects. Core assembles all collected runs into a single `Report`, emits that report on `core.report`, and returns it in `WorkflowOutcome`. ### Reporting [Section titled “Reporting”](#reporting) There is no `core.report.flush` action in the v4 reporting model. The CLI renders terminal output directly from `WorkflowOutcome.report`, while optional reporter plugins consume the passive `core.report` event for file outputs. ### HTTP Request Handling [Section titled “HTTP Request Handling”](#http-request-handling) | Action | Request Payload | Response Payload | Strategy | Description | | ----------------------- | --------------------------- | --------------------- | -------- | ------------------------------------------------------------------------------------------- | | `core.request.sample` | `{ transaction, options? }` | `HttpRequestTemplate` | `first` | Generate a sample HTTP request from a format transaction. Only one plugin needs to respond. | | `core.request.dispatch` | `{ request, options? }` | `HttpResponse` | `first` | Send an HTTP request and return the response. Only one plugin needs to respond. | Note Actions using `first` strategy return only the first response, while `collect` waits for all registered handlers. ## Tutorial: Complete Example Plugin [Section titled “Tutorial: Complete Example Plugin”](#tutorial-complete-example-plugin) Here is a complete example plugin that listens to lifecycle actions, core events, and the `core.format` broadcast: my-format-inspector-plugin.ts ```typescript export default { name: 'my-format-inspector', version: '0.0.1', // Declaration: Which actions does this plugin listen to? actions: { listensOn: ['core.ready', 'core.format', 'core.close'], }, // Declaration: Which events does this plugin listen to? events: { listensOn: ['core.error', 'core.register', 'core.report'], }, async plugin(emitter, logger, options) { logger.info('Plugin is being initialized with options:', options); // ============================================ // LIFECYCLE ACTION: core.ready // ============================================ emitter.onAction('core.ready', async (payload, ctx) => { logger.info('System is ready, all plugins are loaded'); ctx.reply(); }); // ============================================ // ACTION: core.format // Receives the merged API specification format // ============================================ emitter.onAction('core.format', async (format, ctx) => { logger.info('Received format with', format.nodes.length, 'nodes'); // Inspect the format graph, e.g., collect endpoints // You could emit reports based on the format analysis ctx.reply(); }); // ============================================ // LIFECYCLE ACTION: core.close // ============================================ emitter.onAction('core.close', async (payload, ctx) => { logger.info('Cleaning up resources'); ctx.reply(); }); // ============================================ // EVENT LISTENER: core.error // ============================================ emitter.on('core.error', async (error) => { logger.error('Error detected:', error.message); }); // ============================================ // EVENT LISTENER: core.register // ============================================ emitter.on('core.register', async (event) => { logger.info('Plugin registered:', event.name); }); // ============================================ // EVENT LISTENER: core.report // ============================================ emitter.on('core.report', async (report) => { logger.info('Report emitted with', report.runs.length, 'tool run(s)'); }); logger.info('Plugin initialization completed - all handlers registered'); }, }; ``` ## Plugin Integration: Registration and Configuration [Section titled “Plugin Integration: Registration and Configuration”](#plugin-integration-registration-and-configuration) ### 1. Create Plugin File [Section titled “1. Create Plugin File”](#1-create-plugin-file) Create a TypeScript file for your plugin — TypeScript is the recommended default, and JavaScript is fully supported too: my-plugin.ts ```typescript export default { name: 'my-awesome-plugin', version: '1.0.0', async plugin(emitter, logger, options) { // Plugin logic }, }; ``` Note A TypeScript plugin loads with **no build step** — Thymian transpiles it on the fly. Loading does not type-check, and `erasableSyntaxOnly` is not required of your code (that restriction belongs to Node’s own type-stripping feature, which Thymian’s loader does not use). See [Loading Rules and Plugins](/references/loading-rules-and-plugins/) for the full loading contract, including the closed set of loadable extensions. ### 2. Plugin Registration [Section titled “2. Plugin Registration”](#2-plugin-registration) You can register plugins via the Thymian configuration file or via CLI. ### Via CLI [Section titled “Via CLI”](#via-cli) If you want to load a plugin directly via the CLI, use the `--plugin` option: ```bash thymian lint --plugin ./path/to/my-plugin.ts ``` ### Via Configuration File [Section titled “Via Configuration File”](#via-configuration-file) Plugins are registered in `thymian.config.yaml`: ```yaml plugins: my-awesome-plugin: path: ./path/to/my-plugin.ts options: greeting: Hello World customOption: 42 my-format-inspector: path: ./plugins/my-format-inspector-plugin.ts ``` ### 3. Plugin Options [Section titled “3. Plugin Options”](#3-plugin-options) All options from the configuration are passed as the `options` parameter to the plugin function: ```javascript async plugin(emitter, logger, options) { console.log(options.greeting); // "Hello World" console.log(options.customOption); // 42 console.log(options.cwd); // Working directory (automatically added) } ``` ## Best Practices [Section titled “Best Practices”](#best-practices) ### 1. Logging [Section titled “1. Logging”](#1-logging) Use the provided logger instead of `console.log`: ```javascript logger.info('Informative message', { additionalData: 'value' }); logger.warn('Warning'); logger.error('Error', error); logger.debug('Debug info'); ``` ### 2. Asynchronous Operations [Section titled “2. Asynchronous Operations”](#2-asynchronous-operations) The plugin function and all handlers should be async: ```javascript async plugin(emitter, logger, options) { // Use await for asynchronous operations await someAsyncOperation(); } ``` ### 3. Resource Management [Section titled “3. Resource Management”](#3-resource-management) Use `core.close` to properly release resources: ```javascript emitter.onAction('core.close', async (payload, ctx) => { await closeDatabase(); await saveState(); ctx.reply(); }); ``` ### 4. Child Emitters for Submodules [Section titled “4. Child Emitters for Submodules”](#4-child-emitters-for-submodules) Use child emitters for better logging and organization: ```javascript const subEmitter = emitter.child('my-plugin:submodule'); subEmitter.emit('core.report', { reportId: 'report-2', createdAt: new Date().toISOString(), runs: [], }); ``` ### 5. Declare Your Event and Action Dependencies [Section titled “5. Declare Your Event and Action Dependencies”](#5-declare-your-event-and-action-dependencies) Always declare which events and actions your plugin interacts with. This helps Thymian validate the plugin graph and lets other plugins discover capabilities: ```javascript export default { name: 'my-plugin', version: '1.0.0', events: { listensOn: ['core.error', 'core.report'], emits: ['core.report'], }, actions: { listensOn: ['core.ready', 'core.lint', 'core.close'], requires: ['core.format.load'], }, async plugin(emitter, logger, options) { // ... }, }; ``` ## Summary [Section titled “Summary”](#summary) A complete Thymian plugin: 1. **Exports an object** with `name`, `version`, and `plugin` function 2. **Registers handlers** for lifecycle actions (`core.ready`, `core.close`) 3. **Listens to workflow actions** as needed (`core.format.load`, `core.format`, `core.lint`, `core.test`, `core.analyze`, etc.) 4. **Implements event listeners** for system events (`core.error`, `core.register`, `core.report`, `core.exit`) 5. **Communicates via the emitter** with other plugins 6. **Declares its dependencies** via `events` and `actions` metadata 7. **Uses logging** for diagnostics and monitoring 8. **Is registered in the config** (`thymian.config.yaml`) With this knowledge, you can develop powerful plugins that integrate seamlessly into the Thymian system! # How to connect remote plugins? > This guide is for users who want to run the proxy plugin in Thymian and connect remote plugins via WebSocket. This guide is for users who want to run the proxy plugin in Thymian and connect remote plugins via WebSocket. ## Overview [Section titled “Overview”](#overview) The proxy plugin starts a WebSocket server that remote clients can connect to. After a short handshake, a remote plugin can: * send events to Thymian, * trigger actions in Thymian and receive results, * receive events from Thymian, and * handle actions requested by Thymian and reply. For protocol details, see the [Plugin Developers Guide](/guides/remote-plugins/how-to-develop-a-remote-plugin/). ## Configuration in Thymian [Section titled “Configuration in Thymian”](#configuration-in-thymian) Register the proxy plugin in your Thymian setup and configure the port and known remote plugins: ```ts import { Thymian, TextLogger } from '@thymian/core'; import { websocketProxyPlugin } from '@thymian/plugin-websocket-proxy'; const thymian = new Thymian(); thymian.register(websocketProxyPlugin, { port: 51234, // optional, default is 51234 plugins: [ { name: 'remote-plugin', token: 'secret-token', // optional required: false, // optional, if set to true, Thymian waits for the plugin to connect before starting options: {}, // options that are sent to the remote plugin during the handshake, }, ], }); await thymian.ready(); // ... application is running, remote plugins can connect // Shutdown await thymian.close(); ``` The plugin takes an option object with the following properties: ### `port` (optional) - Default: 51234 [Section titled “port (optional) - Default: 51234”](#port-optional---default-51234) The port of the Websocket server. ### `plugins` (optional) - Default: `[]` [Section titled “plugins (optional) - Default: \[\]”](#plugins-optional---default-) List of known remote plugins. Each entry of this list is an object with the following properties: #### `name` (required) [Section titled “name (required)”](#name-required) Unique name of the remote plugin. Used to identify the plugin in the handshake. #### `token` (optional) [Section titled “token (optional)”](#token-optional) If a token is set, it is checked during the handshake, if the corresponding remote plugins sends the same token. #### `required` (optional) - Default: `false` [Section titled “required (optional) - Default: false”](#required-optional---default-false) If set to `true`, Thymian waits for the plugin to connect before starting. #### `options` (optional) - Default: `{}` [Section titled “options (optional) - Default: {}”](#options-optional---default-) Options that are sent to the remote plugin during the handshake. ### `clientTimeoutMs` (optional) - Default: 4000 [Section titled “clientTimeoutMs (optional) - Default: 4000”](#clienttimeoutms-optional---default-4000) Number of milliseconds to wait for a remote plugin to connect before throwing a timeout error. ## Configuration via Thymian configuration file [Section titled “Configuration via Thymian configuration file”](#configuration-via-thymian-configuration-file) The plugin can also be configured via the Thymian configuration file if Thymian is used via its CLI. Thereby all options described above can be used. # How to develop a remote plugin? > This guide explains how a remote plugin communicates with Thymian over WebSockets via the plugin @thymian/websocket-proxy. This guide explains how a remote plugin communicates with Thymian over WebSockets via the plugin `@thymian/websocket-proxy`. The WebSocket server mirrors the `ThymianEmitter API`, i.e.: * `emit(event, payload)` ↔ message `{ "type": "emit", ... }` * `emitAction(name, payload, options)` ↔ message `{ "type": "emitAction", ... }` with response `emitActionResult`/`emitActionError` * `on(event, handler)` ↔ server sends `{ "type": "event", ... }` * `onAction(name, handler)` ↔ server sends `{ "type": "action", ... }`, the client must answer with `actionReply` or `actionError` See also ThymianEmitter in `@thymian/core` — this protocol exposes the same surface over WebSockets. ## Connection and handshake [Section titled “Connection and handshake”](#connection-and-handshake) Before a connection can be established, the client must register to the server via a three-way handshake. 1. Connect to WebSocket: `ws://:` (default port is 51234). 2. Register: * Client sends once: ```json5 { type: 'register', token: 's3cr3t', // optional, shared secret name: 'remote-plugin', // name of the plugin onActions: ['core.run'], // only the actions listed here will be forwarded onEvents: ['core.report'], // ...same for events } ``` This initial message starts the handshake. It tells the websocket server which plugin tries to connect and which events and actions it wants to receive. * Server replies with: ```json5 { type: 'register-ack', ok: true, config: { feature: true, threshold: 3 }, } ``` The server acknowledges the registration and returns the configuration of the plugin. If the provided token is invalid or if the plugin name is already taken, the server responds with `ok: false`. 3. Send ready: After a successful `register-ack`, the client sends: ```json5 { type: 'ready' } ``` The client should send this message AFTER its configuration is done and the plugin is ready to receive events and actions. Note Message order is strict. On violations the connection is closed with code 1008 (Policy Violation). ## Usage [Section titled “Usage”](#usage) After the handshake, the client can emit and receive events and actions. Therefore, the API of the `ThymianEmitter` is mirrored. In the following, the message types and their use cases are explained. Each message uses the JSON format and always has a `type` property, that describes the type of the message. ### Emit an event [Section titled “Emit an event”](#emit-an-event) To emit an event, the client must send a `EmitEventMessage` with the following properties: * `type`: must be `"emit"` * `name`: the name of the event to emit * `payload`: the payload of the event An example: ```json5 { type: 'emit', name: 'core.report', payload: { topic: 'my topic', }, } ``` ### Emit an action [Section titled “Emit an action”](#emit-an-action) To emit an action, the client must send a `EmitActionMessage` with the following properties: * `type`: must be `"emitAction"` * `id`: a unique identifier for the action (a UUID is recommended) * `name`: the name of the action to emit * `payload`: the payload of the action * `options`: optional object with the following properties: * `strategy`: `"first"` (default) or `"collect"` to collect all replies and `"deep-merge"` to merge all replies into a single payload * `timeout`: timeout in milliseconds that is waited for replies An example: ```json5 { type: 'emitAction', id: 'C4A22DA0-FDD7-4864-A731-5CBFA1A49834', name: 'core.run', payload: {}, options: { strategy: 'deep-merge', timeout: 10000, }, } ``` The plugin will receive a response to the action with either `emitActionResult` or `emitActionError` message. ### Responding to an action [Section titled “Responding to an action”](#responding-to-an-action) To respond to an action, the server must send an `ActionReplyMessage` with the following properties: * `type`: must be `"actionReply"` * `correlationId`: the `id` of the action that was emitted * `name`: the name of the action that was emitted * `payload`: the payload of the reply ### Sending an error reply to an action [Section titled “Sending an error reply to an action”](#sending-an-error-reply-to-an-action) To send an error reply to an action, the server must send an `ActionErrorMessage` with the following properties: * `type`: must be `"actionError"` * `correlationId`: the `id` of the action that was emitted * `name`: the name of the action that was emitted * `error`: an object with the following properties: * `name`: the name of the error (optional) * `message`: the error message (required) * `options`: additional options (optional) ### Receiving an event [Section titled “Receiving an event”](#receiving-an-event) When the plugin receives an event, the server sends a `EventMessage` with the following properties: * `type`: must be `"event"` * `name`: the name of the event * `payload`: the payload of the event ### Receiving an action [Section titled “Receiving an action”](#receiving-an-action) When the plugin receives an action, the server sends a `ActionMessage` with the following properties: * `type`: must be `"action"` * `name`: the name of the action * `payload`: the payload of the action The client will respond to the action with either `actionReply` or `actionError` message. ### Receiving a reply to an action [Section titled “Receiving a reply to an action”](#receiving-a-reply-to-an-action) When the plugin receives a reply to an action, the server sends a `ActionResultMessage` with the following properties: * `type`: must be `"emitActionResult"` * `correlationId`: the `id` of the action that was emitted * `name`: the name of the action that was emitted * `payload`: the payload of the reply ### Receiving an error reply to an action [Section titled “Receiving an error reply to an action”](#receiving-an-error-reply-to-an-action) When the plugin receives an error reply to an action, the server sends a `ActionErrorMessage` with the following properties: * `type`: must be `"emitActionError"` * `correlationId`: the `id` of the action that was emitted * `name`: the name of the action that was emitted * `error`: an object with the following properties: * `name`: the name of the error (optional) * `message`: the error message (optional) # Migrating from the Samples Tree > What happened to .thymian/samples, and how to move your v1 hooks onto selector-anchored hooks. If you have a `.thymian/samples/` directory, this page is for you. The move is **manual and one-time**, and this page explains why, and exactly what to do. ## What happened to `.thymian/samples` [Section titled “What happened to .thymian/samples”](#what-happened-to-thymiansamples) It is gone, and nothing replaces it. Request samples are now a projection of your API description, built in memory on every run. The tree existed so that samples could be *found*; the price was that they could also be *stale*, and everything downstream of that was a workaround: | The old problem | Why it is gone | | -------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | Generated `request.json` files went stale after a spec edit | Nothing is generated to disk | | `sampler validate` could flag a stale artifact but not fix it | There is no artifact | | `sampler init --overwrite` never deleted the files of removed operations | There are no files | | Hooks were orphaned at dead paths, because a hook’s directory *was* its identity | A hook’s identity is its Selector, and hook files can live anywhere | | Upgrading Thymian broke byte-exact sample validation | The committed surface is types, compared canonicalized | `thymian sampler check` is unchanged and still there. It answers a different question from `thymian test` — “can these requests be executed at all?” rather than “do the responses conform?” — and it now needs no setup step either. ## Why the move is manual [Section titled “Why the move is manual”](#why-the-move-is-manual) Your hooks are the part of the old tree that carries real investment, and a migration tool could not do the one thing that matters: decide **what each hook is actually for**. In v1 a hook’s target was its position in the directory tree. A file at `…/launches/[id]/@GET/create-launch.beforeEach.ts` applied to everything below that directory — which might be one Transaction, or a status code’s worth, or every method on that path. The tree could not tell you which of those you meant, and neither can a script. In v2 you say so: one Selector, a list of them, or a filter. So the migration is: for each old hook, decide what it was targeting, and write that down. That is a small amount of thinking per hook, once, and it is the thinking the compiler then keeps checking for you. ## Moving a hook [Section titled “Moving a hook”](#moving-a-hook) Tip Do this before deleting anything. `thymian test` ignores `.thymian/samples` entirely, so the old tree can sit there while you work. ### 1. Create the new home [Section titled “1. Create the new home”](#1-create-the-new-home) ```bash mkdir -p .thymian/sampler/hooks ``` Hook files can be anywhere under `hooks/`, at any nesting depth, named anything. The directory layout no longer means anything — it is for your benefit only. ### 2. Find the Selectors you need [Section titled “2. Find the Selectors you need”](#2-find-the-selectors-you-need) ```bash npx thymian sampler show 'GET /launches -> 200 (application/json)' ``` A wrong Selector prints the nearest matches, which is the quickest way to discover the exact spelling of the ones you want. ### 3. Rewrite the hook [Section titled “3. Rewrite the hook”](#3-rewrite-the-hook) The three v1 file-name conventions map like this: | v1 file | v2 | | ----------------- | ----------------------------------------------------------------------- | | `*.beforeEach.ts` | `beforeEach(target, callback)` | | `*.afterEach.ts` | `afterEach(target, callback)` | | `*.authorize.ts` | `authorize(callback)` global, or `authorize(target, callback)` targeted | And the position in the tree becomes the `target`: | The old file sat in… | Write | | -------------------------------------------------- | -------------------------------------------------------------------- | | a directory for one status and media type | that one Selector | | a `@GET` directory | `{ method: 'GET', path: '…' }` | | a path directory, covering every method and status | `{ path: '/launches' }` | | a path directory, covering the whole subtree | `{ path: '/launches/**' }` — note this excludes `/launches` itself | | the source root, applying to everything | a global `authorize(callback)`, or a filter that says what you meant | Three other changes, in the code itself: **Hooks return nothing.** A v1 hook returned the modified request; a v2 hook mutates in place. ```diff const hook: BeforeEachRequestHook = async (request, context, utils) => { request.headers.authorization = 'Bearer …'; return request; }; export default hook; export const auth = beforeEach('GET /launches -> 200 (application/json)', (request) => { request.headers['authorization'] = 'Bearer …'; }); ``` **Registrations are exported values.** A v1 file default-exported a function; a v2 file exports the result of calling `beforeEach`/`afterEach`/… Any export, named or default, is loaded — and a registration you forget to export does nothing, so export it. **`utils.request` is keyed by Selector**, not by `"METHOD url"`, and its `forStatusCode` option is gone because the Selector carries the status. ```diff await utils.request('POST http://localhost:3000/astronauts', { body }, { runHooks: false }); await utils.request('POST /astronauts (application/json) -> 201 (application/json)', { body }, { runHooks: false }); ``` ### 4. Delete the old tree [Section titled “4. Delete the old tree”](#4-delete-the-old-tree) ```bash rm -rf .thymian/samples ``` Then, optionally, turn on the type gate: ```bash npx thymian sampler init ``` * .thymian/sampler/ * generated/ committed, generated, do not edit * … * hooks/ your hooks * … * tsconfig.json scaffolded once — yours from then on Commit `generated/`, and remove `.thymian/samples` from any `.gitignore` or CI step that referenced it. ## Things that used to break and no longer can [Section titled “Things that used to break and no longer can”](#things-that-used-to-break-and-no-longer-can) * **Renaming an `info.title` or a server URL.** Selectors are host-stripped and carry no source name, so neither is a change at all. * **Editing a description.** The generated surface is compared canonicalized, so prose changes are not drift. * **Reordering your document.** The catalog is sorted by Selector. * **Moving your hook files.** A hook is anchored by its Selector, not its path. * **Adding a status code or a media type.** Existing Selectors keep meaning what they meant; the new ones are simply new. What *does* now break loudly, and on purpose: a renamed path, a removed operation or a changed body shape becomes a compile error at the line of every hook that relied on it. That is the trade — see [the drift gate](/guides/sampler/upgrading-thymian/). Note If you merge a report generated before this move with one generated after it, note that `thymian report merge` does not re-render either fragment: the merged output shows a transaction under both spellings, the old display string (`GET /launches - */* → 200 OK - application/json`) from the older fragment and the Selector (`GET /launches (*/*) -> 200 (application/json)`, ADR-0023) from the newer one. Nothing in Thymian keys on either spelling, so this is cosmetic and resolves itself once every input report is regenerated on the current version. # Upgrading Thymian with a Sampler Setup > The one ritual to remember when you bump the Thymian dependency — and what the drift gate is telling you. The ritual **Upgrade Thymian and run `thymian sampler sync` in the same pull request.** That is the whole thing. The rest of this page is why, and what to do when the gate goes red anyway. ## Why [Section titled “Why”](#why) The files under `.thymian/sampler/generated/` are committed, and they are the baseline the drift gate compares against. They have lock-file semantics: they record what your API description said *and* how this version of Thymian expresses it. So a Thymian upgrade that changes type emission — a better body type, a new field on the hook surface — makes `sampler sync --check` red, in exactly the same way an actual API change would. Nothing is wrong; the baseline is simply from the previous version. Running `sync` in the same pull request as the bump turns that into one explicit, reviewable diff, next to the dependency change that caused it. Running it later turns it into a red build on somebody else’s branch. Note If you have not run `thymian sampler init`, none of this applies: with nothing committed there is nothing to be behind, and your hooks keep running either way. ## Reading the gate [Section titled “Reading the gate”](#reading-the-gate) Two commands, and they answer different questions. ### `thymian sampler sync --check` [Section titled “thymian sampler sync --check”](#thymian-sampler-sync---check) *Are the committed types current?* It writes nothing and exits non-zero if regeneration would change them. This is the CI gate. ```bash npx thymian sampler sync --check ``` ```plaintext The committed sampler types are out of sync with this API description: generated/request-types.d.ts Run "thymian sampler sync" and commit the result. ``` ### `thymian sampler validate` [Section titled “thymian sampler validate”](#thymian-sampler-validate) *Do the hooks still fit the API description as it is now?* It regenerates the surface in memory, compares it, and type-checks your hooks against the fresh version. | committed vs fresh | hooks compile | verdict | | ------------------ | ------------- | ---------------------------------------------------------------------------------- | | identical | yes | silent | | **differs** | **yes** | **warning** — run `sampler sync` | | differs | no | **error** — breaking drift: `sync`, then fix the hooks | | identical | no | **error** — the hooks do not compile. Nothing drifted, so `sync` is not the remedy | The middle row is the Thymian-upgrade case, and the additive-API-change case: the types are behind, but nothing you wrote is wrong. `validate` says so and exits zero. The last row is the one that needs you. It names the hook and the line: ```plaintext ✖ hooks/seed.ts (export "seedAstronaut"): beforeEach targets the selector "GET /astronauts/{id} -> 200 (application/json)", which names no transaction in the loaded API description ✖ hooks/seed.ts:4:3 — TS2345: Argument of type '"GET /astronauts/{id} -> 200 (application/json)"' is not assignable to parameter of type 'HookTarget'. Breaking drift: the API description no longer matches these hooks. Fix them, run "thymian sampler sync", and commit the result. ``` ## What is not drift [Section titled “What is not drift”](#what-is-not-drift) By construction, none of these change the committed surface: * editing a `description` or a `summary` anywhere in your API description * renaming `info.title`, or changing a server URL * reordering paths, operations or schemas in your document * reformatting the committed files The comparison strips comments and formatting, and the surface is emitted in a sorted, source-order-independent way. So a meaningless diff never blocks a build. ## A suggested CI shape [Section titled “A suggested CI shape”](#a-suggested-ci-shape) ```yaml - run: npx thymian sampler sync --check # the committed types are current - run: npx thymian sampler validate # the hooks still fit the description - run: npx thymian test # and the API behaves ``` `validate` exits zero on the warning row, so a Thymian upgrade that only shifts type emission fails at `sync --check` — the step whose fix is one command — and not in the middle of your test run. # Getting Started > Getting Started with Thymian This guide will walk you through the first steps of using Thymian. Let’s get started right away! ## Prerequisites [Section titled “Prerequisites”](#prerequisites) * You need **Node.js 22** or newer — we build and test on **22**, **24** and **26** * Any Swagger/OpenAPI specification file (OpenAPI version 3.2 is currently not supported) ## Installation [Section titled “Installation”](#installation) You won’t need to install Thymian locally, we will just use `npx` for this. Check if you can run Thymian by running the following command: ```bash npx thymian --version ``` ## First Run Without a Config [Section titled “First Run Without a Config”](#first-run-without-a-config) Now navigate to your project’s root directory and run Thymian directly against your API description: ```bash npx thymian lint --spec openapi:openapi.yaml ``` This is the fastest onboarding path. It lets you validate one API immediately without creating a configuration file first. If you want to keep that setup for future runs, generate a reusable config afterward: ```bash npx thymian generate config ``` This command will: 1. Detect your OpenAPI specification file 2. Let you choose which file to use when multiple specs are found 3. Create a `thymian.config.yaml` configuration file in your project ### Understanding the Configuration File [Section titled “Understanding the Configuration File”](#understanding-the-configuration-file) After running `generate config`, you’ll have a `thymian.config.yaml` file that looks like this: ```yaml # Thymian Configuration # Generated by `thymian generate config` specifications: - type: openapi location: ./path/to/your/openapi.yaml rules: {} ruleSets: - '@thymian/rules-rfc-9110' ruleSeverity: error # will only show errors traffic: [] plugins: '@thymian/plugin-openapi': {} '@thymian/plugin-http-linter': {} '@thymian/plugin-request-dispatcher': {} '@thymian/plugin-sampler': {} '@thymian/plugin-reporter': options: formatters: markdown: {} '@thymian/plugin-http-tester': {} '@thymian/plugin-http-analyzer': {} ``` **Key sections:** * **`specifications`**: Lists your API specification files. Each entry has a `type` (e.g., `openapi`) and `location` (path to the file). * **`rules`**: Per-rule overrides. Use this to change a rule’s severity or pass rule-specific options (empty `{}` means use defaults). * **`ruleSets`**: Which rule sets to apply. The default is `@thymian/rules-rfc-9110` for HTTP conformance rules. * **`ruleSeverity`**: The minimum severity level to report (`error`, `warn`, or `hint`). * **`traffic`**: Recorded traffic inputs for the `thymian analyze` workflow (empty `[]` means none configured). * **`plugins`**: The Thymian plugins to load. Plugins handle parsing, validation, reporting, and more. You can use this dedicated reference page for all supported keys, defaults, examples, and precedence rules: * [Thymian Configuration File](/references/configuration/) Tip You can list your loaded plugins via: ```bash npx thymian plugins list ``` ## Running With a Saved Configuration [Section titled “Running With a Saved Configuration”](#running-with-a-saved-configuration) After generating a config file, run your next analysis from your project’s root directory: ```bash npx thymian lint ``` This command will: 1. **Load your OpenAPI specification** Loads your OpenAPI specification(s) and transforms them into a single, unified model (the Thymian format). 2. **Load HTTP Rules** Loads all rule sets specified in the configuration file under `ruleSets`. In the example of this guide, we’re using the rule set extracted from [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html). 3. **Static analysis** Applies all loaded rules to your OpenAPI specification. Each rule checks for specific HTTP conformance issues and generates results based on the findings. 4. **Display results** Displays all found issues in the terminal. ## Understanding the Results [Section titled “Understanding the Results”](#understanding-the-results) Thymian will output results in a structured format. Here is an exemplary output from the HTTP linter: ```console @thymian/plugin-http-linter ───────────────────────────────────────────────────── 34 HTTP rules run successfully. 2 rules reported a violation. PUT /launches/{id} (application/json) -> 401 ✖ error: The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource. rfc9110/server-must-send-www-authenticate-header-for-401-response DELETE /launches/{id} -> 401 ✖ error: The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource. rfc9110/server-must-send-www-authenticate-header-for-401-response Found 2 errors, 0 warnings and 0 hints. ``` In this example, two problems were found in the OpenAPI specification. And since we configured the linter to only show errors, both of them are marked as such. Let’s take a closer look and analyze the first issue in detail. ### Severity Level [Section titled “Severity Level”](#severity-level) ```console PUT /launches/{id} (application/json) -> 401 ✖ error: The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource. rfc9110/server-must-send-www-authenticate-header-for-401-response ``` Each problem found is assigned a `severity` level based on the rule it was triggered by. In this case, the severity level is `error`. This indicates that the issue violates the HTTP specification and can cause issues with interoperability or performance, for example. Besides the `error` severity level, there are also `warn` and `hint`, indicating a potential conformance issue and a suggestion for a truly optional mechanism of HTTP, respectively. | Symbol | Severity | Description | | ------ | -------- | ---------------------------------------------------------------------------- | | ✖ | error | Problem that needs to be fixed. | | ⚠ | warn | Problem that should be fixed. May be left as is under certain circumstances. | | ℹ | hint | Only a suggestion. | When no problems are found, Thymian displays: `✓ No problems found`. Tip You can override the minimum reported severity at the command line with the `--rule-severity` flag. For example, to also see warnings and hints: ```bash npx thymian lint --rule-severity hint ``` This overrides the `ruleSeverity` value set in your configuration file. ### Description [Section titled “Description”](#description) ```console PUT /launches/{id} (application/json) -> 401 ✖ error: The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource. rfc9110/server-must-send-www-authenticate-header-for-401-response ``` After the severity level, the description of the problem is shown. This explains what the rule requires. This description is the way to understand the problem and how to fix it. ### Location [Section titled “Location”](#location) ```console PUT /launches/{id} (application/json) -> 401 ✖ error: The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource. rfc9110/server-must-send-www-authenticate-header-for-401-response ``` Next, we look at the exact location of the problem in the OpenAPI specification. As shown above, the problem is located in the `401` response for the `PUT /launches/{id}` operation. So to fix this issue, you would need to navigate to the corresponding operation in the Swagger/OpenAPI specification and add the missing `WWW-Authenticate` header to the `401` response definition. This could look like this: ```diff '401': description: Unauthorized headers: WWW-Authenticate: schema: type: string ``` ### Source [Section titled “Source”](#source) ```console PUT /launches/{id} (application/json) -> 401 ✖ error: The server generating a 401 response MUST send a WWW-Authenticate header field containing at least one challenge applicable to the target resource. rfc9110/server-must-send-www-authenticate-header-for-401-response ``` Finally, we look at the rule that triggered the problem. In this case, the rule is `rfc9110/server-must-send-www-authenticate-header-for-401-response`. ## Summary [Section titled “Summary”](#summary) What did you just do? You have linted your OpenAPI specification and checked your API description for potential HTTP conformance issues. Based on your configured `ruleSeverity` Thymian shows you problems, that **MUST**, **SHOULD** or **MAY** be fixed. If you fixed all problems, you’re ready for your next steps with Thymian and to spice up your API 🌿! ## Next Steps [Section titled “Next Steps”](#next-steps) To statically analyze your OpenAPI specification was just your first step. Thymian has much more to offer and can be your swiss army knife for your HTTP API development process. ### Learn the three core workflows [Section titled “Learn the three core workflows”](#learn-the-three-core-workflows) If you want tutorial-style guidance for the full Thymian lifecycle, continue with these guides: * [Use `thymian lint` to review your API design](/tutorials/thymian-lint-workflow/) * [Use `thymian test` to validate a live API](/tutorials/thymian-test-workflow/) * [Use `thymian analyze` to inspect recorded traffic](/tutorials/thymian-analyze-workflow/) ### Extending Thymian with Plugins [Section titled “Extending Thymian with Plugins”](#extending-thymian-with-plugins) Don’t want to add yet another tool to your development process? Thymian’s plugin architecture lets you integrate tools you’re already using by integrating them as [plugin](/guides/plugin-developers-guide/). It even provides the possibility to write plugins in any of your favorite languages or to connect remote plugins. For this use case, Thymian provides a proxy plugin. This allows you to connect your plugins via WebSockets. See [this guide](/guides/remote-plugins/how-to-develop-a-remote-plugin/) to learn how to develop such a plugin. # HTTP Conformance > A comprehensive analysis of HTTP conformance, its architectural imperatives, and the out-of-the-box benefits of adhering to RFC 9110 semantics. ## Introduction: The Essence of HTTP Conformance [Section titled “Introduction: The Essence of HTTP Conformance”](#introduction-the-essence-of-http-conformance) The Hypertext Transfer Protocol (HTTP) is often mistakenly viewed in practice as a simple transport protocol for JSON payloads. This misapprehension leads to architectures that ignore fundamental principles of the web, instead building complex, proprietary business logic to solve problems that the standard has long since addressed. The core of the protocol, defined in **RFC 9110 (HTTP Semantics)**, is not a transport mechanism but a semantic framework—a common language and behavioral model for a globally distributed hypermedia system.[1](#user-content-fn-1)[2](#user-content-fn-2) RFC 9110 establishes the “overall architecture of HTTP” and defines the “common terminology”.[1](#user-content-fn-1)[2](#user-content-fn-2)[3](#user-content-fn-3) Conformance in the context of RFC 9110 is therefore a profound contract. It demands adherence *both* to the message syntax (e.g., HTTP/1.1 or HTTP/2) and, crucially, to the *semantics* of the protocol elements.[1](#user-content-fn-1)[4](#user-content-fn-4) A client or server that sends syntactically correct messages but violates the defined semantics (e.g., using a “safe” `GET` request to delete data) will inevitably fail to interoperate with standard components.[1](#user-content-fn-1) The architectural costs of non-conformance are immense. Every semantic deviation from the standard—be it the incorrect use of HTTP methods, the faulty interpretation of status codes, or the ignorance of Caching directives—must be compensated for by proprietary “application logic.” This logic must be individually implemented, maintained, and versioned in *every* client and *every* server. This creates extremely tight coupling, destroys interoperability, and leads to massive, long-term technical debt that undermines the entire system’s longevity, security, and scalability. Conversely, conformance is the decisive enabler. Strict adherence to the standards is the explicit “entry ticket” to utilize a global ecosystem of highly optimized, generic intermediaries.[2](#user-content-fn-2)[5](#user-content-fn-5) Proxies, Content Delivery Networks ( CDNs), Web Application Firewalls (WAFs), and browser caches are all designed to understand and act upon the semantics of RFC 9110 “out-of-the-box.” Tim Berners-Lee himself, during the standardization of HTTP/1.1, emphasized the significant advantages in performance, security, and interoperability that result from conformance.[6](#user-content-fn-6) The Internet Engineering Task Force (IETF) deliberately formalized this separation in modern HTTP specifications. The specifications were strategically split: RFC 9110 (Semantics) and RFC 9111 (Caching) are *separate* from the transport definitions in RFC 9112 (HTTP/1.1), RFC 9113 (HTTP/2), and RFC 9114 (HTTP/3).[3](#user-content-fn-3)[7](#user-content-fn-7) The purpose of this separation is to allow the transport “how” (the individual protocol versions) to evolve independently of the stable, underlying semantic “what” (the interaction model).[3](#user-content-fn-3) For architects, this means: Adherence to RFC 9110 is more fundamental and more important to an application’s longevity than the choice between HTTP/2 or HTTP/3. A semantic violation is an architectural error that cannot be fixed by a transport upgrade. The “out-of-the-box” benefits [8](#user-content-fn-8) that are often taken for granted—such as global scalability [9](#user-content-fn-9) or instant caching [10](#user-content-fn-10)—are no accident. They are the direct and measurable result of adhering to this semantic contract. Non-conformance is therefore not just an implementation error [11](#user-content-fn-11); it is an active *exclusion* from the ecosystem of standardized web infrastructure. ## The HTTP Standard’s Solution Framework: Categorized Mechanisms [Section titled “The HTTP Standard’s Solution Framework: Categorized Mechanisms”](#the-http-standards-solution-framework-categorized-mechanisms) The HTTP standard provides a comprehensive solution framework for the core problems of distributed systems. In practice, these solutions are often replaced by proprietary “bad application logic,” leading to the disadvantages previously described. The following analysis catalogs the key standard solutions offered by RFC 9110 and related specifications. ### Solution Area 1: Unambiguous Semantics and State Transitions (Methods) [Section titled “Solution Area 1: Unambiguous Semantics and State Transitions (Methods)”](#solution-area-1-unambiguous-semantics-and-state-transitions-methods) HTTP’s primary mechanism for defining the *intent* of a client request is its methods.[12](#user-content-fn-12)[13](#user-content-fn-13) They are the contract for what kind of action the server is expected to perform. RFC 9110 defines clear semantic properties for these methods. **Safe Methods (Safe):** Methods like `GET`, `HEAD`, and `OPTIONS` are defined as “safe.”[13](#user-content-fn-13)[14](#user-content-fn-14) This is a guarantee from the client to the server that the request has *no intended side effects* on the server’s state. They are solely for retrieving information. This guarantee is used by intermediaries like crawlers or prefetching mechanisms. The anti-pattern of using `GET` for write actions (e.g., `GET /deleteUser?id=123`) [15](#user-content-fn-15) breaks this fundamental guarantee. A search engine crawler or an aggressive browser prefetcher, operating under the assumption that `GET` requests are safe, could unintentionally delete data on the server simply by “visiting” this link. The conformant use of `DELETE` or `POST` is thus a native, “out-of-the-box” defense against the normal behavior of web infrastructure. **Idempotent Methods (Idempotent):** Methods like `GET`, `HEAD`, `PUT`, and `DELETE` are defined as “idempotent.”[13](#user-content-fn-13)[14](#user-content-fn-14) Idempotency means that *multiple identical requests have the same net effect on the server’s state as a single request*.[13](#user-content-fn-13)[14](#user-content-fn-14) The `POST` method is explicitly defined as *not* idempotent.[13](#user-content-fn-13)[14](#user-content-fn-14) This distinction is not an academic nicety but a standard solution that replaces complex, client-side “application logic.” A common problem in distributed systems is handling network errors (e.g., a timeout) where the client does not know if its request reached the server. Caution A client sends a request (e.g., `DELETE /resource/123`) and receives a network timeout. Unsure if the resource was deleted, the client must implement complex, stateful recovery logic: 1. Send `GET /resource/123` to check the state. 2. *If* the resource still exists (Status `200`), send the `DELETE` request *again*. 3. *If* the resource does not exist (Status `404`), the first `DELETE` request was successful, and the client must do nothing. This is error-prone, proprietary logic. Tip The client *knows* that the `DELETE` method is idempotent.[13](#user-content-fn-13)[14](#user-content-fn-14) In the event of a network timeout, the client *does not need* to check the state. It simply sends the `DELETE` request *safely again* (a “safe retry”). If the first request was successful, the second request will target the already-deleted resource (now `404`) or the same state (`204`), but the server’s *final state* remains correct. The standard’s semantics have eliminated the need for complex, stateful “ check-if-deleted“ logic in the client. **Cacheable Methods (Cacheable):** Methods like `GET` and `HEAD` are defined as primarily cacheable. `POST` can also be cacheable under certain conditions, but it is not in the default configuration of most intermediaries.[13](#user-content-fn-13)[16](#user-content-fn-16) The following table summarizes the contractual obligations of HTTP method semantics according to RFC 9110. **Table 1: Semantic Properties of HTTP Methods (RFC 9110)** | Method | Purpose (Simplified) | Is Safe | Is Idempotent | Primarily Cacheable | | ------------- | ---------------------------------------------------------- | ------- | ------------- | ------------------- | | **`GET`** | Retrieve a representation | **Yes** | **Yes** | **Yes** | | **`HEAD`** | Retrieve only headers of a representation | **Yes** | **Yes** | **Yes** | | **`POST`** | Process a resource / create a subordinate resource | No | No | (Conditional) | | **`PUT`** | (Fully) replace or create a resource | No | **Yes** | No | | **`DELETE`** | Delete a resource | No | **Yes** | No | | **`OPTIONS`** | Query communication options for a resource | **Yes** | **Yes** | No | | **`TRACE`** | Perform a “loop-back” test of the request | **Yes** | **Yes** | No | | **`CONNECT`** | Establish a tunnel to the server [17](#user-content-fn-17) | No | No | No | *Data sources for Table 1: [13](#user-content-fn-13)[14](#user-content-fn-14)[17](#user-content-fn-17)* ### Solution Area 2: Reliable Communication and Error Handling (Status Codes) [Section titled “Solution Area 2: Reliable Communication and Error Handling (Status Codes)”](#solution-area-2-reliable-communication-and-error-handling-status-codes) HTTP status codes are the server’s primary mechanism for communicating the *result* of the requested semantic action in a standardized way. They are divided into five classes that allow for immediate categorization of the outcome.[18](#user-content-fn-18)[19](#user-content-fn-19)[20](#user-content-fn-20) The most fundamental distinction the standard makes is that of *responsibility* for the error [21](#user-content-fn-21): * **`2xx` (Successful):** The request was successful and understood.[18](#user-content-fn-18)[19](#user-content-fn-19) * **`4xx` (Client Error):** The request itself is faulty (e.g., invalid syntax, resource not found, lacking permissions). The server *could not or would not* process the request. The client *must not repeat the request without modification*.[18](#user-content-fn-18)[21](#user-content-fn-21)[22](#user-content-fn-22)[23](#user-content-fn-23) * **`5xx` (Server Error):** The request was syntactically and semantically valid, but the server encountered an *internal problem* that prevented it from fulfilling the request.[18](#user-content-fn-18)[21](#user-content-fn-21)[22](#user-content-fn-22) This 4xx/5xx separation is not merely informative; it is a critical mechanism for *automating the assignment of responsibility* in distributed systems. Every “out-of-the-box” monitoring tool, API gateway, or observability stack relies on this semantic. Caution A server returns `500 Internal Server Error` because the client sent invalid data (e.g., a badly formatted date).[22](#user-content-fn-22) Consequence The monitoring system (e.g., an API gateway) detects a `5xx` spike and alerts the *server operations team* (SREs). This team begins a costly investigation on the server, only to find it is functioning perfectly and the problem lies with the client. This leads to “alert fatigue” and wastes valuable operational resources. Tip The server recognizes the invalid data and correctly responds with `400 Bad Request`.[23](#user-content-fn-23) The monitoring system registers this as a client error. It can now automatically alert the *client’s development team* or even temporarily rate-limit the client. The correct use of status codes automatically routes the error to its source. This is what is meant by “Troubleshooting made easy” and “Uniform error handling.”[24](#user-content-fn-24) Furthermore, specific status codes define the *next permissible steps* in a protocol, acting as a machine-readable state machine: * **`201 Created`:** Signals that a resource was successfully created. The response *should* include a `Location` header specifying the URI of the *new* resource.[15](#user-content-fn-15)[19](#user-content-fn-19) * **`204 No Content`:** Signals success but informs the client that no response body will be sent, intentionally (e.g., after a `DELETE` request).[22](#user-content-fn-22) The client does not need to wait for a body. * **`401 Unauthorized`:** Signals that authentication is required. This response *must* include a `WWW-Authenticate` header that provides the “challenge” (i.e., the required authentication methods) to the client.[1](#user-content-fn-1)[25](#user-content-fn-25) * **`403 Forbidden`:** Signals that authentication was successful (or is not required), but the client *lacks permission* for the requested action.[24](#user-content-fn-24) This is semantically and fundamentally different from `401`. * **`409 Conflict`:** Signals that the request could not be processed because it conflicts with the *current state* of the target resource (e.g., a versioning conflict).[22](#user-content-fn-22) ### Solution Area 3: Performance and Scalability (Caching, RFC 9111) [Section titled “Solution Area 3: Performance and Scalability (Caching, RFC 9111)”](#solution-area-3-performance-and-scalability-caching-rfc-9111) HTTP caching, defined in **RFC 9111**, is the standard solution for drastically reducing latency and network overhead.[5](#user-content-fn-5)[26](#user-content-fn-26) It is a local store for response messages.[10](#user-content-fn-10) The standard defines a sophisticated, two-stage system that goes far beyond what most proprietary “in-app” caches provide. **Mechanism 1: Expiration** This is the first stage of optimization. The server tells the cache (browser or intermediary) via the `Cache-Control` response header how long a representation is considered “fresh,” typically via `max-age` (e.g., `Cache-Control: max-age=3600` for one hour).[27](#user-content-fn-27)[28](#user-content-fn-28) As long as the response is fresh, it is served *without any network request* to the origin server.[29](#user-content-fn-29) This completely eliminates both server load and network latency.[30](#user-content-fn-30) **Mechanism 2: Validation** This is the second stage of optimization, which applies when the response is “stale” (expired). Instead of blindly re-requesting the *entire* resource, the cache *must* check with the server (“revalidate”).[29](#user-content-fn-29)[30](#user-content-fn-30) To make this process efficient, the server provides “validators”: * **`ETag` (Entity Tag):** An opaque token that represents a specific version of the resource (e.g., a hash of the content).[30](#user-content-fn-30)[31](#user-content-fn-31) * **`Last-Modified`:** A timestamp of the last modification.[31](#user-content-fn-31) The cache sends these validators in a *conditional request* (e.g., `If-None-Match: "etag-value"`). The server now performs a quick check: * *If* the resource has *not* changed, the server responds with **`304 Not Modified`**. This response has an *empty body*.[29](#user-content-fn-29)[30](#user-content-fn-30)[31](#user-content-fn-31) The cache now knows its “stale” copy is “fresh” again and serves it. * *If* the resource has changed, the server responds with `200 OK` and the *new, complete* resource. This two-stage system is architecturally superior to implementing a proprietary “in-app” cache (e.g., in Redis). A typical in-app cache often only implements Stage 1 (Expiration). When the Redis entry expires, the server *must* regenerate the data (e.g., a 10 MB JSON document) and send the *full response* to the client. The HTTP model is more efficient: even if `max-age` (Stage 1) has expired, Stage 2 (Validation) can still save 10 MB of bandwidth by sending a `304` if the data has not *factually* changed. **The `Cache-Control` “API”** The `Cache-Control` header is an “API” that allows the origin server to programmatically control the behavior of a complex, global ecosystem of intermediaries (CDNs, proxies).[32](#user-content-fn-32)[33](#user-content-fn-33) * **`private` vs. `public`:** The standard distinguishes between `private` caches (for a single user, e.g., a browser cache) and `shared` caches (for many users, e.g., a CDN, proxy).[5](#user-content-fn-5)[26](#user-content-fn-26) With `Cache-Control: private`, the server forbids a CDN from storing the response. With `Cache-Control: public`, it explicitly permits it.[28](#user-content-fn-28) * **Security through Semantics (Authentication):** The standard (RFC 9111) is “secure-by-default.” It mandates that a `shared` cache (CDN) *must never* store a response to a request containing an `Authorization` header, as it is by definition user-specific.[5](#user-content-fn-5)[26](#user-content-fn-26) This response may only be stored in a `private` cache (the user’s browser). A server can *only* override this behavior with explicit directives like `public` or `s-maxage`.[5](#user-content-fn-5) ### Solution Area 4: Flexibility and Representation (Content Negotiation) [Section titled “Solution Area 4: Flexibility and Representation (Content Negotiation)”](#solution-area-4-flexibility-and-representation-content-negotiation) Content Negotiation is the HTTP-conformant mechanism for serving different representations of the same resource under *a single, stable URI*.[34](#user-content-fn-34) This solves the problem of different clients needing different formats (e.g., JSON vs. XML), languages (e.g., German vs. English), or encodings (e.g., Gzip vs. Brotli). **Server-driven Negotiation (Proactive Negotiation):** This is the standard mechanism.[34](#user-content-fn-34) 1. The client sends *Accept headers* in its request, listing its preferences and capabilities. * **`Accept`:** Defines the preferred media types (MIME types). Example: `Accept: application/json, application/xml;q=0.8` (meaning: “I prefer JSON, but XML is acceptable with a priority of 0.8”).[35](#user-content-fn-35)[36](#user-content-fn-36)[37](#user-content-fn-37) * **`Accept-Language`:** Defines the preferred languages. Example: `Accept-Language: de-DE, en-US;q=0.7`.[38](#user-content-fn-38) * **`Accept-Encoding`:** Defines the supported compression algorithms. Example: `Accept-Encoding: gzip, br`.[34](#user-content-fn-34)[38](#user-content-fn-38) 2. The server analyzes these headers, compares them with its available representations, and selects the *best* matching variant. 3. The server sends the selected representation back, informing the client of its choice with response headers like `Content-Type`, `Content-Language`, and `Content-Encoding`.[35](#user-content-fn-35)[38](#user-content-fn-38) **Reactive Negotiation:** If the server cannot find a suitable representation, it can respond conformantly with `406 Not Acceptable` or offer a list of available options with `300 Multiple Choices`.[34](#user-content-fn-34) This standard mechanism is superior to “bad application logic,” which typically solves the same problem using proprietary URL parameters (e.g., `?format=json`) or URI structure (e.g., `/resource.json` vs. `/resource.xml`). The question of why negotiation is superior to a URL parameter is answered clearly in the technical community: “ Standardization.“[39](#user-content-fn-39) A generic client *understands* `Accept` headers “out-of-the-box.” It *does not understand* a proprietary `?format=` parameter. By adhering to the standard, the resource remains accessible at a stable URI for *any* conformant client (including future, unknown clients) without them needing to know the API’s proprietary conventions.[39](#user-content-fn-39) Furthermore, using `Accept` decouples the server’s evolution from the client base. A server (V1) might, for example, only serve XML. A client (V1) sends `Accept: application/xml`. Later, the server (V2) is updated to *also* serve JSON. The old client (V1) continues to work *unchanged*, as its request is still served correctly. A new client (V2) can now send `Accept: application/json` and receive the more modern format. The URI `/resource/123` remains stable for both clients.[34](#user-content-fn-34) This prevents the need for hard API versioning (e.g., `/v2/...`). ### Solution Area 5: Security and State (Authentication and State Management) [Section titled “Solution Area 5: Security and State (Authentication and State Management)”](#solution-area-5-security-and-state-authentication-and-state-management) **Part A: HTTP Authentication (RFC 9110)** Contrary to the assumption that HTTP is designed only for cookie-based authentication, the standard (historically RFC 7235, now integrated into RFC 9110) defines a powerful, schema-agnostic *framework* for authentication.[1](#user-content-fn-1)[4](#user-content-fn-4)[40](#user-content-fn-40)[41](#user-content-fn-41)[42](#user-content-fn-42)[43](#user-content-fn-43) It is a stateless challenge-response mechanism.[40](#user-content-fn-40)[41](#user-content-fn-41)[44](#user-content-fn-44) The standard flow is as follows [25](#user-content-fn-25)[41](#user-content-fn-41)[44](#user-content-fn-44): 1. **Anonymous Request:** The client requests a protected resource (e.g., `GET /admin`). 2. **Server Challenge:** The server rejects the request with **`401 Unauthorized`**. It *must* include a \* \*`WWW-Authenticate`\*\* header in this response.[45](#user-content-fn-45) This header defines the “challenge”—the methods the server accepts (e.g., `WWW-Authenticate: Basic realm="Admin Area"` or `WWW-Authenticate: Bearer` for tokens). 3. **Client Response:** The client (e.g., a browser) can now prompt the user for credentials. It repeats the request, adding the **`Authorization`** header with the credentials in the format requested by the server (e.g., `Authorization: Basic YWxhZGRpbjp...`).[1](#user-content-fn-1)[46](#user-content-fn-46) The advantage of this framework is its flexibility. The server can offer multiple schemes to the client (e.g., `WWW-Authenticate: Digest...`, `WWW-Authenticate: Bearer...`).[45](#user-content-fn-45)[47](#user-content-fn-47) The client *chooses* the most secure scheme it understands.[45](#user-content-fn-45)[46](#user-content-fn-46) “Bad application logic” reinvents this framework, typically through a proprietary header (e.g., `X-Api-Key`). This breaks interoperability with standard tools. A browser or a tool like `curl` *understands* the `401`/`WWW-Authenticate` flow “out-of-the-box” and can react accordingly (e.g., with a password prompt).[41](#user-content-fn-41) These tools have no knowledge of a proprietary `X-Api-Key` scheme. **Part B: HTTP State Management (RFC 6265 - Cookies)** Since HTTP itself is a stateless protocol [1](#user-content-fn-1), **RFC 6265 (HTTP State Management Mechanism)** is the standard that allows a server to store *state* in the user agent (browser) to manage a “stateful session.”[48](#user-content-fn-48)[49](#user-content-fn-49)[50](#user-content-fn-50)[51](#user-content-fn-51) The standard defines not only the `Set-Cookie` and `Cookie` headers [49](#user-content-fn-49)[52](#user-content-fn-52) but also—crucially—the *security attributes* that serve as standard solutions for well-known attack vectors: * **`Secure` attribute:** Ensures the cookie is only sent over “secure” channels (i.e., HTTPS).[53](#user-content-fn-53)[54](#user-content-fn-54) This is the standard solution against sniffing session cookies in insecure networks. * **`HttpOnly` attribute:** Prevents the cookie from being accessed via client-side scripts (i.e., `document.cookie`).[54](#user-content-fn-54)[55](#user-content-fn-55) This is the primary, “out-of-the-box” line of defense against session cookie theft via Cross-Site Scripting (XSS) attacks.[55](#user-content-fn-55) * **`SameSite` attribute (Lax, Strict, None):** Controls whether a cookie is sent with cross-site requests (i.e., requests from a different domain).[54](#user-content-fn-54)[56](#user-content-fn-56)[57](#user-content-fn-57) This is the primary, “out-of-the-box” line of defense against Cross-Site Request Forgery (CSRF) attacks.[58](#user-content-fn-58) Non-conformant implementations (e.g., setting a session cookie without `HttpOnly` and `SameSite=Strict`) actively *create* the attack vectors for XSS and CSRF.[59](#user-content-fn-59)[60](#user-content-fn-60) The “application logic” must then arduously mitigate these threats *again*, typically by implementing Content Security Policies (CSP) and anti-CSRF tokens. Conformance with RFC 6265 is the first, cheapest, and strongest line of defense that the standard provides natively. ## Analysis: HTTP Anti-Patterns and “Bad Application Logic” [Section titled “Analysis: HTTP Anti-Patterns and “Bad Application Logic””](#analysis-http-anti-patterns-and-bad-application-logic) The refusal to use standard solutions leads to a series of well-known anti-patterns.[15](#user-content-fn-15)[61](#user-content-fn-61) These anti-patterns are often a direct symptom of “business logic” or “bad application logic” having taken control of the protocol’s behavior. ### Anti-Pattern 1: The “200 OK” Lie (Ignoring Status Codes) [Section titled “Anti-Pattern 1: The “200 OK” Lie (Ignoring Status Codes)”](#anti-pattern-1-the-200-ok-lie-ignoring-status-codes) This is one of the most harmful anti-patterns.[15](#user-content-fn-15) Instead of using the semantically correct status code, the API *always* returns `HTTP 200 OK`. The “actual” status is wrapped in a proprietary JSON envelope in the body.[22](#user-content-fn-22)[62](#user-content-fn-62)[63](#user-content-fn-63) Danger ```http HTTP/1.1 200 OK Content-Type: application/json { "status": "ERROR", "success": false, "data": null, "error": { "code": "E404", "message": "User not found" } } ``` Tip ```http HTTP/1.1 404 Not Found Content-Type: application/json { "error": "User not found" } ``` *or simply an empty `404` body.* **Drawbacks (Technical Debt):** * **Breaks the Ecosystem:** This anti-pattern blinds *every* standard tool (monitoring, caching, proxies, gateways).[24](#user-content-fn-24) * **Faulty Monitoring:** Monitoring systems see 100% `200 OK` responses and incorrectly report a 100% success rate. The system is down, but the dashboard is “green.” * **Faulty Caching:** A CDN or proxy configured to cache `200` responses might incorrectly cache this *error message* and serve it to other users.[64](#user-content-fn-64)[65](#user-content-fn-65) * **Complex Clients:** The client *must* parse the body of *every* `200` response to find out if the request was *actually* successful.[22](#user-content-fn-22) This doubles the error-handling logic (once for network/HTTP errors, once for the proprietary body error). ### Anti-Pattern 2: Method Tunneling (Ignoring Semantics) [Section titled “Anti-Pattern 2: Method Tunneling (Ignoring Semantics)”](#anti-pattern-2-method-tunneling-ignoring-semantics) This anti-pattern treats HTTP as a “dumb” transport protocol in an RPC (Remote Procedure Call) style.[61](#user-content-fn-61) Every action, regardless of its semantics (read, write, delete), is tunneled over a single method (usually `POST`) to a single endpoint (e.g., `/api`).[15](#user-content-fn-15) Danger ```http POST /api HTTP/1.1 Content-Type: application/json { "action": "getUser", "id": 123 } ``` *instead of `GET /users/123`* ```http POST /api HTTP/1.1 Content-Type: application/json { "action": "deleteUser", "id": 123 } ``` *instead of `DELETE /users/123`* Tip Use the correct method for the specific resource (e.g., `GET /users/123`).[66](#user-content-fn-66) **Drawbacks (Technical Debt):** * **Total Loss of Caching:** Since *every* action is a `POST`, *nothing* can be cached by standard caches (browser, CDN, proxy), as `POST` requests are generally not considered cacheable.[67](#user-content-fn-67)[68](#user-content-fn-68) Every single read request hits the origin server. * **Loss of Idempotency:** The client loses the “safe retry” guarantee for idempotent actions (like `DELETE`), as `POST` is not idempotent.[13](#user-content-fn-13) * **Loss of “Safe” Guarantees:** If `GET` is misused for write operations, there is a risk of unintentional data modification by crawlers.[15](#user-content-fn-15) * **Workarounds:** This anti-pattern is so common that workarounds like the `X-HTTP-Method-Override` header [69](#user-content-fn-69)[70](#user-content-fn-70) were invented to bypass firewalls that only allow `POST`—a “workaround for a workaround” that completely breaks semantics. ### Anti-Pattern 3: “In-App” Caching (Ignoring RFC 9111) [Section titled “Anti-Pattern 3: “In-App” Caching (Ignoring RFC 9111)”](#anti-pattern-3-in-app-caching-ignoring-rfc-9111) Out of ignorance or distrust of HTTP caching, developers implement their own proprietary caching layers within the application (e.g., with Redis or in-memory maps) [71](#user-content-fn-71), while completely ignoring HTTP caching headers.[15](#user-content-fn-15)[61](#user-content-fn-61) Tip Use the `Cache-Control` (for expiration) and `ETag` / `Last-Modified` (for validation) headers.[26](#user-content-fn-26)[30](#user-content-fn-30) **Drawbacks (Technical Debt):** * **Reinventing the Wheel:** Implementing a correct, thread-safe cache invalidation strategy is one of the hardest problems in computer science and is being needlessly reimplemented here. * **Inefficient:** As explained in II.3, this approach almost always lacks the superior two-stage (Expiration + Validation) system of the HTTP standard.[30](#user-content-fn-30) * **Invisible to Intermediaries:** The most severe drawback. The in-app cache is *invisible* to the entire ecosystem. A user’s request from Asia *must* cross the globe, pass through the CDN and proxy (which cannot cache), and hit the origin server in Europe, *just* for the in-app cache to serve the response. A conformant `Cache-Control` header would have allowed the CDN in Asia to serve the response in milliseconds.[10](#user-content-fn-10) ### Anti-Pattern 4: Insecure State Management (Ignoring RFC 6265) [Section titled “Anti-Pattern 4: Insecure State Management (Ignoring RFC 6265)”](#anti-pattern-4-insecure-state-management-ignoring-rfc-6265) This anti-pattern consists of setting authentication or session cookies without using the security attributes provided by the standard.[54](#user-content-fn-54)[55](#user-content-fn-55) Danger `Set-Cookie: session=abc123xyz...` Tip `Set-Cookie: session=abc123xyz...; Secure; HttpOnly; SameSite=Strict` **Drawbacks (Technical Debt):** * **Direct Attack Vectors:** This non-conformance is *not* a theoretical weakness; it *is* the vulnerability. * **XSS Vulnerability:** The absence of `HttpOnly` allows an attacker who finds an XSS flaw to steal the cookie via JavaScript (`document.cookie`) and take over the user’s session.[55](#user-content-fn-55)[59](#user-content-fn-59) * **CSRF Vulnerability:** The absence of `SameSite` allows an authenticated user’s browser to send the cookie with a request from a malicious site (e.g., in an `` tag or form), leading to a Cross-Site Request Forgery.[58](#user-content-fn-58)[60](#user-content-fn-60) * **Increased Complexity:** The “application logic” must now *re-implement* this standard defense, for example, by implementing anti-CSRF tokens—a solution that the `SameSite` attribute would have solved “out-of-the-box” and more robustly. ### Table 2: Comparison: “Application Logic” vs. “HTTP Standard Solution” [Section titled “Table 2: Comparison: “Application Logic” vs. “HTTP Standard Solution””](#table-2-comparison-application-logic-vs-http-standard-solution) This table summarizes the direct confrontation between common proprietary workarounds and the superior standard solutions. | Problem | Anti-Pattern / “Bad Application Logic” | Conformant HTTP Solution (RFC 9110/9111/6265) | “Out-of-the-Box” Benefit of Conformance | | ---------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | **Error Message** | `HTTP 200 OK` + `{ "success": false }` [22](#user-content-fn-22) | `HTTP 4xx` (e.g., `400`, `404`) or `5xx` [22](#user-content-fn-22) | Automatic monitoring, alerting, client handling, no Caching of errors [24](#user-content-fn-24)[65](#user-content-fn-65) | | **State-changing Action** | `POST /api {"action": "deleteUser"}` [15](#user-content-fn-15) | `DELETE /users/{id}` | Idempotency (“safe retry”) [13](#user-content-fn-13), CDN/proxy invalidation [68](#user-content-fn-68), semantic clarity | | **Data Creation** | `POST /api {"action": "createUser"}` | `POST /users` (leading to `201 Created` + `Location` header) [19](#user-content-fn-19) | Discovery of the new resource, semantic clarity [15](#user-content-fn-15) | | **Data Retrieval (Caching)** | `POST /api {"action": "getUser"}` OR `GET /users/{id}` (no cache headers) [15](#user-content-fn-15) | `GET /users/{id}` + `Cache-Control: max-age=...` + `ETag: "..."` [26](#user-content-fn-26)[30](#user-content-fn-30) | CDN, proxy & browser caching (Expiration) [28](#user-content-fn-28) AND bandwidth saving (Validation with `304`) [30](#user-content-fn-30) | | **Format Selection** | Proprietary parameter (e.g., `?format=json`) or URI (`.json`) | `Accept: application/json` header [34](#user-content-fn-34)[35](#user-content-fn-35) | Stable URIs, decoupling of clients, interoperability with generic tools [34](#user-content-fn-34)[39](#user-content-fn-39) | | **Session Security (CSRF)** | `Set-Cookie: session=...` (no `SameSite`) + Anti-CSRF token in app logic | `Set-Cookie:...; SameSite=Strict` [56](#user-content-fn-56) | Native, robust CSRF protection directly in the browser, no complex token logic needed [54](#user-content-fn-54) | | **Session Security (XSS)** | `Set-Cookie: session=...` (no `HttpOnly`) + Content Security Policy (CSP) | `Set-Cookie:...; HttpOnly` [54](#user-content-fn-54) | Native protection against cookie theft via JavaScript, first line of defense [55](#user-content-fn-55) | ## The Multiplier Effect: “Out-of-the-Box” Benefits of Conformance [Section titled “The Multiplier Effect: “Out-of-the-Box” Benefits of Conformance”](#the-multiplier-effect-out-of-the-box-benefits-of-conformance) Adherence to HTTP semantics is not an academic exercise [11](#user-content-fn-11) but a fundamental architectural investment. The “cost” of adherence (i.e., correctly setting headers and using methods/status codes) *unlocks* an ecosystem of generic, highly optimized intermediaries.[2](#user-content-fn-2)[32](#user-content-fn-32) The performance of these standard components (CDNs, proxies, browsers) surpasses that of *any* proprietary “in-app” solution by orders of magnitude. The “out-of-the-box” benefits [8](#user-content-fn-8) are the direct result of this leverage. Intermediaries, from an IBM Proxy Server [32](#user-content-fn-32) to a global CDN like Cloudflare [65](#user-content-fn-65), are programmed to strictly interpret the semantics of RFC 9110 and RFC 9111. Consider two scenarios: * **Scenario A (Non-conformant):** An API uses Anti-Patterns 1 (errors as `200`) and 2 (everything over `POST`). An expensive, global CDN is placed in front of this API. The result: The CDN is *useless*. Every single request is a `POST`, is classified as “not cacheable” (Cache-Miss), and must be forwarded to the origin server.[67](#user-content-fn-67)[68](#user-content-fn-68) The CDN cannot distinguish between success and failure (everything is `200`).[65](#user-content-fn-65) The developer has *disabled* a global infrastructure worth millions. * **Scenario B (Conformant):** An API uses `GET /resource` with `Cache-Control: public, max-age=60` [28](#user-content-fn-28) and an `ETag`.[30](#user-content-fn-30) The CDN serves 99.9% of requests globally from its edge locations without contacting the origin server.[10](#user-content-fn-10) After 60 seconds, it validates efficiently with `If-None-Match` and a `304`.[30](#user-content-fn-30) The server load collapses. The *only* difference between global failure and global scalability in this case was HTTP conformance. ### Benefit 1: Transparent Scalability (CDNs & Proxies) [Section titled “Benefit 1: Transparent Scalability (CDNs & Proxies)”](#benefit-1-transparent-scalability-cdns--proxies) Intermediaries use HTTP semantics for far more than just caching `200 OK` responses: * **Method Caching:** They aggressively cache `GET` and `HEAD`.[16](#user-content-fn-16)[68](#user-content-fn-68) They know that `PUT` and `DELETE` change state and, upon arrival, *automatically invalidate* the cached `GET` responses for that resource.[29](#user-content-fn-29)[68](#user-content-fn-68) An API that only uses `POST` robs the CDN of this cache invalidation capability. * **Status Code Caching (Negative Caching):** CDNs [65](#user-content-fn-65) and proxies [32](#user-content-fn-32) don’t just cache success. They *conformantly* cache `301 Moved Permanently` (often for a long time) and `404 Not Found` (typically for a short time, e.g., 3 minutes).[65](#user-content-fn-65)[72](#user-content-fn-72) This is a critical “out-of-the-box” protection mechanism that shields the origin server from repeated, pointless requests for non-existent resources (e.g., during a denial-of-service attack or from a faulty client). An API that masks errors as `200 OK` loses this protection. * **Authentication Caching (Security Function):** As detailed in II.3, the default behavior of a `shared` cache (CDN) is *security-critical*: It *must not* store a response to a request with an `Authorization` header, as it could contain private data.[5](#user-content-fn-5)[26](#user-content-fn-26) “Bad application logic” (e.g., passing an API key or session token as a *URL parameter*) bypasses this built-in protection. The CDN does not see the `Authorization` header, incorrectly treats the request as anonymous and public, and caches the private response. The next user to request the same URL receives the previous user’s private data (Cache Poisoning). Conformance (using the `Authorization` header) prevents this massive data leak “out-of-the-box.” ### Benefit 2: Increased Security and Resilience (WAFs & Studies) [Section titled “Benefit 2: Increased Security and Resilience (WAFs & Studies)”](#benefit-2-increased-security-and-resilience-wafs--studies) Adherence to HTTP specifications is not just a formality; it is a fundamental security practice. Web Application Firewalls (WAFs) and API gateways use HTTP semantics as a first line of defense to detect anomalies (e.g., a `GET` request with a body, which could indicate a smuggling attack). A non-conformant application is far more difficult for a WAF to protect, as it cannot distinguish “normal” traffic from “abnormal” traffic. The need for conformance is not just theoretical. Empirical studies “harden” this argument by demonstrating the widespread prevalence of non-conformance and its direct security implications. **Evidence 1: CISPA-Studie (2024) - HTTP Conformance** A systematic analysis of HTTP conformance and its security impacts [7](#user-content-fn-7) extracted 106 testable rules from the core RFCs (9110-9113, 6265 etc.) and tested them against real servers. * **Result:** Conformance is extremely low. “Most HTTP systems break at least one rule,” and “more than half of all rules were broken at least once.”[7](#user-content-fn-7) * **Consequences:** These violations are not harmless; they lead directly to known security vulnerabilities [7](#user-content-fn-7): * **HTTP Request Smuggling (HRS):** Caused by semantic violations such as sending a *body in a `304 Not Modified` response*, “incorrect whitespace” in headers, or “forbidden headers” (e.g., `Content-Length` in a `204` response). These violations lead to parser desynchronization between a proxy and the backend server.[7](#user-content-fn-7) * **Cross-Site Scripting (XSS):** Caused by a “missing `Content-Type` header.” When this header is missing, browsers are forced to perform “MIME-sniffing,” which can lead to an uploaded file (e.g., an image) being misinterpreted as HTML or script and executed.[7](#user-content-fn-7) * **Security Policy Bypass:** Caused by “duplicate headers” (e.g., two `Strict-Transport-Security` headers). Different intermediaries (browser, proxy) may pick the first or the last header, leading to inconsistent and potentially insecure processing.[7](#user-content-fn-7) * **Illegal Characters:** Many servers (7 of 9 tested) failed to correctly reject requests with illegal characters (like `CR`, `LF`, `NUL`) in headers with `400 Bad Request`, which can also lead to smuggling attacks.[7](#user-content-fn-7) **Evidence 2: Studie “Non-compliant and Proud” (2008)** This earlier study also confirmed the widespread nature of non-conformance, particularly in the implementation of HTTP methods. It concluded that many websites are “non-compliant out of choice, not necessity”—the servers *could* be conformant but are not, due to misconfigurations.[73](#user-content-fn-73) Taken together, these studies prove that adherence to the specifications is a native, “out-of-the-box” defense against entire classes of complex protocol attacks (like HRS). Non-conformance is an invitation for these attacks. ### Table 3: Evidence-Based Risks of Non-Conformance (based on studies) [Section titled “Table 3: Evidence-Based Risks of Non-Conformance (based on studies)”](#table-3-evidence-based-risks-of-non-conformance-based-on-studies) This table links the rule violations observed in research [7](#user-content-fn-7) directly to the resulting security vulnerabilities and the standard semantics that were violated. | Observed Non-Conformance [7](#user-content-fn-7) | Violated Semantics (RFC) | Potential Security Impact (Out-of-the-Box Loss) | | ------------------------------------------------------------------ | ------------------------------------------------ | ----------------------------------------------------------------------------------------- | | **Body in `304 Not Modified` response** | RFC 9110 (Semantics: `304` must not have a body) | **HTTP Request Smuggling (HRS)** via parser desynchronization [7](#user-content-fn-7) | | **Incorrect Whitespace** (e.g., in header names) | RFC 9110 / 9112 (ABNF syntax) | **HTTP Request Smuggling (HRS)**, parser confusion, filter bypass [7](#user-content-fn-7) | | **Missing `Content-Type` header** | RFC 9110 (Semantics: Define content semantics) | **Cross-Site Scripting (XSS)** via browser MIME-sniffing [7](#user-content-fn-7) | | **Duplicate Security Headers** (e.g., `Strict-Transport-Security`) | RFC 9110 (Header field definitions) | Inconsistent security policy, bypass of protections [7](#user-content-fn-7) | | **Illegal Characters** (`CR`, `LF`, `NUL`) in header values | RFC 9110 (Semantics: Must be rejected as `400`) | **HTTP Request Smuggling (HRS)**, injection attacks [7](#user-content-fn-7) | ## Summary Analysis and Architectural Recommendations [Section titled “Summary Analysis and Architectural Recommendations”](#summary-analysis-and-architectural-recommendations) The analysis presented shows that HTTP conformance, particularly adherence to the semantics of RFC 9110, is not an “ academic exercise“ [11](#user-content-fn-11) or technical dogma. It is a deliberate and fundamental *architectural decision* in favor of robustness, longevity [3](#user-content-fn-3), security, and scalability.[9](#user-content-fn-9) The “business logic” or “bad application logic” observed by the querent, which leads to the bypassing of HTTP standards, is almost invariably a more expensive, proprietary, error-prone, and lower-performance reinvention of an already existing, highly optimized, and globally understood standard function. 1. **Conformance Resolves Complexity:** The standard semantics for idempotency (RFC 9110) [13](#user-content-fn-13), caching (RFC 9111) [26](#user-content-fn-26)[30](#user-content-fn-30), and state management security (RFC 6265) [54](#user-content-fn-54) eliminate the need for complex, proprietary client logic (e.g., “safe-retry” checks, anti-CSRF tokens, in-app caches). 2. **Conformance is the Key to Scalability:** The “out-of-the-box” benefits of a global ecosystem of intermediaries ( CDNs, proxies, browsers) [8](#user-content-fn-8)[32](#user-content-fn-32) are *directly* tied to semantic adherence. Non-conformant APIs (e.g., through method tunneling or `200 OK` error messages) *disable* this infrastructure.[22](#user-content-fn-22)[65](#user-content-fn-65)[67](#user-content-fn-67) 3. **Conformance is an “Out-of-the-Box” Security Feature:** As empirical studies demonstrate [7](#user-content-fn-7), strict adherence to protocol semantics (e.g., correct length specifications, no bodies in `304` responses, correct cookie attributes) is a native line of defense against entire classes of attacks like HTTP Request Smuggling and Cross-Site Scripting. The refusal to be HTTP-conformant is, ultimately, a refusal to leverage the benefits of a globally scaled, highly secure distributed system optimized for over 30 years.[6](#user-content-fn-6) It is the equivalent of provisioning a high-performance CDN [74](#user-content-fn-74) and then, through `POST` method tunneling [15](#user-content-fn-15), effectively degrading it into a simple, expensive load balancer. Adherence to HTTP semantics, as laid out in RFC 9110, is not an obstacle to implementing business logic; it is the indispensable foundation upon which professional, future-proof, and scalable web architectures are built. *** ## Footnotes [Section titled “Footnotes”](#footnote-label) 1. [RFC 9110: HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110.html) [↩](#user-content-fnref-1) [↩2](#user-content-fnref-1-2) [↩3](#user-content-fnref-1-3) [↩4](#user-content-fnref-1-4) [↩5](#user-content-fnref-1-5) [↩6](#user-content-fnref-1-6) [↩7](#user-content-fnref-1-7) [↩8](#user-content-fnref-1-8) 2. [Information on RFC 9110 » RFC Editor](https://www.rfc-editor.org/info/rfc9110) [↩](#user-content-fnref-2) [↩2](#user-content-fnref-2-2) [↩3](#user-content-fnref-2-3) [↩4](#user-content-fnref-2-4) 3. [RFC 9110 - HTTP Semantics - IETF Datatracker](https://datatracker.ietf.org/doc/rfc9110/) [↩](#user-content-fnref-3) [↩2](#user-content-fnref-3-2) [↩3](#user-content-fnref-3-3) [↩4](#user-content-fnref-3-4) 4. [RFC 9110 - HTTP Semantics - IETF Datatracker](https://datatracker.ietf.org/doc/html/rfc9110) [↩](#user-content-fnref-4) [↩2](#user-content-fnref-4-2) 5. [RFC 9111 - HTTP Caching - IETF Datatracker](https://datatracker.ietf.org/doc/html/rfc9111) [↩](#user-content-fnref-5) [↩2](#user-content-fnref-5-2) [↩3](#user-content-fnref-5-3) [↩4](#user-content-fnref-5-4) [↩5](#user-content-fnref-5-5) [↩6](#user-content-fnref-5-6) 6. [World Wide Web Consortium Supports HTTP/1.1 Reaching IETF Draft Standard - W3C](https://www.w3.org/press-releases/1999/http11/) [↩](#user-content-fnref-6) [↩2](#user-content-fnref-6-2) 7. [Who’s Breaking the Rules? Studying Conformance to the HTTP …](https://swag.cispa.saarland/papers/rautenstrauch2024conformance.pdf) [↩](#user-content-fnref-7) [↩2](#user-content-fnref-7-2) [↩3](#user-content-fnref-7-3) [↩4](#user-content-fnref-7-4) [↩5](#user-content-fnref-7-5) [↩6](#user-content-fnref-7-6) [↩7](#user-content-fnref-7-7) [↩8](#user-content-fnref-7-8) [↩9](#user-content-fnref-7-9) [↩10](#user-content-fnref-7-10) [↩11](#user-content-fnref-7-11) [↩12](#user-content-fnref-7-12) [↩13](#user-content-fnref-7-13) [↩14](#user-content-fnref-7-14) [↩15](#user-content-fnref-7-15) [↩16](#user-content-fnref-7-16) 8. [What is OOTB (Out of the Box) Solutions?](https://artoonsolutions.com/glossary/ootb/) [↩](#user-content-fnref-8) [↩2](#user-content-fnref-8-2) [↩3](#user-content-fnref-8-3) 9. [Was ist Skalierbarkeit in der IT? Kurz und einfach erklärt - RNT | Rausch](https://rnt.de/glossar/skalierbarkeit/) [↩](#user-content-fnref-9) [↩2](#user-content-fnref-9-2) 10. [Was ist Web-Caching und wie funktioniert es? - Bluehost](https://www.bluehost.com/de-de/blog/was-ist-web-caching-und-wie-funktioniert-es/) [↩](#user-content-fnref-10) [↩2](#user-content-fnref-10-2) [↩3](#user-content-fnref-10-3) [↩4](#user-content-fnref-10-4) 11. [HTTP Compliance and W3C QA](https://www.w3.org/2001/01/qa-ws/pp/alex-rousskov-measfact.html) [↩](#user-content-fnref-11) [↩2](#user-content-fnref-11-2) [↩3](#user-content-fnref-11-3) 12. [What are HTTP Methods (GET, POST, PUT, DELETE) - Apidog](https://apidog.com/blog/http-methods/) [↩](#user-content-fnref-12) 13. [HTTP request methods - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods) [↩](#user-content-fnref-13) [↩2](#user-content-fnref-13-2) [↩3](#user-content-fnref-13-3) [↩4](#user-content-fnref-13-4) [↩5](#user-content-fnref-13-5) [↩6](#user-content-fnref-13-6) [↩7](#user-content-fnref-13-7) [↩8](#user-content-fnref-13-8) [↩9](#user-content-fnref-13-9) [↩10](#user-content-fnref-13-10) [↩11](#user-content-fnref-13-11) 14. [Idempotent Requests in HTTP - Fullstack.wiki](https://fullstack.wiki/http/idempotent.html) [↩](#user-content-fnref-14) [↩2](#user-content-fnref-14-2) [↩3](#user-content-fnref-14-3) [↩4](#user-content-fnref-14-4) [↩5](#user-content-fnref-14-5) [↩6](#user-content-fnref-14-6) 15. [REST Anti-Patterns - InfoQ](https://www.infoq.com/articles/rest-anti-patterns/) [↩](#user-content-fnref-15) [↩2](#user-content-fnref-15-2) [↩3](#user-content-fnref-15-3) [↩4](#user-content-fnref-15-4) [↩5](#user-content-fnref-15-5) [↩6](#user-content-fnref-15-6) [↩7](#user-content-fnref-15-7) [↩8](#user-content-fnref-15-8) [↩9](#user-content-fnref-15-9) [↩10](#user-content-fnref-15-10) [↩11](#user-content-fnref-15-11) 16. [HTTP Caching and Proxy Behavior - OpenStack Specifications](https://specs.openstack.org/openstack/api-wg/guidelines/http/caching.html) [↩](#user-content-fnref-16) [↩2](#user-content-fnref-16-2) 17. [CONNECT request method - HTTP - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/CONNECT) [↩](#user-content-fnref-17) [↩2](#user-content-fnref-17-2) 18. [HTTP Status Codes: All 63 explained - including FAQ & Video - Umbraco](https://umbraco.com/knowledge-base/http-status-codes/) [↩](#user-content-fnref-18) [↩2](#user-content-fnref-18-2) [↩3](#user-content-fnref-18-3) [↩4](#user-content-fnref-18-4) 19. [HTTP response status codes - MDN Web Docs - Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status) [↩](#user-content-fnref-19) [↩2](#user-content-fnref-19-2) [↩3](#user-content-fnref-19-3) [↩4](#user-content-fnref-19-4) 20. [List of HTTP status codes - Wikipedia](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) [↩](#user-content-fnref-20) 21. [Status codes in HTTP - W3C](https://www.w3.org/Protocols/HTTP/HTRESP.html) [↩](#user-content-fnref-21) [↩2](#user-content-fnref-21-2) [↩3](#user-content-fnref-21-3) 22. [What Are HTTP Status Codes? Complete Guide - Postman Blog](https://blog.postman.com/what-are-http-status-codes/) [↩](#user-content-fnref-22) [↩2](#user-content-fnref-22-2) [↩3](#user-content-fnref-22-3) [↩4](#user-content-fnref-22-4) [↩5](#user-content-fnref-22-5) [↩6](#user-content-fnref-22-6) [↩7](#user-content-fnref-22-7) [↩8](#user-content-fnref-22-8) [↩9](#user-content-fnref-22-9) [↩10](#user-content-fnref-22-10) 23. [HTTP Status Codes - REST API Tutorial](https://restfulapi.net/http-status-codes/) [↩](#user-content-fnref-23) [↩2](#user-content-fnref-23-2) 24. [The importance of HTTP status codes to REST-based APIs - Treblle](https://treblle.com/blog/the-importance-of-http-status-codes-in-rest-apis) [↩](#user-content-fnref-24) [↩2](#user-content-fnref-24-2) [↩3](#user-content-fnref-24-3) [↩4](#user-content-fnref-24-4) 25. [401 Unauthorized - HTTP - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/401) [↩](#user-content-fnref-25) [↩2](#user-content-fnref-25-2) 26. [RFC 9111: HTTP Caching](https://www.rfc-editor.org/rfc/rfc9111.html) [↩](#user-content-fnref-26) [↩2](#user-content-fnref-26-2) [↩3](#user-content-fnref-26-3) [↩4](#user-content-fnref-26-4) [↩5](#user-content-fnref-26-5) [↩6](#user-content-fnref-26-6) [↩7](#user-content-fnref-26-7) 27. [Cache-Control header - HTTP - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) [↩](#user-content-fnref-27) 28. [What is cache-control? | Cache explained - Cloudflare](https://www.cloudflare.com/learning/cdn/glossary/what-is-cache-control/) [↩](#user-content-fnref-28) [↩2](#user-content-fnref-28-2) [↩3](#user-content-fnref-28-3) [↩4](#user-content-fnref-28-4) 29. [ETag update after resource modification - Stack Overflow](https://stackoverflow.com/questions/79207937/etag-update-after-resource-modification) [↩](#user-content-fnref-29) [↩2](#user-content-fnref-29-2) [↩3](#user-content-fnref-29-3) [↩4](#user-content-fnref-29-4) 30. [ETags: What they are, and how to use them | Fastly](https://www.fastly.com/blog/etags-what-they-are-and-how-to-use-them) [↩](#user-content-fnref-30) [↩2](#user-content-fnref-30-2) [↩3](#user-content-fnref-30-3) [↩4](#user-content-fnref-30-4) [↩5](#user-content-fnref-30-5) [↩6](#user-content-fnref-30-6) [↩7](#user-content-fnref-30-7) [↩8](#user-content-fnref-30-8) [↩9](#user-content-fnref-30-9) [↩10](#user-content-fnref-30-10) [↩11](#user-content-fnref-30-11) 31. [HTTP caching - MDN Web Docs - Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching) [↩](#user-content-fnref-31) [↩2](#user-content-fnref-31-2) [↩3](#user-content-fnref-31-3) 32. [Overview of proxy server caching - IBM](https://www.ibm.com/docs/en/was-nd/8.5.5?topic=caching-overview-proxy-server) [↩](#user-content-fnref-32) [↩2](#user-content-fnref-32-2) [↩3](#user-content-fnref-32-3) [↩4](#user-content-fnref-32-4) [↩5](#user-content-fnref-32-5) 33. [HTTP Proxy Caching — Apache Traffic Server documentation](https://docs.trafficserver.apache.org/admin-guide/configuration/cache-basics.en.html) [↩](#user-content-fnref-33) 34. [Content negotiation - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Content_negotiation) [↩](#user-content-fnref-34) [↩2](#user-content-fnref-34-2) [↩3](#user-content-fnref-34-3) [↩4](#user-content-fnref-34-4) [↩5](#user-content-fnref-34-5) [↩6](#user-content-fnref-34-6) [↩7](#user-content-fnref-34-7) 35. [Accept header - HTTP - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept) [↩](#user-content-fnref-35) [↩2](#user-content-fnref-35-2) [↩3](#user-content-fnref-35-3) 36. [Content Negotiation - Apache HTTP Server Version 2.4](https://httpd.apache.org/docs/current/content-negotiation.html) [↩](#user-content-fnref-36) 37. [Content Negotiation in a REST API](https://restfulapi.net/content-negotiation/) [↩](#user-content-fnref-37) 38. [Content Negotiation in ASP.NET Web API - Microsoft Learn](https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/content-negotiation) [↩](#user-content-fnref-38) [↩2](#user-content-fnref-38-2) [↩3](#user-content-fnref-38-3) 39. [Why would HTTP content negotiation be preferred to explicit parameters in an API scenario?](https://stackoverflow.com/questions/44735653/why-would-http-content-negotiation-be-preferred-to-explicit-parameters-in-an-api) [↩](#user-content-fnref-39) [↩2](#user-content-fnref-39-2) [↩3](#user-content-fnref-39-3) 40. [RFC 7235 - Hypertext Transfer Protocol (HTTP/1.1): Authentication - IETF Datatracker](https://datatracker.ietf.org/doc/html/rfc7235) [↩](#user-content-fnref-40) [↩2](#user-content-fnref-40-2) 41. [HTTP authentication - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Authentication) [↩](#user-content-fnref-41) [↩2](#user-content-fnref-41-2) [↩3](#user-content-fnref-41-3) [↩4](#user-content-fnref-41-4) 42. [HTTP Authentication : Methods and Strategies to Protect your Application - Medium](https://medium.com/@diego.coder/http-authentication-methods-and-strategies-to-protect-your-application-cda2ce9147a7) [↩](#user-content-fnref-42) 43. [Information on RFC 7235 - » RFC Editor](https://www.rfc-editor.org/info/rfc7235) [↩](#user-content-fnref-43) 44. [WWW-Authenticate header - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate) [↩](#user-content-fnref-44) [↩2](#user-content-fnref-44-2) 45. [WWW-Authenticate header - HTTP - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/WWW-Authenticate) [↩](#user-content-fnref-45) [↩2](#user-content-fnref-45-2) [↩3](#user-content-fnref-45-3) 46. [Authorization header - HTTP - MDN Web Docs - Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Authorization) [↩](#user-content-fnref-46) [↩2](#user-content-fnref-46-2) 47. [Understanding HTTP Authentication - WCF - Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/understanding-http-authentication) [↩](#user-content-fnref-47) 48. [HTTP cookie - Wikipedia](https://en.wikipedia.org/wiki/HTTP_cookie) [↩](#user-content-fnref-48) 49. [Information on RFC 6265 - » RFC Editor](https://www.rfc-editor.org/info/rfc6265) [↩](#user-content-fnref-49) [↩2](#user-content-fnref-49-2) 50. [Cookies: HTTP State Management Mechanism](https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html) [↩](#user-content-fnref-50) 51. [Using HTTP cookies - MDN Web Docs - Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies) [↩](#user-content-fnref-51) 52. [RFC 6265: HTTP State Management Mechanism](https://www.rfc-editor.org/rfc/rfc6265.html) [↩](#user-content-fnref-52) 53. [RFC 6265 - HTTP State Management Mechanism - IETF Datatracker](https://datatracker.ietf.org/doc/html/rfc6265) [↩](#user-content-fnref-53) 54. [Understanding Secure Cookies | BrowserStack](https://www.browserstack.com/guide/cookie-secure) [↩](#user-content-fnref-54) [↩2](#user-content-fnref-54-2) [↩3](#user-content-fnref-54-3) [↩4](#user-content-fnref-54-4) [↩5](#user-content-fnref-54-5) [↩6](#user-content-fnref-54-6) [↩7](#user-content-fnref-54-7) 55. [Securing Cookies Using HTTP Headers - Infosec Institute](https://www.infosecinstitute.com/resources/general-security/defending-against-web-attacks-using-http-headers-part-3/) [↩](#user-content-fnref-55) [↩2](#user-content-fnref-55-2) [↩3](#user-content-fnref-55-3) [↩4](#user-content-fnref-55-4) [↩5](#user-content-fnref-55-5) 56. [SameSite Cookie Attribute explained](https://cookie-script.com/documentation/samesite-cookie-attribute-explained) [↩](#user-content-fnref-56) [↩2](#user-content-fnref-56-2) 57. [SameSite-Cookies | Articles - web.dev](https://web.dev/articles/samesite-cookies-explained?hl=de) [↩](#user-content-fnref-57) 58. [XSS vs CSRF | Web Security Academy - PortSwigger](https://portswigger.net/web-security/csrf/xss-vs-csrf) [↩](#user-content-fnref-58) [↩2](#user-content-fnref-58-2) 59. [CSRF vs. XSS - open-appsec](https://www.openappsec.io/post/csrf-vs-xss) [↩](#user-content-fnref-59) [↩2](#user-content-fnref-59-2) 60. [XSS vs CSRF: Key Differences & How They Work - Invicti](https://www.invicti.com/blog/web-security/xss-vs-csrf-differences) [↩](#user-content-fnref-60) [↩2](#user-content-fnref-60-2) 61. [REST anti-patterns - Marcelo Cure - Medium](https://marcelocure.medium.com/rest-anti-patterns-b128597f5430) [↩](#user-content-fnref-61) [↩2](#user-content-fnref-61-2) [↩3](#user-content-fnref-61-3) 62. [When 200 OK Is Not OK - Unveiling the Risks of Web Responses In API Calls - Graylog](https://graylog.org/post/when-200-ok-is-not/) [↩](#user-content-fnref-62) 63. [Returning http 200 OK with error within response body - Stack Overflow](https://stackoverflow.com/questions/27921537/returning-http-200-ok-with-error-within-response-body) [↩](#user-content-fnref-63) 64. [Caching overview | Cloud CDN - Google Cloud Documentation](https://docs.cloud.google.com/cdn/docs/caching) [↩](#user-content-fnref-64) 65. [Cache by status code - Cloudflare Docs](https://developers.cloudflare.com/cache/how-to/configure-cache-status-code/) [↩](#user-content-fnref-65) [↩2](#user-content-fnref-65-2) [↩3](#user-content-fnref-65-3) [↩4](#user-content-fnref-65-4) [↩5](#user-content-fnref-65-5) [↩6](#user-content-fnref-65-6) [↩7](#user-content-fnref-65-7) 66. [Understanding and Using HTTP Methods: GET, POST, PUT, DELETE | by August - Medium](https://medium.com/@2957607810/understanding-and-using-http-methods-get-post-put-delete-794cda75470e) [↩](#user-content-fnref-66) 67. [what does cache means in POST and GET - Stack Overflow](https://stackoverflow.com/questions/27020783/what-does-cache-means-in-post-and-get) [↩](#user-content-fnref-67) [↩2](#user-content-fnref-67-2) [↩3](#user-content-fnref-67-3) 68. [What data is cached by web proxy server(or other http caches)? - Stack Overflow](https://stackoverflow.com/questions/39751143/what-data-is-cached-by-web-proxy-serveror-other-http-caches) [↩](#user-content-fnref-68) [↩2](#user-content-fnref-68-2) [↩3](#user-content-fnref-68-3) [↩4](#user-content-fnref-68-4) [↩5](#user-content-fnref-68-5) 69. [Tunneling HTTP verbs - OneDrive API - Microsoft Learn](https://learn.microsoft.com/en-us/onedrive/developer/rest-api/concepts/http-verb-tunneling?view=odsp-graph-online) [↩](#user-content-fnref-69) 70. [HTTP Verb Tunnelling - Fano Framework](https://fanoframework.github.io/security/http-verb-tunnelling/) [↩](#user-content-fnref-70) 71. [Introduction to antipatterns | Apigee - Google Cloud Documentation](https://docs.cloud.google.com/apigee/docs/api-platform/antipatterns/intro) [↩](#user-content-fnref-71) 72. [CDN:Configure TTL for HTTP status code - Alibaba Cloud](https://www.alibabacloud.com/help/en/cdn/user-guide/create-a-cache-rule-for-http-status-codes) [↩](#user-content-fnref-72) 73. [Non-compliant and Proud: A Case Study of HTTP Compliance - ResearchGate](https://www.researchgate.net/profile/Ralph-Johnson-3/publication/32964874_Non-compliant_and_Proud_A_Case_Study_of_HTTP_Compliance/links/555f593e08ae86c06b636c08/Non-compliant-and-Proud-A-Case-Study-of-HTTP-Compliance.pdf?origin=scientificContributions) [↩](#user-content-fnref-73) 74. [Common CDN issues and how to fix them - Cloudflare](https://www.cloudflare.com/learning/cdn/common-cdn-issues/) [↩](#user-content-fnref-74) # What is Thymian? > Thymian is an extensible and plugin-rich tool to lint and test HTTP endpoints and APIs. Thymian is an innovative open-source HTTP conformance linting tool that revolutionizes how developers build, test, and maintain APIs. By combining static analysis with runtime testing capabilities, Thymian ensures your APIs follow industry best practices and RFC/IETF standards throughout the entire development lifecycle. **Thymian is not yet another tool in your API toolbox. It’s the toolbox!** ## Why Thymian? [Section titled “Why Thymian?”](#why-thymian) The web has been evolving rapidly since decades. Software as a Service (SaaS) has become the norm, and countless apps and platforms rely on critical services and APIs. Mobile devices work as digital wallets, and almost everybody is connected 24/7 to the internet. Virtually everything relies on one communication standard: the **Hypertext Transfer Protocol** or short **HTTP** HTTP plays a key role in practically every aspect in our digital life and is designed to be **secure, reliable, and fast.** HTTP brings complimentary and can reduce complexity on the API and client level when implemented properly. Thymian is here to help you build and maintain high-quality APIs and HTTP-based communication that follow industry standards and keep your business logic simple and secure by ensuring compliance with HTTP standards. ## Comprehensive API Testing & Validation [Section titled “Comprehensive API Testing & Validation”](#comprehensive-api-testing--validation) At its core, Thymian provides powerful API analysis through multiple complementary approaches: * **Static Analysis**: Catch issues early by analyzing API specifications at design time * **Runtime Linting**: Validate actual API behavior and responses during execution * **OpenAPI Support**: First-class support for OpenAPI specifications enables thorough API contract validation * **HTTP Conformance**: Ensure your APIs properly implement HTTP standards and best practices ## Flexible & Extensible Architecture [Section titled “Flexible & Extensible Architecture”](#flexible--extensible-architecture) Thymian is built on a modern, plugin-based microkernel architecture that enables extensive customization: * **Plugin System**: Extend functionality through WebSocket-based plugins * **Custom Rulesets**: Create and share HTTP conformance rules and tests (similar to ESLint) * **Custom Plugins**: Build plugins to add new capabilities and integrations ## Developer-Focused Experience [Section titled “Developer-Focused Experience”](#developer-focused-experience) We’ve designed Thymian with developers in mind: * **CLI Interface**: Powerful command-line tools for automation and CI/CD integration * **IDE Integration**: Direct integration with popular IDEs like IntelliJ and WebStorm * **API Testing**: Built-in support for payload/data seeding and sharable test cases * **Framework Agnostic**: Works with any API implementation and technology stack (e.g., Node.js, NestJS, Express, Spring Boot, etc.) ## Enterprise Ready [Section titled “Enterprise Ready”](#enterprise-ready) Thymian bridges the gap between development and production: * **Complete Toolchain**: Seamlessly connects static analysis, contract testing, and runtime validation * **API Governance**: Enforce standards and best practices across your organization * **AGPL Licensed**: Free for commercial use while ensuring code remains open source * **Production Grade**: Built for reliability and performance at scale By serving as the bridge between static specification linting, contract testing, and runtime testing, Thymian provides a comprehensive solution for modern API development. Whether you’re building new APIs or maintaining existing ones, Thymian helps ensure quality, consistency, and compliance with HTTP standards. # Accessibility Statement > A statement of the current state of accessibility for thymian.dev As part of our accessibility statement, we would like to provide you with an overview of the compatibility status of the service(s) described below with accessibility requirements according to legal regulations (in particular with the Barrier-Free Strengthening Act / “Barrierefreiheitsstärkungsgesetz – BFSG”). ## Service Provider Information [Section titled “Service Provider Information”](#service-provider-information) qupaya technologies GmbH\ Puscherstraße 7\ 90411 Nuremberg\ Bavaria ## General Description of Service [Section titled “General Description of Service”](#general-description-of-service) Documentation of the Open Source Software Thymian. ## Service Implementation Details [Section titled “Service Implementation Details”](#service-implementation-details) Through thymian.dev, we provide publicly accessible and free documentation for the Open Source Software Thymian. ## Compliance Status with Requirements [Section titled “Compliance Status with Requirements”](#compliance-status-with-requirements) The above-mentioned service is fully compliant with the Web Content Accessibility Guidelines (WCAG) 2.1 Level AA and the Barrier-Free Strengthening Act (BFSG). The following measures were taken to ensure accessibility: * All text meets WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text) * All interactive elements are keyboard accessible with visible focus indicators * Content remains visible and usable when JavaScript is unavailable * Responsive layouts use correct semantic markup for assistive technologies * Dark mode theming respects the Starlight theme toggle rather than relying solely on OS-level preference * Automated accessibility testing using axe-core is part of the continuous integration pipeline ## Creation of Accessibility Statement [Section titled “Creation of Accessibility Statement”](#creation-of-accessibility-statement) Date of creation of an accessibility statement: 07.10.2025\ Date of last review of the above services regarding accessibility requirements: 15.04.2026 ## Assessment of Accessibility Status [Section titled “Assessment of Accessibility Status”](#assessment-of-accessibility-status) The assessment of accessibility status is based on our self-assessment, supplemented by automated testing with axe-core (WCAG 2.1 AA ruleset). ## Feedback Options and Contact Details [Section titled “Feedback Options and Contact Details”](#feedback-options-and-contact-details) Address:\ qupaya technologies GmbH\ Puscherstraße 7\ 90411 Nürnberg Email: Phone: +4915678941602 Website Link: ## Responsible Market Surveillance Authority [Section titled “Responsible Market Surveillance Authority”](#responsible-market-surveillance-authority) Market Surveillance Office of the Federal States for the Accessibility of Products and Services (MLBF) Phone: + 49 (0) 391 567 6970\ Email: Information about the current address of MLBF can be found here: # Privacy Policy > The privacy policy of thymian.dev ## 1. An overview of data protection [Section titled “1. An overview of data protection”](#1-an-overview-of-data-protection) ### General information [Section titled “General information”](#general-information) The following information will provide you with an easy to navigate overview of what will happen with your personal data when you visit this website. The term “personal data” comprises all data that can be used to personally identify you. For detailed information about the subject matter of data protection, please consult our Data Protection Declaration, which we have included beneath this copy. ### Data recording on this website [Section titled “Data recording on this website”](#data-recording-on-this-website) #### Who is the responsible party for the recording of data on this website (i.e., the “controller”)? [Section titled “Who is the responsible party for the recording of data on this website (i.e., the “controller”)?”](#who-is-the-responsible-party-for-the-recording-of-data-on-this-website-ie-the-controller) The data on this website is processed by the operator of the website, whose contact information is available under section “Information about the responsible party (referred to as the “controller” in the GDPR)” in this Privacy Policy. #### How do we record your data? [Section titled “How do we record your data?”](#how-do-we-record-your-data) We collect your data as a result of your sharing of your data with us. This may, for instance be information you enter into our contact form. Other data shall be recorded by our IT systems automatically or after you consent to its recording during your website visit. This data comprises primarily technical information (e.g., web browser, operating system, or time the site was accessed). This information is recorded automatically when you access this website. #### What are the purposes we use your data for? [Section titled “What are the purposes we use your data for?”](#what-are-the-purposes-we-use-your-data-for) A portion of the information is generated to guarantee the error free provision of the website. Other data may be used to analyze your user patterns. If contracts can be concluded or initiated via the website, the transmitted data will also be processed for contract offers, orders or other order enquiries. #### What rights do you have as far as your information is concerned? [Section titled “What rights do you have as far as your information is concerned?”](#what-rights-do-you-have-as-far-as-your-information-is-concerned) You have the right to receive information about the source, recipients, and purposes of your archived personal data at any time without having to pay a fee for such disclosures. You also have the right to demand that your data are rectified or eradicated. If you have consented to data processing, you have the option to revoke this consent at any time, which shall affect all future data processing. Moreover, you have the right to demand that the processing of your data be restricted under certain circumstances. Furthermore, you have the right to log a complaint with the competent supervising agency. Please do not hesitate to contact us at any time if you have questions about this or any other data protection related issues. ### Analysis tools and tools provided by third parties [Section titled “Analysis tools and tools provided by third parties”](#analysis-tools-and-tools-provided-by-third-parties) There is a possibility that your browsing patterns will be statistically analyzed when your visit this website. Such analyses are performed primarily with what we refer to as analysis programs. For detailed information about these analysis programs please consult our Data Protection Declaration below. ## 2. Hosting [Section titled “2. Hosting”](#2-hosting) We are hosting the content of our website at the following provider: ### External Hosting [Section titled “External Hosting”](#external-hosting) This website is hosted externally. Personal data collected on this website are stored on the servers of the host. These may include, but are not limited to, IP addresses, contact requests, metadata and communications, contract information, contact information, names, web page access, and other data generated through a web site. The external hosting serves the purpose of fulfilling the contract with our potential and existing customers (Art. 6(1)(b) GDPR) and in the interest of secure, fast, and efficient provision of our online services by a professional provider (Art. 6(1)(f) GDPR). If appropriate consent has been obtained, the processing is carried out exclusively on the basis of Art. 6 (1)(a) GDPR and § 25 (1) TDDDG, insofar the consent includes the storage of cookies or the access to information in the user’s end device (e.g., device fingerprinting) within the meaning of the TDDDG. This consent can be revoked at any time. Our host(s) will only process your data to the extent necessary to fulfil its performance obligations and to follow our instructions with respect to such data. We are using the following host(s): GitHub, Inc. 88 Colin P. Kelly Jr. St. San Francisco, CA 94107 United Sates of America #### Data processing [Section titled “Data processing”](#data-processing) We have concluded a data processing agreement (DPA) for the use of the above-mentioned service. This is a contract mandated by data privacy laws that guarantees that they process personal data of our website visitors only based on our instructions and in compliance with the GDPR. ## 3. General information and mandatory information [Section titled “3. General information and mandatory information”](#3-general-information-and-mandatory-information) ### Data protection [Section titled “Data protection”](#data-protection) The operators of this website and its pages take the protection of your personal data very seriously. Hence, we handle your personal data as confidential information and in compliance with the statutory data protection regulations and this Data Protection Declaration. Whenever you use this website, a variety of personal information will be collected. Personal data comprises data that can be used to personally identify you. This Data Protection Declaration explains which data we collect as well as the purposes we use this data for. It also explains how, and for which purpose the information is collected. We herewith advise you that the transmission of data via the Internet (i.e., through e-mail communications) may be prone to security gaps. It is not possible to completely protect data against third-party access. ### Information about the responsible party (referred to as the “controller” in the GDPR) [Section titled “Information about the responsible party (referred to as the “controller” in the GDPR)”](#information-about-the-responsible-party-referred-to-as-the-controller-in-the-gdpr) The data processing controller on this website is: qupaya technologies GmbH Puscherstr. 7 90411 Nürnberg Phone: +4915678941602 E-mail: The controller is the natural person or legal entity that single-handedly or jointly with others makes decisions as to the purposes of and resources for the processing of personal data (e.g., names, e-mail addresses, etc.). ### Storage duration [Section titled “Storage duration”](#storage-duration) Unless a more specific storage period has been specified in this privacy policy, your personal data will remain with us until the purpose for which it was collected no longer applies. If you assert a justified request for deletion or revoke your consent to data processing, your data will be deleted, unless we have other legally permissible reasons for storing your personal data (e.g., tax or commercial law retention periods); in the latter case, the deletion will take place after these reasons cease to apply. ### General information on the legal basis for the data processing on this website [Section titled “General information on the legal basis for the data processing on this website”](#general-information-on-the-legal-basis-for-the-data-processing-on-this-website) If you have consented to data processing, we process your personal data on the basis of Art. 6(1)(a) GDPR or Art. 9 (2)(a) GDPR, if special categories of data are processed according to Art. 9 (1) DSGVO. In the case of explicit consent to the transfer of personal data to third countries, the data processing is also based on Art. 49 (1)(a) GDPR. If you have consented to the storage of cookies or to the access to information in your end device (e.g., via device fingerprinting), the data processing is additionally based on § 25 (1) TDDDG. The consent can be revoked at any time. If your data is required for the fulfillment of a contract or for the implementation of pre-contractual measures, we process your data on the basis of Art. 6(1)(b) GDPR. Furthermore, if your data is required for the fulfillment of a legal obligation, we process it on the basis of Art. 6(1)(c) GDPR. Furthermore, the data processing may be carried out on the basis of our legitimate interest according to Art. 6(1)(f) GDPR. Information on the relevant legal basis in each individual case is provided in the following paragraphs of this privacy policy. ### Recipients of personal data [Section titled “Recipients of personal data”](#recipients-of-personal-data) In the scope of our business activities, we cooperate with various external parties. In some cases, this also requires the transfer of personal data to these external parties. We only disclose personal data to external parties if this is required as part of the fulfillment of a contract, if we are legally obligated to do so (e.g., disclosure of data to tax authorities), if we have a legitimate interest in the disclosure pursuant to Art. 6 (1)(f) GDPR, or if another legal basis permits the disclosure of this data. When using processors, we only disclose personal data of our customers on the basis of a valid contract on data processing. In the case of joint processing, a joint processing agreement is concluded. ### Revocation of your consent to the processing of data [Section titled “Revocation of your consent to the processing of data”](#revocation-of-your-consent-to-the-processing-of-data) A wide range of data processing transactions are possible only subject to your express consent. You can also revoke at any time any consent you have already given us. This shall be without prejudice to the lawfulness of any data collection that occurred prior to your revocation. ### Right to object to the collection of data in special cases; right to object to direct advertising (Art. 21 GDPR) [Section titled “Right to object to the collection of data in special cases; right to object to direct advertising (Art. 21 GDPR)”](#right-to-object-to-the-collection-of-data-in-special-cases-right-to-object-to-direct-advertising-art-21-gdpr) IN THE EVENT THAT DATA ARE PROCESSED ON THE BASIS OF ART. 6(1)(E) OR (F) GDPR, YOU HAVE THE RIGHT TO AT ANY TIME OBJECT TO THE PROCESSING OF YOUR PERSONAL DATA BASED ON GROUNDS ARISING FROM YOUR UNIQUE SITUATION. THIS ALSO APPLIES TO ANY PROFILING BASED ON THESE PROVISIONS. TO DETERMINE THE LEGAL BASIS, ON WHICH ANY PROCESSING OF DATA IS BASED, PLEASE CONSULT THIS DATA PROTECTION DECLARATION. IF YOU LOG AN OBJECTION, WE WILL NO LONGER PROCESS YOUR AFFECTED PERSONAL DATA, UNLESS WE ARE IN A POSITION TO PRESENT COMPELLING PROTECTION WORTHY GROUNDS FOR THE PROCESSING OF YOUR DATA, THAT OUTWEIGH YOUR INTERESTS, RIGHTS AND FREEDOMS OR IF THE PURPOSE OF THE PROCESSING IS THE CLAIMING, EXERCISING OR DEFENCE OF LEGAL ENTITLEMENTS (OBJECTION PURSUANT TO ART. 21(1) GDPR). IF YOUR PERSONAL DATA IS BEING PROCESSED IN ORDER TO ENGAGE IN DIRECT ADVERTISING, YOU HAVE THE RIGHT TO OBJECT TO THE PROCESSING OF YOUR AFFECTED PERSONAL DATA FOR THE PURPOSES OF SUCH ADVERTISING AT ANY TIME. THIS ALSO APPLIES TO PROFILING TO THE EXTENT THAT IT IS AFFILIATED WITH SUCH DIRECT ADVERTISING. IF YOU OBJECT, YOUR PERSONAL DATA WILL SUBSEQUENTLY NO LONGER BE USED FOR DIRECT ADVERTISING PURPOSES (OBJECTION PURSUANT TO ART. 21(2) GDPR). ### Right to log a complaint with the competent supervisory agency [Section titled “Right to log a complaint with the competent supervisory agency”](#right-to-log-a-complaint-with-the-competent-supervisory-agency) In the event of violations of the GDPR, data subjects are entitled to log a complaint with a supervisory agency, in particular in the member state where they usually maintain their domicile, place of work or at the place where the alleged violation occurred. The right to log a complaint is in effect regardless of any other administrative or court proceedings available as legal recourses. ### Right to data portability [Section titled “Right to data portability”](#right-to-data-portability) You have the right to have data that we process automatically on the basis of your consent or in fulfillment of a contract handed over to you or to a third party in a common, machine-readable format. If you should demand the direct transfer of the data to another controller, this will be done only if it is technically feasible. ### Information about, rectification and eradication of data [Section titled “Information about, rectification and eradication of data”](#information-about-rectification-and-eradication-of-data) Within the scope of the applicable statutory provisions, you have the right to demand information about your archived personal data, their source and recipients as well as the purpose of the processing of your data at any time. You may also have a right to have your data rectified or eradicated. If you have questions about this subject matter or any other questions about personal data, please do not hesitate to contact us at any time. ### Right to demand processing restrictions [Section titled “Right to demand processing restrictions”](#right-to-demand-processing-restrictions) You have the right to demand the imposition of restrictions as far as the processing of your personal data is concerned. To do so, you may contact us at any time. The right to demand restriction of processing applies in the following cases: * In the event that you should dispute the correctness of your data archived by us, we will usually need some time to verify this claim. During the time that this investigation is ongoing, you have the right to demand that we restrict the processing of your personal data. * If the processing of your personal data was/is conducted in an unlawful manner, you have the option to demand the restriction of the processing of your data instead of demanding the eradication of this data. * If we do not need your personal data any longer and you need it to exercise, defend or claim legal entitlements, you have the right to demand the restriction of the processing of your personal data instead of its eradication. * If you have raised an objection pursuant to Art. 21(1) GDPR, your rights and our rights will have to be weighed against each other. As long as it has not been determined whose interests prevail, you have the right to demand a restriction of the processing of your personal data. If you have restricted the processing of your personal data, these data – with the exception of their archiving – may be processed only subject to your consent or to claim, exercise or defend legal entitlements or to protect the rights of other natural persons or legal entities or for important public interest reasons cited by the European Union or a member state of the EU. ### SSL and/or TLS encryption [Section titled “SSL and/or TLS encryption”](#ssl-andor-tls-encryption) For security reasons and to protect the transmission of confidential content, such as purchase orders or inquiries you submit to us as the website operator, this website uses either an SSL or a TLS encryption program. You can recognize an encrypted connection by checking whether the address line of the browser switches from “http\://” to “https\://” and also by the appearance of the lock icon in the browser line. If the SSL or TLS encryption is activated, data you transmit to us cannot be read by third parties. ## 4. Online-based Audio and Video Conferences (Conference tools) [Section titled “4. Online-based Audio and Video Conferences (Conference tools)”](#4-online-based-audio-and-video-conferences-conference-tools) ### Data processing [Section titled “Data processing”](#data-processing-1) We use online conference tools, among other things, for communication with our customers. The tools we use are listed in detail below. If you communicate with us by video or audio conference using the Internet, your personal data will be collected and processed by the provider of the respective conference tool and by us. The conferencing tools collect all information that you provide/access to use the tools (email address and/or your phone number). Furthermore, the conference tools process the duration of the conference, start and end (time) of participation in the conference, number of participants and other “context information” related to the communication process (metadata). Furthermore, the provider of the tool processes all the technical data required for the processing of the online communication. This includes, in particular, IP addresses, MAC addresses, device IDs, device type, operating system type and version, client version, camera type, microphone or loudspeaker and the type of connection. Should content be exchanged, uploaded, or otherwise made available within the tool, it is also stored on the servers of the tool provider. Such content includes, but is not limited to, cloud recordings, chat/ instant messages, voicemail uploaded photos and videos, files, whiteboards, and other information shared while using the service. Please note that we do not have complete influence on the data processing procedures of the tools used. Our possibilities are largely determined by the corporate policy of the respective provider. Further information on data processing by the conference tools can be found in the data protection declarations of the tools used, and which we have listed below this text. ### Purpose and legal bases [Section titled “Purpose and legal bases”](#purpose-and-legal-bases) The conference tools are used to communicate with prospective or existing contractual partners or to offer certain services to our customers (Art. 6(1)(b) GDPR). Furthermore, the use of the tools serves to generally simplify and accelerate communication with us or our company (legitimate interest in the meaning of Art. 6(1)(f) GDPR). Insofar as consent has been requested, the tools in question will be used on the basis of this consent; the consent may be revoked at any time with effect from that date. ### Duration of storage [Section titled “Duration of storage”](#duration-of-storage) Data collected directly by us via the video and conference tools will be deleted from our systems immediately after you request us to delete it, revoke your consent to storage, or the reason for storing the data no longer applies. Stored cookies remain on your end device until you delete them. Mandatory legal retention periods remain unaffected. We have no influence on the duration of storage of your data that is stored by the operators of the conference tools for their own purposes. For details, please directly contact the operators of the conference tools. ### Conference tools used [Section titled “Conference tools used”](#conference-tools-used) We employ the following conference tools: #### Google Meet [Section titled “Google Meet”](#google-meet) We use Google Meet. The provider is Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Ireland. For details on data processing, please see the Google privacy policy: . The company is certified in accordance with the “EU-US Data Privacy Framework” (DPF). The DPF is an agreement between the European Union and the US, which is intended to ensure compliance with European data protection standards for data processing in the US. Every company certified under the DPF is obliged to comply with these data protection standards. For more information, please contact the provider under the following link: . ##### Data processing [Section titled “Data processing”](#data-processing-2) We have concluded a data processing agreement (DPA) for the use of the above-mentioned service. This is a contract mandated by data privacy laws that guarantees that they process personal data of our website visitors only based on our instructions and in compliance with the GDPR. # Thymian License > The GNU Affero General Public License (AGPLv3) for thymian.dev ## GNU APGL v3 License [Section titled “GNU APGL v3 License”](#gnu-apgl-v3-license) *Version 3, 19 November 2007* Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ## Preamble [Section titled “Preamble”](#preamble) The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program–to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users’ freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. ## TERMS AND CONDITIONS [Section titled “TERMS AND CONDITIONS”](#terms-and-conditions) ### 0. Definitions [Section titled “0. Definitions”](#0-definitions) “This License” refers to version 3 of the GNU Affero General Public License. “Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. “The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. A “covered work” means either the unmodified Program or a work based on the Program. To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. ### 1. Source Code [Section titled “1. Source Code”](#1-source-code) The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work’s System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. ### 2. Basic Permissions [Section titled “2. Basic Permissions”](#2-basic-permissions) All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. ### 3. Protecting Users’ Legal Rights From Anti-Circumvention Law [Section titled “3. Protecting Users’ Legal Rights From Anti-Circumvention Law”](#3-protecting-users-legal-rights-from-anti-circumvention-law) No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work’s users, your or third parties’ legal rights to forbid circumvention of technological measures. ### 4. Conveying Verbatim Copies [Section titled “4. Conveying Verbatim Copies”](#4-conveying-verbatim-copies) You may convey verbatim copies of the Program’s source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. ### 5. Conveying Modified Source Versions [Section titled “5. Conveying Modified Source Versions”](#5-conveying-modified-source-versions) You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation’s users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. ### 6. Conveying Non-Source Forms [Section titled “6. Conveying Non-Source Forms”](#6-conveying-non-source-forms) You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. “Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. ### 7. Additional Terms [Section titled “7. Additional Terms”](#7-additional-terms) “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. ### 8. Termination [Section titled “8. Termination”](#8-termination) You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. ### 9. Acceptance Not Required for Having Copies [Section titled “9. Acceptance Not Required for Having Copies”](#9-acceptance-not-required-for-having-copies) You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. ### 10. Automatic Licensing of Downstream Recipients [Section titled “10. Automatic Licensing of Downstream Recipients”](#10-automatic-licensing-of-downstream-recipients) Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party’s predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. ### 11. Patents [Section titled “11. Patents”](#11-patents) A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor’s “contributor version”. A contributor’s “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor’s essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient’s use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. ### 12. No Surrender of Others’ Freedom [Section titled “12. No Surrender of Others’ Freedom”](#12-no-surrender-of-others-freedom) If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. ### 13. Remote Network Interaction; Use with the GNU General Public License [Section titled “13. Remote Network Interaction; Use with the GNU General Public License”](#13-remote-network-interaction-use-with-the-gnu-general-public-license) Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. ### 14. Revised Versions of this License [Section titled “14. Revised Versions of this License”](#14-revised-versions-of-this-license) The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. ### 15. Disclaimer of Warranty [Section titled “15. Disclaimer of Warranty”](#15-disclaimer-of-warranty) THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ### 16. Limitation of Liability [Section titled “16. Limitation of Liability”](#16-limitation-of-liability) IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. ### 17. Interpretation of Sections 15 and 16 [Section titled “17. Interpretation of Sections 15 and 16”](#17-interpretation-of-sections-15-and-16) If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. ## END OF TERMS AND CONDITIONS [Section titled “END OF TERMS AND CONDITIONS”](#end-of-terms-and-conditions) ### How to Apply These Terms to Your New Programs [Section titled “How to Apply These Terms to Your New Programs”](#how-to-apply-these-terms-to-your-new-programs) If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. # Site Notice > The site notice of thymian.dev qupaya technologies GmbH\ Puscherstraße 7\ 90411 Nürnberg Commercial Register: HRB 37193\ Registration court: Amtsgericht Nürnberg **Represented by:**\ Markus Ende, Peter Müller ## Contact [Section titled “Contact”](#contact) Phone: +4915678941602\ E-mail: ## VAT ID [Section titled “VAT ID”](#vat-id) Sales tax identification number according to Sect. 27 a of the Sales Tax Law:\ DE327806108 ## Information regarding professional liability insurance [Section titled “Information regarding professional liability insurance”](#information-regarding-professional-liability-insurance) **Name and registered office of the insurer:**\ Versicherungskammer Bayern\ Maximilianstraße 53\ 80530 München **Scope of insurance:**\ Europa, USA, Kanada ## Dispute resolution proceedings in front of a consumer arbitration board [Section titled “Dispute resolution proceedings in front of a consumer arbitration board”](#dispute-resolution-proceedings-in-front-of-a-consumer-arbitration-board) We are not willing or obliged to participate in dispute resolution proceedings in front of a consumer arbitration board. # Thymian CLI Reference # Commands [Section titled “Commands”](#commands) * [`thymian analyze`](#thymian-analyze) * [`thymian config show`](#thymian-config-show) * [`thymian explain rule RULENAME`](#thymian-explain-rule-rulename) * [`thymian feedback`](#thymian-feedback) * [`thymian generate config`](#thymian-generate-config) * [`thymian generate rule`](#thymian-generate-rule) * [`thymian help [COMMAND]`](#thymian-help-command) * [`thymian lint`](#thymian-lint) * [`thymian plugins list`](#thymian-plugins-list) * [`thymian report convert`](#thymian-report-convert) * [`thymian report merge`](#thymian-report-merge) * [`thymian request`](#thymian-request) * [`thymian rules list`](#thymian-rules-list) * [`thymian serve`](#thymian-serve) * [`thymian test`](#thymian-test) * [`thymian validate`](#thymian-validate) * [`thymian version`](#thymian-version) ## `thymian analyze` [Section titled “thymian analyze”](#thymian-analyze) Analyze recorded API traffic against specifications and configured rule sets. ```plaintext USAGE $ thymian analyze [--verbose] [--debug] [--guidance] [--log-level trace|debug|info|warn|error|silent] [--config ] [--autoload] [--plugin ...] [--option .=...] [--spec type:location...] [--traffic type:location...] [--rule-set package-name...] [--rule-severity off|error|warn|hint] [--sort-reports-by rule|endpoint|severity] [--timeout ] [--idle-timeout ] [--cwd ] [--suppress-feedback] [--validate-specs] [--validate-traffic-source] FLAGS --cwd= [default: /home/runner/work/thymian/thymian/packages/thymian] Set current working directory. --[no-]validate-traffic-source Validate included traffic sources and fail on source-schema validation errors. BASE FLAGS --[no-]autoload Automatically load and initialize plugins from the configuration file. --config= Path to thymian configuration file. --debug Run thymian in debug mode. --[no-]guidance Show guidance hints on stderr. Defaults to true for TTY, false for non-TTY. --idle-timeout= [default: 500] Set the duration in ms to waited for events and actions when closing Thymian. --log-level=