Pipe I/O
Return a method's I/O artifacts — its pipe I/O contracts, its input form and its output form — without validating it.
Endpoint: POST /v1/pipe-io
The route resolves the closure through the same static core as POST /v1/resolve, selects a pipe the way the per-pipe build routes do, and derives the three artifacts with the builder that POST /v1/validate and every run already call. It runs no dry-run sweep, so a call costs one load and one derivation, where /v1/validate mock-runs every pipe of the method. A caller that shows a method, prepares its inputs or generates types for it reads this route; a caller that needs the dry-run verdict or the dry-run graph stays on /v1/validate, whose views still attach both forms beside the verdict.
It is a Pipelex API extension, not an MTHDS Protocol route: it is not tagged x-mthds-protocol in the OpenAPI artifact. The artifacts it carries are the standard's, under their neutral names — see the MTHDS specification's pages on pipe I/O contracts, the input-form descriptor and the output-form descriptor.
It speaks the /v1/validate verdict discipline: a produced verdict is always a 200 discriminated on is_valid, and a non-2xx is reserved for a request that produced no verdict.
Request
The body is the crate routes' closure selector plus a pipe selector and two opt-ins:
files(list) ormethod_ref(string), exactly one: the closure, as on/v1/resolve. Neither or both is a request-shape422.pipe_ref(string, optional): the qualified refdomain.pipe_codeof the pipe to describe. Omitted, the selection chain below decides.all_pipes(boolean, defaultfalse): describe every pipe the closure loads instead of the selected one.include_files(boolean, defaultfalse): echo the resolved closure's.mthdsfiles on the valid arm.
There is no views field, because the valid arm always carries all three artifacts. The hosted API also accepts a catalog method_id, which the platform resolves into files[] before forwarding, so this server never declares it.
{
"files": [{ "content": "...bundle...", "source": "main.mthds" }],
"all_pipes": true,
"include_files": true
}
Pipe selection
The selection chain is the one the build routes share: the request's pipe_ref; else a fetched package's manifest main_pipe; else the closure's own main_pipe declaration, when exactly one domain declares one. The chain stops at the first link that is present, so a manifest main_pipe the closure does not declare, or declares in several domains, is a failed selection rather than a fall-through to the closure's declarations.
The closure is resolved first, so an invalid closure answers its invalid verdict whatever pipe_ref names. For a valid closure these selections are refused with an input 422 problem+json. Its error_type is the pipelex entry-lookup class that names the failure, never the ValidationError of a malformed request, so a client tells a selection refusal from a request-shape one by that field:
EntryPipeNotFoundErrorfor apipe_refthat names no pipe of the closure, for a manifestmain_pipethe closure does not declare, and, withoutall_pipes, for a request with nopipe_refwhose closure declares nomain_pipe;EntryPipeAmbiguousErrorfor a bare code that matches pipes in several domains, whether the request or the manifest spelled it, and, withoutall_pipes, for a request with nopipe_refwhose closure declares severalmain_pipes.
The detail says which case it is and, for an ambiguity, names the qualified refs to choose from; the candidates are in the detail alone, with no structured list, as on the run routes. The problem's type and title are the class's. Its user_action is the class's too, advising a check of the pipe code, except for a request with no pipe_ref whose closure declares no main_pipe or several, where it asks for a pipe_ref instead.
An unknown or ambiguous pipe_ref answers the error_type the run routes answer for the same pipe_code. A closure declaring no main_pipe, or several, has no run-route twin: this route refuses both as the pipe-selector design classifies them, while a run over such a closure today fails with a 500 or runs the first declaration.
A bare pipe_ref that matches one pipe (echo where smoke.echo is meant) is still resolved today, and the valid arm reports the qualified ref.
With all_pipes: true the route never refuses for want of an entry pipe, so a method that declares no entry pipe, or several, is still describable. The valid arm's pipe_ref is then the requested ref (an unknown one is still a 422), else the chain's answer, else null.
Response (valid verdict)
{
"is_valid": true,
"pipe_ref": "smoke.echo",
"pipe_io_contracts": { "smoke.echo": { "inputs": { "…": {} }, "output": { "…": {} } } },
"input_form": { "smoke.echo": { "fields": [ { "kind": "prose", "name": "text", "…": "…" } ] } },
"output_form": { "smoke.echo": { "field": { "kind": "prose", "name": "output", "…": "…" } } },
"default_pipe_ref": "smoke.echo",
"pending_signatures": [],
"is_runnable": true,
"files": [{ "content": "...bundle...", "source": "main.mthds" }]
}
is_valid(true): the closure parsed, loaded and passed static validation. It does not mean a dry run passed; this route runs none.pipe_ref(string | null): the qualified ref the selection resolved, read off the resolved pipe and never echoed from the request.nullonly underall_pipeswhen nothing resolves.pipe_io_contracts,input_form,output_form(objects keyed by qualifiedpipe_ref): the three artifacts, sharing one key set — the resolvedpipe_refalone by default, every pipe the closure loads underall_pipes.default_pipe_ref(string | null): the method's own entry pipe, the selection chain without the request'spipe_ref. A request that omitspipe_reftherefore always answerspipe_ref == default_pipe_ref. When the request named a pipe, or asked forall_pipes, and the chain finds no entry pipe or several, the field is a statednull.pending_signatures(list of strings) andis_runnable(boolean): exactly what they mean on/v1/validate— the qualified refs of the pipes still declared as signatures, andnot pending_signatures. Both come from the loaded library with no dry run, so a method whose dry run would fail is still reported runnable here.files(list, only withinclude_files: true): the closure in the request's ownfiles[]shape. For inlinefilesit is the request's files echoed back; for amethod_refit is the fetched package's.mthdsfiles, eachsourcebeing the file's path relative to the package root. A package's other files are never included. Withoutinclude_filesthe field is absent, not empty.
The artifacts are pinned to /v1/validate. For a closure both routes accept, each artifact map equals the same-named field of /v1/validate's valid arm (requested with views: ["input_form", "output_form"]) restricted to the same keys: both routes derive them with the one builder and dump them the same way, keeping null members such as a contract's item_count: null. The two routes do not accept exactly the same closures: /v1/validate refuses a closure whose dry run fails, which this route accepts; this route refuses an address-based cross-package dependency, which /v1/validate loads; and /v1/validate by method_ref also loads a package's non-.mthds files, which this route never reads.
default_pipe_ref is not /v1/validate's field of the same name. That one is the run default: where several domains declare a main_pipe, it names the first declaring blueprint's, because a run would take it, while this route refuses to choose and states null.
Response (invalid verdict)
An invalid closure is the 200 crate verdict /v1/resolve gives: is_valid: false, the structured validation_errors[] and a message. It carries no artifacts, no pipe_ref or default_pipe_ref, no runnability facts and no files, whatever the request asked for.
No verdict
Every non-2xx is RFC 7807 application/problem+json (see Error Responses):
422for a malformed body, neither or both closure selectors and an over-limit file, allValidationError, and for the selection refusals above,EntryPipeNotFoundErrororEntryPipeAmbiguousError.- The
method_reffetch outcomes, exactly as on/v1/resolve:404MethodPackageNotFoundErrorwhen no package in the fetched repository matches the address,422for a reference that does not parse or a fetch the server cannot accept, and501MethodRefNotSupportedfor a registry-form reference. The route reads only a package's.mthdsfiles, so the custom-code and structures403s of/v1/validateand the run routes never arise here. 401/403for authentication, and413for a body over the size limit.500when an artifact cannot be derived: a pipe whose input or output JSON Schema cannot be rendered raisesPipeIOContractError, which answers500as it does on/v1/validate— a fault of the tool, not a verdict about the method.