Skip to content

Diagnostics reference

Diagnostics are how Docuccino tells you it knows less than your code does, or that something you configured didn’t take. They’re printed by docuccino:export, docuccino:validate, docuccino:cache and docuccino:watch, grouped by route and in a stable order, and they can be embedded in a UIR document with --embed-diagnostics.

Every one carries a code — the stable identifier in the first column below — plus a message naming the exact class, route or file it’s about, and usually a line of help. This page is the exhaustive list. If you’re working back from a symptom rather than from a code, start at Troubleshooting.

Every code a build reports prints a link to this page beneath it, the first time that code appears in a run — so a code you met in your terminal is one click from what it means.

A diagnostic’s severity is what --fail-on gates on: pass it a floor and anything that loud or louder exits non-zero.

Severity What it means
Error Something is missing from the document, or the document is invalid. Always worth fixing.
Warning Something you wrote didn’t take effect, or the output is less useful than you asked for.
Info Docuccino recovered less than your code says and widened to stay truthful. Normal in small numbers; the same code on every action is a signal.
Hint The quietest level: a note an extension chose not to make louder. Nothing Docuccino itself emits today.
Terminal window
# Fail CI on anything that isn't a recovery note.
php artisan docuccino:export --fail-on=warning
# Tighter: gate on inference certainty too, once the info list is short.
php artisan docuccino:export --fail-on=info

On an existing codebase, start at warning and tighten to info later. A gate that fires on day one is a gate the team switches off.

The other way to tighten a gate is to accept the codes you can’t act on. List them under diagnostics.accept in docuccino.yaml and they keep printing — marked accepted, with a hit count at the end of the block — while --fail-on stops counting them:

diagnostics:
accept: ['eloquent.no-columns', 'validation.rule-unrecoverable']
[info, accepted] eloquent.no-columns: Model App\Vendor\Ledger exposes no documentable columns.
Accepted, so --fail-on ignores them: eloquent.no-columns (12)

Acceptance changes the exit code and nothing else: the document is byte-identical either way, and the report is still in the log the day it starts firing somewhere new. An error is never accepted — it says the document is wrong or the build lost a whole tier of facts, and an entry that reaches one is reported as config.accept-refused while the run fails as it always would. An entry nothing reports is reported as config.accept-unused, so the list can’t outlive what it was for.

Docuccino reads your types with an embedded static analyzer. These codes are about the analyzer itself — whether it ran, and where reading your code hit a bound.

Code Severity What it means What to do
engine.not-installed warning No analyzer is present, so the document came from docblocks and attributes only Install the engine: composer require --dev docuccino/inference-phpstan. Set DOCUCCINO_ENGINE=null if you meant to document without inference
engine.boot-failed error The analyzer is installed and couldn’t start, so the document came from docblocks and attributes only Generate from the project root, in an environment your app boots in — see the analyzer won’t start. The message quotes what it threw
engine.mode-unknown warning engine.mode isn’t a mode Docuccino has; it ran in-process anyway Set DOCUCCINO_ENGINE to in-process or null — see Engine
inference.action-failed warning Analyzing one action threw One or two: annotate those actions. All of them: you probably have a version mismatch
inference.method-not-found warning An action’s method has no body the analyzer can read — it’s abstract, or resolved to something that isn’t a method Point the route at a concrete method, or state the response with #[Response]
inference.callable-failed warning Analyzing a callable the trace followed — a render callback, a query-object method — threw The message names it. Simplify it, or state the shape it produces with an attribute
inference.callable-not-found info A callable the trace wanted to follow has no readable body Usually vendor code, and usually fine. If it’s yours, express it as a plain method the analyzer can reach
inference.response-shape-truncated info Recovering a response shape ran out of descent depth or file budget, so the response is documented as its bare declared type — true, but poorer than your code is Flatten the chain between the return and the value it builds — every project-code call on the way is one hop, and no config key raises the bound. That is the only thing that clears it: #[Response] corrects the document and this notice keeps naming the callable
inference.http-exception-status-unread info A throw carries an HTTP status the analyzer could not read, so the error is documented without one and a later tier classifies it. Names the exception, the file and line the throw is written at, and which fold gave up — a status handed to abort() that isn’t a constant, a construction that folds to more than one status, a class that states none. Only ever where you can act: a status written in vendor/ is never named, because the only fix would be an edit you don’t own Write the status as a constant where the throw is — a literal and a class constant both fold, and so does the constructor default a construction leaves the slot empty for. A status chosen at run time is not one: this build cannot tell which of them the response is. Pin it in the class with parent::__construct(409, …) if every instance is that status, and otherwise write it at the throw, at the abort(), or in the static factory the throw names
inference.descend-scope-narrowed info You set engine.project_paths, and it excludes a source root your composer.json declares under autoload — so a call that can throw was not opened and an error behind it is documented nowhere. Names the callee, the line it is called at, and the file descent stopped short of. Only ever where deleting the key would reach the file: never unset, never for vendor code, and never for a root only autoload-dev declares, since none of those is a scope you chose Delete engine.project_paths from docuccino.yaml to descend into every PSR-4 root you declare under autoload, which is the default, or add this file’s directory to the list you keep
inference.ambiguous-narrowing info More than one return site is reachable for the narrowed type, so the first in source order was used and the recovered shape may be ambiguous Give the action one return site per status, or pin the ones you care about with #[Response]

The document has one operation per path and method, and one component per name. These codes fire where two things want the same slot, or where a name reached the document that a client can’t use.

Code Severity What it means What to do
route.build-failed error Documenting one route threw, so it was omitted or replaced by a skeleton operation The message quotes the failure and names the route. Fix it, or exclude the route from the document
route.duplicate-operation error Two routes resolve to the same operation identity — their paths differ only by a parameter’s name, which isn’t part of an identity — so a semantic diff pairs them as one operation Give one of them a path that differs by more than a parameter name
route.operation-collision error Two routes claim one path and method, usually the same URI on two hosts, and OpenAPI has room for one Remove the duplicate registration, or give each host its own document — see routes bound to a host
route.duplicate-operation-id warning Two routes publish one operationId, so a generated client names one function for the pair Give one its own id with #[OperationId], or name the routes distinctly
route.fallback-omitted info A Route::fallback() catch-all was left out: its path stands for every unmatched request, not an endpoint Nothing, usually. Document the not-found body as a 404 on the operations that produce one — see Fallback routes
route.unmatched-exclusion warning A withoutMiddleware() removed nothing and named no alias the build could resolve — so either the route never carried it, or it is another spelling of a middleware the route does carry and the responses behind it are documented without being enforced Check the spelling, or write both sides in the same one — the class name on both, or the alias on both. See Opting out of middleware
route.middleware-registrations-unreadable warning The application’s middleware registrations could not be read, because the HTTP kernel that writes them to the router could not be resolved. Nothing a route inherits from a middleware group is visible then: a group’s throttle publishes no 429 and no rate-limit headers, and a group’s authenticator publishes no 401 and no security requirement, which documents a protected route as public. Middleware written on the route itself is unaffected Settle whatever makes Illuminate\Contracts\Http\Kernel unresolvable — the message quotes the container’s own reason. A dev-only class named in bootstrap/app.php’s middleware block is the usual cause of one that only fails in production
route.middleware-aliases-unreadable warning The framework’s own default middleware alias table could not be read, so middleware is resolved through whatever aliases the router itself holds — nothing at all where the HTTP kernel could not be resolved either. A middleware named by its class is then no longer equated with its alias Report it, quoting the message: the installed laravel/framework does not carry the table this version reads
route-binding.column-untyped info A {invoice:reference} binding names a column nothing types, so the parameter is a plain string rather than the column’s type Add a @property tag (or a $casts entry) for that column on the model — see Path parameters
route-binding.untyped info A path parameter is bound to a class nothing enabled could type — a custom UrlRoutable, an int-backed or pure enum (Laravel binds string-backed enums only), or an Eloquent model with the eloquent integration off — so it is documented as a plain string Declare the segment’s type with #[PathParameter], or bind a string-backed enum or an Eloquent model with the eloquent integration enabled
tags.name-collision info Two controllers of the same short name derived one default tag, so their operations group together Nothing, if that grouping is what you meant. Otherwise split them with #[Group] or tags.map
components.name-collision warning Two components asked for one name — two schemas, or the shared API version header and something already holding the name it asks for — so each was published under a name of its own — derived from its namespace, or from a hash of its content where there was no namespace to walk Nothing, if those names read well. A hashed one rarely does: name them yourself with #[SchemaName]; for an error body, #[ErrorComponent] where it arises from a throw, or #[Response(errorComponent:)] where the operation declares it. A name reaches the shape under a response only where the response states one representation. The message lists every claimant and the name it got. For the version header, rename the component holding the name or name the header something else with api_version.header
components.example-name-conflict warning Two operations sharing one error body give the same example name to different examples, so neither example kept that name: each publishes on the shared response under it plus a hash of its own content, rather than one key meaning two things Rename one of them, or make them agree, and the name is published as you wrote it. The message names the key and the component the examples went to
components.name-invalid warning A shared error-response name an OpenAPI component key can’t carry reached the document — from an overlay, since a producer’s own name is refused where it’s written — so the body was published under its status instead Fix the name: a component key is letters, digits, ., _ and - only. The message names whatever the document says wrote it
document.schema-invalid error The assembled document failed validation against the UIR schema Report it — this is a bug in Docuccino, not in your app. The message gives the JSON pointer and the rule that failed
document.transformer-failed error A document transformer threw. Every other transformer still ran and the document was still written, so what it holds may be neither what that transformer would have made of it nor what it was before Report it, quoting the message — a built-in transformer throwing is a bug in Docuccino, one of your own or a package’s is a bug there. Use --fail-on=error where a pipeline must refuse to ship the document meanwhile

Docuccino widens rather than guesses. Each of these says which response got vaguer and why.

Code Severity What it means What to do
inferred-response.payload-unrecoverable info An action returns a bare framework response whose body the document still can’t describe — a JSON body of unrecovered shape, or a stream whose media type nothing states Build the payload where the analyzer can see it, or name the body with #[Response(type: …)] — which silences it, as does #[IgnoreResponse]. A stream needs type: and mediaType:: a media type is what it lost, and mediaType: alone documents nothing to put under it. See framework response objects and file downloads and streams
inferred-handler.too-dynamic warning Your exception handler couldn’t be folded to a fixed response shape for some exception types, so those errors are documented without the shape it renders — and where Docuccino can see your handler replaced the framework’s body, with no body at all, which a contract test on a response that really returns bytes will fail Return a JsonResponse from the arm — response()->json(…), not a plain response(), a view or a redirect — with the payload written at that call site and a literal integer status (404, not $e->getCode()). The payload is what settles it: where only the status or the Content-Type folds, the response publishes that much and no shape. #[Response(status: …, type: …)] publishes the shape instead, and this warning keeps naming the callback. See Error responses
inferred-handler.render-callback-skipped info A render callback couldn’t be analyzed and was skipped, so its error responses fall through to the next tier Register the renderer as an invokable object, an [$object, 'method'] pair, or a closure typed to the exception it handles
validation.rule-unrecoverable info A validation field’s rules are a closure, a custom rule object with no #[RuleSchema], or a conditional descriptor, so the field is documented from its type alone — or omitted where it has no type either Express the field with recoverable rules, or annotate the rule class with #[RuleSchema]
validation.rule-values-unread info A rule states a value that isn’t written at the rule — it comes from a call, a variable or a spread — so the constraint is left off and the field keeps the rules that did recover Write every value where the rule is (Rule::in('draft', 'live')), which is what settles it — a partial list would make a generated client reject a value the API accepts. An overlay corrects the document instead, and this notice keeps naming the rule
validation.rule-unhandled info No transformer handled a validation rule, so the property stays permissive Nothing, for rules with no schema meaning. For one that should constrain the schema, register a rule transformer — see Documenting a custom rule
validation.container-undecided info A field’s array rule has no item or key rules under it. array is Laravel’s one word for both containers, so a JSON array and a JSON object both satisfy the field, and it is documented as either Add field.* rules for a list, or dotted field.<key> rules for an object, and the document states the one the endpoint means. A #[BodyParameter] naming a key inside the field answers it too, as does one naming the field with a type: that says which shape it is — object for a free-form map with no keys to enumerate. A spatie Data property states it in its type instead — list<string>, array<string, mixed> — which the build reads. Leaving it is sound: what is published is true, only wider than the endpoint

An attribute that couldn’t be applied never fails the build — it says so here and the document keeps what it would have had.

Code Severity What it means What to do
attribute.body-on-bodyless-status warning A #[Response] names a body under a status HTTP forbids one on, so the body wasn’t documented Document it under a status that may carry one, or drop type: — 1xx, 204, 205 and 304 never carry content
attribute.body-parameter-name warning A #[BodyParameter] whose name is not a field path — it has an empty segment, from a leading, trailing or doubled dot, or the name is empty — so no property was documented Write the name the way a validation rule key is written: nickname, meta.validation_overrides, items.*.id. A dot that belongs to the field name itself is escaped \.
attribute.body-parameter-parent warning A #[BodyParameter] whose name nests under a field the body can’t nest into — a scalar, an allOf/anyOf/oneOf, or a $ref to a shared component — so no property was documented. The message names the field and what it’s documented as For a scalar, document the parent as an object first with a #[BodyParameter] naming it and type: 'object', or name a top-level field instead. For a shared component, declare the property where the component is defined — every operation using it would inherit one added here — or patch this one body with an overlay
attribute.error-component-invalid warning An error component name an OpenAPI component key can’t carry — from an #[ErrorComponent] or from a #[Response(errorComponent:)] — so it named nothing and the response kept the name it would have had. The message names the declaration to fix Fix the name: letters, digits, ., _ and - only. A reason phrase as one word — NotFound, TooManyRequests — reads best as a generated client’s type. See Name your own errors
attribute.error-component-contested warning Two exceptions one action signals name the same status’s response differently, and a response carries one name, so the default stands Keep the attribute on the exception that response really is, give the errors statuses of their own, or name the body on the render method that builds it
attribute.error-component-unread warning An #[ErrorComponent] on an action, which names nothing: the attribute names an error where the error is defined, not where an operation answers with it. Reported for the action’s own declaration only — one inherited from a controller would say the same thing on every route under it Move it to the exception class the error is raised from, or to the render method that builds the body. For one status of this operation, use the errorComponent: argument of the #[Response] that declares it
attribute.error-component-unreachable warning A #[Response(errorComponent:)] whose name reached nothing: the status shares no error body (anything below 400), or the status is a $ref to a component that was named where it is defined For a non-error status, move the name to the error status it describes or drop it — a success body’s schema is named after the class type: points at. For a referenced one, name the component at its own definition
attribute.hidden-unmatched warning A class-level #[Hidden('…')] names no property the schema publishes, so it hid nothing — and where the name went stale through a rename, the field it was written to keep out is published under the new spelling. The message lists what the schema does publish. Not raised for an Eloquent model: a model’s documented columns are recovered from @property tags, $casts and $fillable rather than declared, so a name outside them is far more often an undocumented column than a typo Correct the name against the list in the message, or delete the declaration. A Data class hides by the property’s own name, not by the key #[MapName] publishes it under. See #[Hidden]
attribute.mock-invalid warning A #[Mock] that can publish nothing — no faker expression and no seed group, or a class-level one that doesn’t name its property Fill it in or delete it. The message names the exact class or property — see Mock data hints
attribute.mock-unknown-property warning A class-level #[Mock(property: '…')] names a member the schema doesn’t publish, so the hint was dropped Fix the name, or unhide the property. Columns, toArray() keys and validated fields are all named as they appear on the wire
attribute.example-unusable warning An #[Example] that doesn’t describe one example — no value, or more than one, or more than one target, or a nameless one sharing a node with named ones Give it exactly one value and at most one target; the message names which half is wrong. See #[Example]
attribute.example-target-missing warning An #[Example] points at something the operation doesn’t document — a status, a media type, a parameter, a request body Document the target first with #[Response], #[BodyParameter] or a parameter attribute, or point the example somewhere it is
attribute.example-duplicate-name warning Two #[Example] declarations on one node share a name, and a name is a map key, so the second was dropped Give each its own name — the message says which name and which node
attribute.in-docs-unknown warning An #[InDocs] names a document key that isn’t configured, so the key pins nothing — and where a declaration names no configured key at all, #[InDocs] is an allow-list, so every route or webhook under it is left out of every document. The message names the routes it is written on and lists the documents that do exist. Raised once per key, however many routes the declaration covers Correct the key against the list in the message, or delete the declaration — a document renamed in documents.* keeps its old key only in the attribute. To keep a route out of every document on purpose, use #[ExcludeFromDocs]. See #[InDocs]
attribute.ignore-param-location warning An #[IgnoreParam] whose in: names no parameter location, so it dropped nothing Use cookie, header, path or query — in any case — or leave in: off to drop the name from every location. See #[IgnoreParam]
attribute.ignore-param-unmatched warning An #[IgnoreParam] on an action names a parameter the operation doesn’t document, so it dropped nothing and the field the author meant to hide — if it is there under another name — is still published. The message lists what the operation does document. Reported for the action’s own declaration only: one inherited from a controller names a key only some of its actions take, and an action that never documented it is not a mistake Correct the name against the list in the message, or delete the declaration. A parameter that was renamed keeps its old spelling only in the attribute; a key only some actions take belongs on the class. See #[IgnoreParam]
attribute.ignore-response-unmatched warning An #[IgnoreResponse] on an action names a status no producer would have written, so it dropped nothing. The message lists the statuses the operation does document. Reported for the action’s own declaration only, for the same reason attribute.ignore-param-unmatched is Correct the status against the list in the message, or delete the declaration — a status only some actions answer with belongs on the class. An ignore names one status, so a response published as a range (3XX for an unpinned redirect) is not one it can drop. See #[IgnoreResponse]
example-file.missing warning An #[Example(file: …)] names a file that isn’t there Create it or fix the path, which is read relative to your application root. Docuccino watches it either way, so the example appears the moment the file does
example-file.invalid warning An #[Example(file: …)] file didn’t decode — not .json/.yaml/.yml, unparseable, or empty Fix the file; the message quotes the parser
example-file.escapes-base-path error An #[Example(file: …)] path doesn’t name a path inside your application — it leaves the root, or holds a character no filesystem can — so nothing was read Write the path relative to the application root; a path that leaves it is refused by design
attribute.description-unusable warning A #[Description] that documented nothing: it carries both text: and file:, or neither, so it says nothing certain — or it carries request: true on an operation with no request body, so there is nothing for it to describe The message says which. Give it exactly one of text: and file:; for the request form, document the body first or drop request: to describe the operation instead. See #[Description]
attribute.path-parameter-unmatched error A #[PathParameter] names no {segment} of the route’s own URI. OpenAPI requires every in: path parameter to correspond to a template variable, so the parameter is withheld rather than published — an invalid document is not something a generated client can read, and a path parameter no request can carry describes nothing the server accepts, so nothing true is lost by leaving it out. The message lists the segments the template does have. Reported for the action’s own declaration only: one inherited from a controller covers a segment some of its actions have, and an action without it is not a mistake — it is withheld there too, so the document stays valid either way Correct the name to a segment of the route’s URI, or delete the declaration. A parameter that is not in the URI is #[QueryParameter], #[HeaderParameter] or #[CookieParameter]. See #[PathParameter]
attribute.property-unsupported warning A declaration says something the schema it sits on has no field for — a #[Description(file: …)] on a property or a class (no application root reaches a schema), a #[Description(request: true)] on either (a request body is one operation’s use of a type, not part of it), or an #[Example] naming what only an Example Object holds (name:, status:, request:, parameter:, file:, externalValue:) Say it in the field that exists: #[Description(text: '…')] for the prose, a bare #[Example(…)] value for the example. Everything else belongs on the action — see #[Example]
attribute.schema-class-unread warning A Docuccino attribute on a request type — a Form Request, a DTO, a Data class — that nothing reads on a TYPE. TARGET_CLASS means an action class or a schema class, and an action’s declarations are read off the action; PHP accepts them on the type too, where nothing does. The message names the class and the attribute, and the remedy says where that attribute IS read. Raised once per attribute, so a repeatable one written twice is one report Move it to where it is read — the action, for an operation-level declaration. A type is read for #[Description], #[SchemaName], #[SchemaId], #[Hidden], #[Mock] and #[BodyParameter]
attribute.schema-class-unusable warning A #[BodyParameter] on a request type that no operation could read. It is honoured on a type — but only where the route documents a request body, and a read verb (GET, HEAD) sends the same validation rules to query parameters instead. Reported once per type, and only when NO operation built from that type documents a body: a type shared by GET /things and POST /things carries a declaration that is doing its job on the POST, so nothing is said about it Describe what a read route accepts with #[QueryParameter] on the action, or delete the declaration. If a write route was meant to accept the type, the missing route is the real fix. See #[BodyParameter]
attribute.unreadable warning A Docuccino attribute at the named declaration could not be instantiated — usually a typo’d argument — so it was ignored Check the arguments at that declaration against the attribute’s constructor; the message names what it threw
description-file.missing warning A #[Description(file: …)], or info.description.file in config, names a file that isn’t there Create it or fix the path, which is read relative to your application root. Docuccino watches it either way, so the description appears the moment the file does
description-file.escapes-base-path error A #[Description(file: …)], or info.description.file in config, doesn’t name a path inside your application — it leaves the root, or holds a character no filesystem can — so nothing was read Write the path relative to the application root — see symbol-anchored prose

A docblock tag can only hold text, so a value written in one has to be read back as the type it describes. Where the text doesn’t read as that type, nothing is published — an example of the wrong type would be copied out of the document and rejected by your own API.

Code Severity What it means What to do
docblock.example-untypable warning An @example (or a #[RuleSchema(example: …)]) doesn’t read as the type declared beside it — @example n/a on an int, say — so no example was published Write the example as the value the property really is: @example false for a boolean, @example 7 for an integer, a JSON literal (@example ["a", "b"]) for an array or object. The message names the property, the type and the text

Everything Docuccino read out of your configuration and couldn’t use. Every code here names the exact key, or — for the four that are about the file itself — the file.

Code Severity What it means What to do
config.file-invalid error docuccino.yaml isn’t configuration this can read — not valid YAML, or valid YAML whose aliases expand to more settings than a configuration holds — so nothing in it was applied. Every command that builds a document refuses rather than building one from defaults, and the refusal doesn’t go through --fail-on Fix the line the message names. A duplicate key counts: YAML refuses two settings of the same name rather than quietly keeping the last. An expansion is an alias (*name) repeated inside an anchor: write the settings out
config.file-not-a-map error docuccino.yaml holds something other than a map of settings — most often it’s empty, or everything in it is commented out. Every command that builds a document refuses rather than building one from defaults Write the settings as top-level key: value pairs, or delete the file: no file at all is a supported state, and an empty one is not the same thing
config.file-unreadable error docuccino.yaml is there and couldn’t be read. Every command that builds a document refuses rather than building one from defaults Check the file’s permissions
config.file-misnamed warning There’s no docuccino.yaml, but there is a file that was obviously meant to be it — docuccino.yml, a dotfile, a .dist template — and it was not read. A warning rather than a refusal, because there is no configuration file: the document built from defaults is what an application with none is supposed to get Rename it to docuccino.yaml. That’s the one name configuration is read from, so there’s no precedence to work out
config.not-migrated error There’s no docuccino.yaml, and config/docuccino.php still holds build settings — which nothing reads. Building from defaults would have thrown away the routes, info and security you configured and produced a plausible-looking document, so every command that builds one refuses before it starts. That refusal doesn’t go through --fail-on: the flag gates what a build found, and this is the configuration not being read at all. docuccino:migrate-config still runs, because it’s the way out Run php artisan docuccino:migrate-config to write docuccino.yaml from the settings already there, then delete them from config/docuccino.php
config.stale-php-keys warning docuccino.yaml says what your documents are, and config/docuccino.php still holds build settings beside it. Nothing there is merged or given precedence — they were ignored Delete them. config/docuccino.php keeps enabled, each document’s viewer and cache.store, and nothing else. If those settings never made it into docuccino.yaml, php artisan docuccino:migrate-config --force writes them there first
config.unknown-setting warning A key in docuccino.yaml names no setting, so nothing written under it does anything — a misspelling, or a block one indentation level too far in, which moves a whole bag under the wrong parent. The message names the key as you wrote it, and the help names the one setting you probably meant: the same name at the depth it belongs at, a near miss by spelling, or the fact that it’s read from config/docuccino.php instead Fix the key, or delete it. A bag under an unknown key is reported once rather than once per line, so the count is the number of mistakes. Nothing is reported inside info, servers, security.schemes, security.default, security.document, tags.map, lint.leakage.patterns, representation.examples.formats or query_builder.filter_descriptions, where the names are yours
config.viewer-orphan warning config/docuccino.php configures a viewer for a document docuccino.yaml doesn’t define. Boot can’t know that, so the routes are registered and every request to them fails Add the document to docuccino.yaml, or delete its viewer entry. A document with no viewer is fine — an export-only document is an ordinary shape
config.viewer-route-collision warning Two documents configure the same viewer.route, and the framework keeps only the last one registered — so one document’s page is unreachable Give each document its own viewer.route
config.value-type warning A key in docuccino.yaml holds a value of the wrong shape, and it was refused rather than converted, so the setting fell back to its default. Usually YAML read the value as something other than what it looks like: version: 1.10 is the number 1.1, and enabled: no is the text no rather than false. Every key the shipped file declares is checked against the shape that file writes there — a section holding a line of text, a list holding one, a text key holding a number. The two spellings of the same mistake are why: routes: 'api/*' and routes: { include: 'api/*' } both leave no route filter at all, so every route in the application is documented Do what the message says — usually quote it, write false/true, write the section as key: value pairs indented under its name, or write the list as one - entry per line. Converting the value would be worse than refusing it: 1.10 would publish as 1.1, a different version number, and a non-empty no would come out true. An on/off switch is reported by config.not-a-switch instead, and error_responses by config.unknown-error-responses — one line to fix gets one report
config.value-not-finite warning A setting holds .nan, .inf or -.inf. No document can carry those, so the setting was read as empty and fell back to its default Write a finite number, or drop the key
config.extension-missing warning An entry in extensions names no autoloadable class, or isn’t a class-string at all, so that extension contributed nothing Fix the class name and namespace, and check it’s autoloadable. InvoiceExtension::class still evaluates to a string when the class is missing, so a typo is otherwise silent
config.engine-config-missing warning engine.config names an analyzer config file that isn’t there, so the analyzer ran without whatever it registers Fix the path — it’s read relative to your application root — or drop the key. See Engine
config.unknown-integration info An integrations.<key> bag names no integration, so nothing reads it Fix the key. The message suggests the one you probably meant, and Integrations lists them all
config.enabled-ignored info You set enabled on an integration that’s always on and has no toggle Delete the key
config.machine-dependent-value warning A value your clients act on — an OAuth flow URL, a session cookie name — came from the build machine rather than from anything you pinned, and was published as-is Pin it in docuccino.yaml — see Pin the values your clients act on
config.machine-dependent-path info A configured path points outside your application, so it’s folded into the document’s hash verbatim and the output stops being portable between machines Move the target inside the application; in-app paths are stored relative to the base path
config.path-rejected warning A configured path holds a NUL byte, which no filesystem path can hold, so nothing read it — an overlay wasn’t applied, a content tree wasn’t compiled, the fragment cache stayed off Write the path in single quotes, or escape the backslash. "\0" in a double-quoted PHP string is a NUL byte, not the two characters it looks like
config.not-a-switch warning A key that is an on/off switch holds something that is neither true nor false, so the switch could not be honoured and its default was used instead Write true or false. Any other value is refused rather than coerced — 'no' and 'off' are strings, and a coerced string is true, so coercing one would turn the switch on
config.route-filter-unusable error routes.filter names something that can’t be applied — not a class-string, not autoloadable, not a RouteFilter, or it threw while the container built it — so the run stopped rather than documenting every route the globs admitted Fix the class name, or implement the contract — see routes. Skipping the filter would publish a route set you had narrowed on purpose, so nothing is written until it works
config.tag-mapper-unusable warning tags.mapper names something no tag mapper could be got from — not a class-string, not autoloadable, not a TagMapper, or one the container could not build — so tags are published exactly as your controllers and #[Group] attributes wrote them Fix the class name, or implement the contract — see Tags. The message says which of the four it was. Unlike routes.filter this doesn’t stop the run: unmapped tags are still your API’s real tags, where a route set you narrowed on purpose is not
config.unknown-value warning A setting whose values are a closed set — error_responses, tags.default_strategy, the representation policy keywords, versioning, viewer.source, on_route_error — holds a value that is none of them, so the setting fell back to its documented default. Usually a mistyped keyword, or one YAML read as something other than text: 1.10 is the number 1.1, and a bare date is a number too Write one of the values the message lists. It names the setting, what you wrote and the value used instead; the accepted values for each setting are in Configuration
config.unknown-tag-parent info A tag in tags.definitions is parented to a tag no definition declares, so the link was dropped Declare the parent, or remove the parent key. OpenAPI requires a parent tag to exist
config.tag-parent-cycle info A tags.definitions parent link closes a cycle, so it was dropped to keep the hierarchy a tree Re-parent one of the tags in the cycle the message names
config.tag-mapper-unhashable info tags.mapper resolved to a class declared in no file — eval()’d code, say — so the fragment cache can’t tell whether its answer has changed and rebuilds every operation it tags Declare the mapper as an ordinary autoloadable class in a file of its own. Only raised when the fragment cache is on, because with it off there is nothing to rebuild
extension.unhashable info A registered extension is declared in no file — eval()’d code, say — so the fragment cache can’t tell whether its body has changed, and is off for the whole document: an extension shapes whatever operations it is run over, and nothing can say which Declare the extension as an ordinary autoloadable class in a file of its own. Only raised when the fragment cache is on, because with it off there is nothing to rebuild
config.unknown-filter-kind info A key in integrations.query_builder.filter_descriptions names no Query Builder filter kind, so the sentence under it is never used Use one of the kinds the message lists — see overriding the generated prose
config.format-sample-rejected warning A sample in representation.examples.formats couldn’t be published — a field’s own validation rules reject it, or it isn’t a string at all — so that field falls back to the built-in sample Use a value every field carrying that format accepts; the message names the format, your value and the keyword it failed — see representation
config.export-no-targets error export.targets is set but holds no entries, so the document has nowhere to go Remove the key to fall back to export.path, or list at least one {format, path} entry
config.export-target-shape error An export.targets entry isn’t a {format, path} pair with both members set to a non-empty string Fix the entry — see Export targets
config.export-unknown-format error An export target names a format Docuccino doesn’t emit Use one of the formats the message lists
config.export-duplicate-format error Two export targets name the same format, and --format and the viewer’s artifact both have to resolve to one file Keep one target per format
config.export-duplicate-path error Two export targets in one document write the same file, so the later would clobber the earlier Give each target its own path
config.export-yaml-unsupported error An export target asks for a .yaml file in a format that has no YAML serialization Give it a .json path rather than a .yaml file holding JSON
config.export-path-collision error Two documents write the same file, so one would clobber the other Give each document its own export path — see Multiple documents
config.accept-refused warning A code in diagnostics.accept was reported as an error, and acceptance never covers an error, so it still failed the run Fix what the error reports, then delete the entry — see Diagnostics
config.accept-unused warning A code in diagnostics.accept that nothing reported: the cause is fixed, or the code is misspelled Delete the entry. Checked only once a run has built every document, so a single-document run never raises it
config.export-path-ignored info A document sets both export.targets and export.path, and targets win, so the path is never written Delete the export.path key
integration.disabled info An integration’s package is installed, but the integration is off — either opt-in and never switched on, or explicitly disabled Set integrations.<key>.enabled = true if you want its contributions. The message names the package and the key

What each package integration couldn’t recover from your code. Every one of these degrades to a correct-but-vaguer document rather than dropping the endpoint.

Code Severity What it means What to do
eloquent.no-columns info A model exposes no documentable columns, so its response is a bare object Add @property (or @property-read) tags for the model’s attributes — e.g. @property int $id — so its columns and their types are recovered
eloquent.custom-date-serialization info A model overrides serializeDate(), so its date attributes’ wire format isn’t statically known and they’re documented as plain strings Nothing you can annotate: no attribute carries a column format, and a docblock type has no format to state. If clients need an exact one, state it in an overlay — which corrects the document, and this notice keeps naming the model
eloquent.unresolved-eager-load info A relation named in $with couldn’t be resolved to a related model, so it’s omitted from the schema Give the relation method a generic return type — e.g. HasMany<LineItem, $this>
eloquent.unmapped-morph info A morph variant has no Relation::morphMap() alias, so the union is emitted without a discriminator Register an alias in Relation::enforceMorphMap([...]) for every variant, so a stable discriminator can be emitted
query-builder.unresolved-entry warning A Query Builder allow-list entry couldn’t be resolved statically, so it’s omitted from the docs and that list is documented as a plain string rather than a value enum Use a literal value or a factory call — AllowedFilter::exact('status') — so it can be recovered
query-builder.no-allowlists-recovered info A paginating terminal was reached and no allow-lists and no default sort were recovered from the chain, so it is behind an indirection the trace couldn’t follow Declare the allow-lists somewhere the trace reaches — see query objects. An endpoint whose chain WAS read and simply offers no filters or sorts doesn’t report this
query-builder.partial-on-enum info A partial-match filter over an enum-cast column can’t have its values enumerated Use AllowedFilter::exact() so the enum’s values are documented — see partial filters over an enum column
query-builder.default-config info The package’s config wasn’t readable, so documented parameter names use its defaults (filter/sort/include/fields) Publish it — php artisan vendor:publish --tag=query-builder-config — so custom names reach the docs
query-builder.legacy-package-version info spatie/laravel-query-builder below v7 is installed, so the sort/include/fields allow-lists are documented as plain strings rather than value enums Upgrade to spatie/laravel-query-builder ^7 to document the sort/include/fields allow-lists as enums
query-builder.enum-name-collision info Two sort/include/fields values would share one SDK enum member name, so distinct value-derived names were published instead Rename one of the colliding allow-list entries so each value mints its own member name
spatie-data.nested-collection-wrap warning A nested collection of Data objects will be sent as {"data": [ … ]} because data.wrap is set, while the document describes a bare array Unwrap the property with a #[WithTransformer], or state the wrapped shape in an overlay — see Nested collections
spatie-data.response-status-unresolved info A Data class’s calculateResponseStatus() doesn’t fold to constant statuses, so the success response is documented as 200 Return constant ints, or a ternary whose arms are both constant — see Success statuses
spatie-data.unknown-mapper info A Data class uses a name mapper Docuccino doesn’t recognize, so its property names are documented unmapped Use one of the package’s own mappers, or rename the properties
json-api-paginate.default-config info The package’s config wasn’t readable, so documented pagination parameters use its defaults (page[number]/page[size]) Publish it — php artisan vendor:publish --tag=json-api-paginate — so custom names reach the docs
rate-limit.unregistered-limiter info A route throttles on a named limiter nothing registers with RateLimiter::for(), so the allowance can’t be documented Register it in a service provider, or state the allowance inline as throttle:60,1 — see Named limiters
rate-limit.multiple-throttles info A route carries more than one throttle middleware; one 429 is documented from the first Nothing. The others are still enforced — they just aren’t separately representable in OpenAPI

A route’s authorization is read from its middleware, so the 403 is documented from the presence of a gate. Usually that is the whole story; where it is not, this says so and the response stays put.

Code Severity What it means What to do
authorization.gate-cannot-deny info A route’s ->can() gate resolves to a policy method whose whole body is return true;, and nothing else on the route produces a 403 — so the error the operation publishes is one no request can provoke, and it reaches a consumer as a dead catch branch in their generated client Tighten the policy method, so the 403 describes something that can happen — or, if the gate is deliberately a formality, drop the response with #[IgnoreResponse(403)] on the action. The response is published either way: dropping a real error needs certainty a build cannot have, so this reports and changes nothing

Only a literal, unconditional return true; counts — or, where an analyser is installed, a body every one of whose returns it can prove is true. A method that reads anything at all — the user, a request, ambient state, a helper — can deny, and the notice stays quiet even where the body looks decorative: return currentTeam() instanceof Team; names neither a user nor a permission and denies perfectly well. It is quiet, too, wherever the gate’s own answer is not the last word — a Gate::before or Gate::after hook, a policy before() method, a signed or verified middleware beside the gate, a FormRequest that authorizes, or a 403 your action throws for itself.

The notice names the class the ability method is declared in, which is not always the policy the gate resolved to: an inherited or trait-provided method is written somewhere else, and that is the file to open. For the same reason it says nothing about a method declared under vendor/ — a policy a package ships, or a base class one of yours extends. The only remedy there would be an edit to somebody else’s code, so there is nothing to report.

Both spellings of the middleware are read: ->can('view', Post::class) and Authorize::using('view', Post::class).

Checked over the finished document, so overlays, transformers and config all count. A security requirement names its scheme by key rather than by $ref, so nothing else in the pipeline resolves it.

Code Severity What it means What to do
security.undefined-scheme error An operation or the document names a security scheme that components.securitySchemes never defines. OpenAPI requires every requirement to name a declared scheme, so the document is invalid and a generated client has nothing to build the credential from Add the scheme under this document’s security.schemes, or name one it already defines — the message lists them. The reference is still published: dropping it would leave a valid document saying the endpoint is public, which is the one answer worse than an invalid one
security.undeclared-scope warning An OAuth2 requirement asks for a scope none of that scheme’s flows declare, so the document contradicts itself and an authorization server following it would refuse the token Declare the scope under that scheme’s flows.*.scopes, or ask for one it offers — the message lists them

Webhooks are collected from #[Webhook] classes under the directory you configure. See Webhooks.

Code Severity What it means What to do
webhook.dir-missing warning The configured webhook directory doesn’t exist, so no webhooks were collected Create it, or unset webhooks.dir — see pointing the document at your webhook classes
webhook.dir-escapes-base warning The webhook directory doesn’t name a path inside your application and was ignored Write it relative to the application root
webhook.name-invalid warning A class carries a #[Webhook] with no name, so it isn’t in the document — a webhook is published under its name Give the attribute the name the receiving endpoint subscribes to, e.g. #[Webhook('invoice.paid')]
webhook.name-collision error Two classes claim one webhook name and method, so one of them isn’t in the document Give one a name of its own — a webhook name is the contract a consumer subscribes to
webhook.operation-collision error A webhook already documents that method from another class, so this one isn’t in the document Give one of them its own name, or a method the other doesn’t use
webhook.method-unknown warning A #[Webhook] asks for an HTTP method OpenAPI has no path-item member for, so it’s documented as POST Use one of the methods the message lists
webhook.payload-unresolved warning A webhook’s payload type resolves to no shape, so its body is an unconstrained object Name a class or array shape the payload is built from, or drop the payload argument to document the annotated class itself — see Annotate the payload. payload: 'object' resolves: in an attribute that word is the JSON one, and it documents a free-form map
webhook.build-failed error Documenting one webhook threw, so it isn’t in the document The message quotes the failure and names the webhook

Codes from the version changes a document declares under api_version. They fire while an older version’s document is derived from the code you have today — see API versioning.

Code Severity What it means What to do
versioning.dir-missing warning A configured version-changes directory doesn’t exist — or a glob entry matched nothing — so its changes were not read and every version publishes today’s shape for them Create it, or drop the entry from api_version.changes
versioning.dir-escapes-base warning A version-changes entry doesn’t name a path inside your application — or its wildcard matched one that doesn’t — and was ignored Write it relative to the application root, and keep what its wildcards match inside the application
versioning.change-invalid warning A change can’t be applied as it’s written — no version on its #[ApiVersionChange], an empty or self-referential rename, a verb leaving schema: or field: empty, a rename onto a field or parameter an operation already has, an in: that names no parameter location, a #[RenamedParameter] naming in: 'path', or a version the document’s order can’t read Fix the declaration: to: is the name in the code today, from: the one older versions publish, and every other verb names its field exactly as the code spells it today. A parameter’s in: is one of cookie, header or query, in any case — a path parameter is named by the URL template as well as by the parameter, so nothing can rename it on its own. Where the version itself is the problem, write it the way the document writes its own
versioning.change-target-missing warning A change names something the document no longer has — a field the schema doesn’t publish, on either half of the wire, or a parameter no operation in scope declares — so that version is left saying what the code says Update the change to the name as it’s spelled today, or retire it if the field or parameter is gone
versioning.change-target-unchanged warning A change says a field’s required-ness moved, or that a field was removed, and the schema already publishes it the way the older version would — so there was nothing to undo and that version says what the code says. Weaker than the rename’s check by nature: “remove from required” done twice is a no-op, so this can only tell you the code disagrees with the declaration, never that the edit already ran Read the direction: #[MadeResponseFieldRequired] says the field is required in the code TODAY and was optional before, the two Optional verbs say the reverse, and #[RemovedResponseField] names a field the code no longer has. Retire the declaration if the field has changed again since
versioning.type-unresolved warning A #[RemovedResponseField] states a type: that’s neither a class this document publishes a response schema for nor an OpenAPI type name, so the field went back with no shape at all Name a class this document publishes, or one of string, integer, number, boolean, object, array — each may be suffixed [] for a list of them or ? for one that may be null. Leave type: out to publish the field unconstrained on purpose
versioning.schema-unresolved warning A change names a class this document publishes no schema for — or, for a request verb, no request body schema for — so it was skipped Name the class whose shape the document actually publishes — a change can only rewrite a schema the document contains
versioning.version-unstated warning A document declares api_version but writes no info.version, so there’s nothing to derive a version from Set info.version to the version this document describes — that value is the API version
versioning.unordered warning The versions in play are neither all dates nor all semver, so the changes couldn’t be placed in order and none was applied Write every version the same way, and set versioning to date or semver
versioning.scope-matches-nothing warning An #[AppliesTo] names no operation this document publishes that schema for, so that part of the change applies to nothing — including the case where this document publishes the schema for no operation at all. For a parameter rename, which names no schema, it means the selector names no operation this document publishes at all Write the operation as the document names it — GET /api/things, an operationId, or either with a * — and check this document publishes that schema for it. Nothing is renamed: a scope that reaches nothing never widens into a document-wide rename
versioning.scope-unforkable warning A scoped change matched an operation it can’t be narrowed to — the schema contains itself, so no private copy of it can be written, or the operation is published through a path item shared with operations the scope leaves out, so both operations are one node. The operation stays at the shape the code publishes, parameter names included Drop the #[AppliesTo], or widen it to every operation that publishes the schema — or, for a shared path item, to every operation behind it — and the shared node is edited in place instead
versioning.example-dropped warning A version moved a field of a schema — a response one or a request one — and an example published beside it couldn’t be given the shape this version publishes — a rename the walk couldn’t follow, or a #[RemovedResponseField(required: true)] putting back a field no example can carry — so it was dropped rather than published failing its own schema A consumer copies an example and sends it back, so one the schema rejects is worse than none. A rename’s rewrite stops where the schema doesn’t settle on one shape for the example — a oneOf/anyOf branch, a $ref that leads back to itself, a value that isn’t the kind of thing the schema describes, or an example already carrying both names. Pin an example that matches the schema beside it, narrow the schema so one shape governs it, or drop required: true if that version didn’t always send the field

Codes from the Markdown pages you fold into the document. See Adding your own pages.

Code Severity What it means What to do
content.dir-missing warning The configured content directory doesn’t exist, so no pages were compiled Create it, or unset content.dir
content.dir-escapes-base warning The content directory doesn’t name a path inside your application and was ignored Write it relative to the application root
content.frontmatter-not-a-switch warning A markdown page’s frontmatter sets a switch — nav.hidden — to something that is neither true nor false, so it was refused and the default used Write true or false. Frontmatter is YAML, where no, off, yes and on are strings rather than booleans
content.duplicate-slug error Two content pages share a slug, so the later one is ignored Rename one of them; a slug is a page’s address
content.duplicate-operation-id warning Two operations share an operationId, so an ::operation directive naming it resolves to the last one in path order Give one its own id with #[OperationId]
content.unresolved-directive error An ::operation or ::schema directive is missing its attribute, or points at something the document doesn’t have Point it at a documented operation id, METHOD /path, or component schema name — see Linking to your API
content.unknown-directive warning A directive Docuccino doesn’t resolve was left in the page untouched Nothing, if your renderer handles it. Docuccino resolves ::operation and ::schema
content.unresolved-nav-ref error A page’s nav frontmatter references something that resolves to nothing Fix the reference — see the frontmatter reference

Codes from OpenAPI Overlays. See Customizing the output.

Code Severity What it means What to do
overlay.invalid warning An overlay file couldn’t be read as an overlay, so it was skipped entirely The message quotes the problem. Check the file parses and carries the overlay members
overlay.conflicting-operation error An overlay action declares both update and remove, and an action carries exactly one operation Split it into two actions
overlay.unsupported-selector error An overlay target uses a selector Docuccino doesn’t resolve Rewrite it with the supported subset: object members, array indexes, and [?(@.field=='value')] equality filters — see the target selector
overlay.target-missing warning An overlay target matched no node, so the action did nothing Check the target against the document you’re overlaying — overlays edit what already exists

Codes from examples your test suite recorded. See Examples your tests recorded.

Code Severity What it means What to do
examples.recordings-empty info The recordings directory holds no recordings, so the document publishes none Recording is opt-in per assertion: pass recordAs: at the assertions whose responses you want published, and run your suite with the recorder registered. Or drop examples.recordings
examples.recordings-escapes-base warning examples.recordings doesn’t name a path inside your application — it leaves the root, or a symlink in it does — so the directory was never read and no recorded examples were published Write it relative to the application root, or point it at a directory inside the application
examples.recording-unreadable warning A file in the recordings directory isn’t a recording Docuccino can read, or records an operation its filename doesn’t name Re-record it, or delete it. The message names the file
examples.recording-orphaned warning A recording is for an operation this document no longer has — the route was renamed, moved or removed Delete the file, then re-record whatever replaced it. The message names the endpoint it came from
examples.recording-unsafe warning A committed recording still holds what looks like a credential, so it wasn’t published Re-record it; a credential string is replaced on the way out. A number is reported and left alone, because a placeholder where the schema says integer would make the example contradict its own contract — stop returning it. If the value really is public, list the pointer the message names under lint.leakage.allow — a bare property name silences the lint but never the redaction. The message names the pointer, never the value
examples.recording-unnamed info A committed recording holds a body no assertion named — a file from before recording became opt-in. It still publishes, and no run will ever refresh it Pass recordAs: at the assertion that produces that body and the next run replaces it, or delete the file if the endpoint no longer needs an example

Document-quality rules. lint.data-leakage is on by default; the rest are opt-in — see Lint.

Code Severity What it means What to do
lint.data-leakage warning A schema property, a query or path parameter, or an example or default value looks like a credential Take a property out of the shape that publishes it — #[Hidden] for a response, #[HiddenFromRequest] for a request body. Carry a parameter’s value in a header instead, since a URL is recorded in access logs and browser history. Or safelist either under lint.leakage.allow; see Data leakage
lint.missing-description warning An operation publishes neither a summary nor a description, so the document never says what it does Give the action a docblock — its first line becomes the summary — or write one in an overlay. See Descriptions
lint.operation-id-style warning An operationId a generated client can’t name a method after: empty, leading with a digit, or outside letters, digits and . - _ @ Give it an id with #[OperationId], or rename the route. See Operation ids
lint.example-mismatch warning A published example doesn’t satisfy the schema it sits beside — a string where the type says boolean, a number under a minimum Correct the example, or widen the schema if the example is what your API really accepts. Where the example is right and the schema merely under-describes it, accept the pointer under lint.examples.allow
lint.example-uncheckable warning An example was left unchecked because the validator wouldn’t read the schema beside it, so this build knows nothing about that example either way Nothing you can fix in your own code: a schema no validator will read is a bug in Docuccino. Report it with the message, which names the schema’s pointer and what the validator objected to. lint.examples.allow accepts the pointer meanwhile
lint.unresolved-reference warning A path item, response or request body is written as a $ref to a component the document doesn’t define, so nothing downstream can read what it promises — a generated client, a viewer and a contract test all see an empty node Define the component the pointer names, or correct the pointer. An overlay that renamed or removed a component is the usual cause
lint.unpinned-redirect info An operation’s document never says exactly one thing about its redirect: the 3XX range stands alone, so it never says which redirect it is, or the range stands beside a concrete 3xx, so it says both Name the code with #[Response(302)], which declares it and retires the range together. A code named in an overlay needs a second action removing the 3XX response, since an overlay is applied after the document is built. Either shape is accepted as-is under lint.unpinned_redirect.allow
lint.vacuous-union info An anyOf in the operation carries an unconstrained branch ({}, or the true that means the same), so the value validates as anything and the typed branches beside it add no constraint Pin the arm that recovered as “anything” — a return docblock or #[Response] — or accept the operation under lint.vacuous_union.allow
lint.undocumented-tag warning Operations carry a tag tags.definitions never declares, so it publishes without the summary, description and parent the declared ones have Add an entry for it, or map it onto a declared tag with tags.map or #[Group]. See Undocumented tags

Docuccino’s document is OpenAPI 3.2-shaped. Exporting openapi-3.1 or openapi-3.0 rewrites what those versions spell differently and drops what they can’t express — each one saying which member, at which JSON pointer. None of them mean you did anything wrong: they’re the price of the older target, and keeping the 3.2 artifact alongside costs you nothing.

Where a code is info or warning, the quieter one means Docuccino rewrote the construct into something equivalent and the louder one means it had to drop it.

downlevel.empty-responses is the one that isn’t only about the older target: it reports a gap in the document itself. Every OpenAPI version rejects a present-but-empty responses: {} — 3.1 and 3.2 included — and what they allow instead is leaving responses out altogether, which 3.0 does not. So the placeholder is the 3.0 emitter’s alone, while the missing responses it stands in for are worth naming whichever version you ship.

A downlevel.* code is raised once per emitted format that raises it, by the emitter doing the rewriting — nothing dedupes across formats. downlevel.empty-responses comes from the 3.0 emitter only, so it appears once in a build exporting 3.0 and not at all in one exporting 3.2, 3.1 or Postman. A 3.0 export chains through the 3.1 emitter, so it also carries every code the 3.1 target raises. --format=uir emits no report of this kind at all.

Code Severity What it means What to do
downlevel.query-method warning The 3.2 query HTTP method has no 3.1 spelling, so the operation isn’t in the emitted document Keep the 3.2 artifact for consumers that need it
downlevel.additional-operations warning The 3.2 additionalOperations member has no 3.1 spelling and was dropped Model custom methods with a standard method for 3.1 toolchains
downlevel.tag-summary warning A tag’s 3.2 summary was dropped; 3.1 tags fall back to their name for display Nothing, or lead the tag’s description with the same sentence
downlevel.tag-parent warning A tag’s 3.2 parent was dropped. Where the document also carries the hierarchy as x-tagGroups — which a generated document does — only the native member is lost; where it doesn’t, the hierarchy flattens Nothing, when x-tagGroups is there. Otherwise nest the naming instead — “Billing / Invoices”
downlevel.tag-kind warning A tag’s 3.2 kind was dropped, so 3.1 consumers treat every tag the same Nothing
downlevel.member-not-in-3.1 warning A member OpenAPI 3.2 added, which 3.1 and 3.0 have no word for, was dropped where it stood — the message names it and its JSON Pointer. Covers the streaming members (itemSchema, prefixEncoding, itemEncoding, and a media type’s own description), an Encoding Object’s nested encoding, prefixEncoding and itemEncoding, an example’s dataValue and serializedValue, a response summary, a server name, a security scheme’s deprecated, an OAuth deviceAuthorization flow, and the components.mediaTypes bucket — whose members are inlined where a $ref named them, so no reference dangles Keep the 3.2 artifact for consumers that need the member; nothing a 3.1 or 3.0 document can say replaces it
downlevel.value-not-in-3.1 warning A VALUE OpenAPI 3.2 added to a member’s domain, which 3.1 and 3.0 don’t define — the member exists at every version and only the value is 3.2’s, so it is a different loss from downlevel.member-not-in-3.1 above. Two of them today. An in: querystring parameter is dropped whole: there’s no 3.1 way to describe the raw query string as one value, and it takes content rather than schema, so it isn’t a query parameter under another name. A shared one is reported where it’s defined and every $ref naming it goes with it, and a parameters member the drop empties is removed rather than left empty. A cookie parameter’s style: cookie costs only the member: the parameter stands at form, the one cookie style 3.1 spells Keep the 3.2 artifact for consumers that need the parameter — nothing a 3.1 or 3.0 document can say replaces it. The cookie style needs nothing written in its place: form is 3.1’s default there and both styles default explode to true, so what the older artifacts lose is RFC 6265 escaping and the ; an exploded value joins on, neither of which any explode value restores
downlevel.webhooks warning webhooks was dropped; OpenAPI 3.0 doesn’t define it Keep the 3.1 or 3.2 artifact for consumers that need the webhook contract
downlevel.component-path-items info components.pathItems was dropped; OpenAPI 3.0 doesn’t define it, so each path item a $ref names is inlined where it stands Nothing
downlevel.path-item-ref info A $ref to a shared path item was inlined at its use site; the message names every hop it followed Nothing
downlevel.path-item-unresolved warning A $ref chain reached no shared path item — the last hop names one the document doesn’t define, or the chain returns to a name it already followed — so the path was dropped rather than left pointing at a member the 3.0 artifact no longer carries. A 3.0 consumer loses the endpoint Define the shared path item the message names, or break the cycle it traces; either way one of them has to describe the path rather than point at another
downlevel.info-summary warning info.summary was dropped; OpenAPI 3.0 doesn’t define it Lead info.description with the same sentence
downlevel.license-identifier info or warning The SPDX info.license.identifier became info.license.url, or was dropped where a url was already set Nothing
downlevel.mutual-tls warning A mutualTLS security scheme was dropped, along with every requirement naming it Document mutual TLS in prose for 3.0 consumers, or keep the 3.1 artifact
downlevel.empty-responses info An operation documents no responses, and OpenAPI 3.0 requires every operation to declare at least one, so the 3.0 artifact carries a placeholder default response that describes no status or body Name what the endpoint returns — #[Response], a return docblock, or an overlay — so every artifact carries the real shape
downlevel.multi-type info or warning A multi-type type became an anyOf of single-type branches, or was dropped where the schema already composes Nothing
downlevel.null-type warning A null-only type became an untyped nullable: true; OpenAPI 3.0 has no null type Nothing, though 3.0 consumers see a looser schema
downlevel.nullable-composition info A {type: null} branch moved onto its parent as nullable: true Nothing
downlevel.ref-siblings info OpenAPI 3.0 ignores a $ref sibling, so members beside one were hoisted into an allOf wrapper — or, for a reference’s own summary/description, dropped in favour of the referenced component’s wording Nothing
downlevel.const info A const became a single-value enum, which is how OpenAPI 3.0 pins a value Nothing
downlevel.content-encoding info or warning contentEncoding: base64 became format: byte, or was dropped where a format was already set Nothing
downlevel.exclusive-bound warning A numeric exclusiveMinimum/exclusiveMaximum was dropped, because 3.0 spells it as a boolean on a bound that’s already taken Nothing, though 3.0 consumers see an unbounded number
downlevel.schema-examples info or warning The first of a schema’s examples was kept as example, or they were dropped where none could be Nothing
downlevel.boolean-subschema info A boolean schema — true for “anything goes”, false for “nothing is valid” — became the OpenAPI 3.0 spelling of the same constraint, {} or {"not": {}}. 3.0 takes a boolean at additionalProperties and nowhere else Nothing; the constraint is unchanged
downlevel.unsupported-keyword warning A JSON Schema keyword OpenAPI 3.0 doesn’t define was dropped from a schema Keep the 3.1 or 3.2 artifact for consumers that validate against the full constraint

Facts about the servers a document publishes. Every emitter that writes a URL resolves them for itself, so a code here is raised once per emitted format — a build exporting OpenAPI 3.2, 3.1, 3.0 and a Postman collection reports the same variable four times, once against each. --format=uir publishes the variables as configured and reports nothing, since nothing has resolved a URL yet.

Code Severity What it means What to do
server.variable-no-default warning A server variable declares no default, which every OpenAPI version requires of one. Where the variable declares an enum, its first value stands in — that set is your API’s own, so a member of it resolves the URL to something you serve. Where it declares neither, an OpenAPI export leaves the variable out rather than resolve the URL to a value nobody serves, and a Postman collection publishes it blank Give the variable a default — one of its enum values, where it declares an enum. See servers

A Postman collection describes requests a person sends, so it carries less than an OpenAPI document does. See Postman collections.

Code Severity What it means What to do
postman.no-server warning The document declares no servers, so the collection’s baseUrl is empty Declare a server, or fill the variable in once after importing
postman.variable-name-collision warning A server variable is named baseUrl, which the collection already uses, so it isn’t published as a variable of its own Rename the server variable
postman.path-template-partial warning A path segment templates only part of itself, and a Postman path variable stands for a whole segment, so the segment was left literal Edit the URL after importing, or template the whole segment
postman.auth-unsupported warning A security scheme has no Postman equivalent, so requests are sent unauthenticated Add the credential by hand in Postman
postman.auth-multi-scheme warning An operation requires more than one credential together, and a Postman request carries one Supply the others by hand — the message names which one the collection sends
postman.body-not-object warning A form body isn’t an object, and a form body is a list of fields, so it’s sent empty Fill the body in after importing
postman.body-unresolved warning A request body’s schema is a $ref the document doesn’t define, so there’s no shape to build a body from and the request is sent empty Define the component the pointer names, or correct the pointer — an overlay that renamed or removed a schema is the usual cause
postman.body-media-type warning No example body could be built for a media type, so requests using it are sent empty Add an example for that media type — see Example payloads
postman.examples-truncated warning An operation documents more saved responses than the collection keeps, so the first few by status were saved to stay navigable Nothing. The full set is still in the OpenAPI document
postman.callbacks-dropped warning The callbacks an operation declares have no Postman equivalent Nothing
postman.webhooks-dropped warning A collection describes requests you send, so it can’t carry the webhooks your API delivers Nothing. Export an OpenAPI format alongside for the webhook contract
postman.yaml-ignored warning A Postman collection has no YAML form, so JSON was written Give the target a .json path