How does routing work in tRPC especially when using merged routers?

I am having trouble understanding how tRPC lays out routes. Let's say I have the below

export const apiRouter = router({
  version: publicProcedure.query(() => {
    return { version: '0.42.0' }
  hello: publicProcedure
    .input(z.object({ username: z.string().nullish() }).nullish())
    .query(({ input, ctx }) => {
      return {
        text: `hello ${input?.username ?? ctx.user?.name ?? 'world'}`,

export const appRouter = router({
  api: apiRouter,
  // ...other routers

// server.ts
server.register(fastifyTRPCPlugin, {
  prefix: '/trpc',
  trpcOptions: { router: appRouter, createContext },

My assumption is that from the above example I have:

GET /trpc/api/version
GET /trpc/api/hello?username=Bob

However when using Insomnia to query the endpoints as I familiarize myself with tRPC more I get 404s. Could someone explain this to me better?

I am running:

@trpc/server 10.21.0
pnpm 8.2.0
node 18.15.0
In Postman I had to use:
GET /trpc/user.getUsers

So in your case it would be:
GET /trpc/api.version

Also new to TRPC and unfamiliar with Insomnia, but I hope that helps.
@Longhumans you sir are a fucking boss! Thank you I would never have figured that out. Makes some sense now with the way the client consumes:


Thanks again!
Happy it helped 🙂