Call the API from a Script

This guide calls the tRPC Platform API from a standalone Node or Bun script. It covers getting a token, a raw fetch version (to see the superjson wire format), the typed @trpc/client version, paginating a list, and handling the error envelope.

Get a token

Programmatic callers authenticate with a session JWT in the Authorization header:

Authorization: Bearer <session JWT>

The token is the HS256 session JWT (signed with AUTH_SECRET) — the same value the __Secure-authjs.session-token cookie carries. Keep it out of source; read it from the environment:

export INKLURA_TOKEN="<session JWT>"

Verify it resolves before doing real work by calling auth.getSession (shown below).

Raw fetch

Useful for seeing the wire format. Queries are GET with the input URL-encoded into ?input={"json":<value>}; mutations are POST with the body {"json":<value>}. The result is nested at result.data.json.

const BASE = "https://manage.inklura.fr/api/trpc";
const auth = { Authorization: `Bearer ${process.env.INKLURA_TOKEN}` };

// Query (GET): who am I?
const input = encodeURIComponent(JSON.stringify({ json: {} }));
const meRes = await fetch(`${BASE}/auth.getSession?input=${input}`, {
  headers: auth,
});
const me = (await meRes.json()).result.data.json;
console.log("session:", me);

// Mutation (POST): create a client
const createRes = await fetch(`${BASE}/clients.create`, {
  method: "POST",
  headers: { ...auth, "Content-Type": "application/json" },
  body: JSON.stringify({ json: { name: "Acme SARL", email: "contact@acme.example" } }),
});
const created = (await createRes.json()).result.data.json;
console.log("created:", created.id);

For a procedure that takes no input you can omit ?input= entirely.

Typed @trpc/client

For a TypeScript caller, @trpc/client handles the superjson wrapping and batching for you. Configure the superjson transformer on an httpBatchLink and set the Authorization header there:

import { createTRPCClient, httpBatchLink } from "@trpc/client";
import superjson from "superjson";

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: "https://manage.inklura.fr/api/trpc",
      transformer: superjson,
      headers() {
        return {
          Authorization: `Bearer ${process.env.INKLURA_TOKEN}`,
          // Optionally pin tenant / site scope:
          "X-Tenant-Id": process.env.INKLURA_TENANT_ID ?? "",
          "X-Site-Id": process.env.INKLURA_SITE_ID ?? "",
        };
      },
    }),
  ],
});

const session = await client.auth.getSession.query();
const created = await client.clients.create.mutate({
  name: "Acme SARL",
  email: "contact@acme.example",
});

With the transformer configured, you pass and receive plain values — no manual {"json":…} wrapping. See tRPC Procedures for the calling conventions and batching details.

Paginate a list

List procedures follow the { page, pageSize } convention and return { items, total, page, pageSize }. Walk every page:

async function listAllProducts() {
  const all = [];
  let page = 1;
  const pageSize = 50;
  for (;;) {
    const res = await client.products.list.query({ page, pageSize });
    all.push(...res.items);
    if (all.length >= res.total || res.items.length === 0) break;
    page += 1;
  }
  return all;
}

Pagination is a convention rather than a framework guarantee — confirm the shape for the procedure you call. See Pagination & Filtering.

Handle errors

Failures come back as the standard tRPC error envelope; the useful fields are under error.data (code, httpStatus), and Zod validation failures add data.zodError. The typed client throws a TRPCClientError whose data carries them:

import { TRPCClientError } from "@trpc/client";

try {
  await client.clients.create.mutate({ name: "", email: "not-an-email" });
} catch (err) {
  if (err instanceof TRPCClientError) {
    const data = err.data; // { code, httpStatus, ... }
    console.error(data?.code, data?.httpStatus);
    if (data?.zodError) {
      console.error("field errors:", data.zodError.fieldErrors);
    }
  } else {
    throw err;
  }
}

With raw fetch, read the same fields off the parsed body's error.data instead. See Errors & Rate Limits for the code-to-status mapping and the zodError shape.

Related