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 with the selection chain below, 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 names the method as the crate routes do, a method tree read exactly as /v1/validate and the run routes read it, plus a pipe selector and two opt-ins:
files(object) ormethod_ref(string), exactly one: the method, as on/v1/resolve.filesis the MTHDS protocol's map of file entries, each key a tree path and each value the file's text,{"base64": "…"}or{"sha256": "…", "size": n};method_refan address-form reference to a published package, whose whole tree is fetched. Neither or both is a request-shape422.dependencies(object, optional): the method packages the method calls by address, each a map of file entries keyed by its address, as on a run. It accompaniesfilesonly.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 text of the method's own.mthdsbundles on the valid arm.resources(object, optional): files the method's steps read, such as aPipeDocGenstep'stemplate_file, keyed by the path from the tree's root, each in standard base64, joining the tree as on/v1/validate. It accompaniesfilesonly. A step whose template file the tree does not hold makes the method invalid.read_scope(string, optional): Pipelex-API extension. The scope the request reads the method under, as on/v1/validate: a hash entry offilesresolves in that scope's blob store, and only a request carrying it records the tree it read there; ajwtcaller may send only a scope under its own id, else a403 ReadScopeForbidden.
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 the method's files before forwarding, so this server never declares it.
{
"files": {
"METHODS.toml": "...manifest...",
"main.mthds": "...bundle...",
"templates/invoice.docx": { "base64": "UEsDBBQABgAIAAAAIQ..." }
},
"all_pipes": true,
"include_files": true
}
The tree's refusals produce no verdict, as on /v1/validate: an InvalidMethodFiles, MissingBlobs, MethodTreeTooLarge or InvalidBundle 422, and the custom-Python policy's 403s (see No verdict).
Pipe selection
The selection chain takes the request's pipe_ref; else the main_pipe of the METHODS.toml at the tree's root, whether the tree was fetched by method_ref or sent in files; else the closure's own main_pipe declaration, when exactly one domain declares one. A root METHODS.toml the route cannot read as a manifest, being no UTF-8 text or no valid manifest, is a 422 MethodEntryError when the request names no pipe. A request naming its pipe in pipe_ref never reads the manifest to select it, so there a manifest the route cannot read refuses nothing and only leaves default_pipe_ref a stated null. 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": { "main.mthds": "...bundle..." }
}
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, as it is when the request named a pipe and the rootMETHODS.tomlcannot be read.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(object, only withinclude_files: true): the text of each.mthdsbundle of the method's own tree, keyed by its tree path. For a tree sent infiles, it is the request's bundles, whatever entry form carried them; for amethod_ref, the fetched package's bundles, each keyed by its path from the package root. The tree's other files and the bundles of its dependencies 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. Both routes read the same tree, but they do not accept exactly the same methods: this route loads a package the method calls by its address only from what was shipped with it (see Resolve & Codegen), where /v1/validate also looks among the methods installed on the runner and fetches one on a miss; /v1/validate refuses a method whose dry run fails, which this route accepts, and checks the tree's Python by syntax and a method_ref package's requirements.txt, which this route does not; this route refuses a root METHODS.toml it cannot read when the request names no pipe, since its selection chain then reads the manifest, where /v1/validate still answers a verdict.
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. A call into a package nothing shipped is one, its unresolved_package_dependency item naming the package as address-based dependency '<address>'. 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 or neither or both method selectors,ValidationError; for a tree the runner cannot read,InvalidMethodFiles,MissingBlobs,MethodTreeTooLargeorInvalidBundle, as on/v1/resolve; for an unreadable root manifest on a request naming no pipe,MethodEntryError; 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. 403for the custom-Python policy of/v1/validateand the run routes, whose tree the route loads:CustomCodeRequiresSandboxfor a tree holding a.pyfile on a deployment that is not sandbox-hosted, where loading the library would import each.pydeclaring a structure class and run its module-level code in the server's process, andMethodStructuresRefusedErrorfor one whose Python declares a structure class.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.