Skip to main content

Import a tRPC router

tRPC has no IDL: the API contract exists only as the router in your TypeScript code. Apiome reads that router and catalogs every procedure as an operation, with its input and output schemas, so a tRPC API can be searched, diffed and converted like any other.

What you can import​

You haveImport it as
One module that does initTRPC and exports a router (export const appRouter = t.router({ … }))One file, or paste the source
An app router that merges feature routers from other modules, with schemas in a shared moduleOne archive with all the modules; the root is the module that exports the app router

Apiome reads routers built with @trpc/server 10 or 11, whose inputs and outputs are Zod 3 schemas.

Import it​

Import the file like any other specification: in the import wizard, with apiome import, or through the REST API (see Import a specification). Apiome recognises a module that imports @trpc/server and builds a router, and picks tRPC router (trpc). A module of plain Zod schemas goes to Zod schemas instead.

The router lands in the Catalog as an rpc item, named after the exported router.

Like a Zod module, a router only exists once its code has run, so Apiome runs the module in the same sandbox as Zod import: no file access, no network, an empty environment and a time limit. Only @trpc/server, zod and the modules in your upload can be imported. Apiome never calls a procedure, so resolver code, the errors it throws and the context your middleware builds are not read.

What each part becomes​

tRPCBecomes
.query()A query operation
.mutation()A mutation operation
.subscription()A subscription operation (server streaming)
The procedure's path (invoice.list)The operation's name and path; its key is the router path, for example appRouter.invoice.list
A nested t.router({ … })A group of operations under its parent, three or more levels deep if you nest that far
.input(schema)The request payload. Several chained .input() calls are merged into one, as tRPC merges them.
.output(schema)The response payload
.meta({ … })Kept on the operation

Inputs and outputs are read the same way as Zod schemas, with all their constraints. A schema that your modules export by name — Order, Money — becomes a type with that name, shared by every procedure that uses it. A schema written inline becomes a type named after the procedure, for example InvoiceListInput and InvoiceListOutput.

What is declared, not imported​

Apiome lists these in the import's coverage ledger instead of guessing:

  • A hand-written validator. .input((raw) => …) is a function, not a schema, so there is nothing to read. The operation is imported without a request payload, rather than with an empty one.
  • A lazily loaded router (lazy(() => import('./billing'))). Its procedures are not known until it is first called.
  • Everything Zod import declares — refine, transform, brand and the rest — inside a procedure's schemas.

When an import is refused​

Error codeWhy
INPUT_SEMANTIC_INVALIDThe module exports no router, the router has no procedures, no procedure has a Zod input or output schema, or the module threw while it was being run.
INPUT_MALFORMEDThe module has a TypeScript syntax error, such as an unclosed t.router({.
INPUT_TRUNCATEDThe module ends in the middle of a procedure.
INPUT_TIME_LIMITRunning the module took longer than the time limit.
INPUT_REFERENCE_UNRESOLVEDThe module imports something other than @trpc/server, zod and the modules in the upload.
FORMAT_MISMATCHThe module does not import @trpc/server.
INPUT_ENCODING_INVALIDThe file is not UTF-8 (for example, it was saved as UTF-16).

What is inferred​

On the catalog item, the type that holds a procedure's inline input or output (InvoiceListInput) is synthesized. Those entities carry a badge with the evidence, and the Provenance filter on the Overview tab narrows to them — see Declared, inferred and synthesized.