VarisDocs
Dashboard

TypeScript

Declare Varis services and verify Varis requests in TypeScript and JavaScript with @usevaris/sdk.

@usevaris/sdk does two things in your code:

  • Declares your services with varis.services.define, so the CLI can publish them.
  • Verifies requests from Varis with varis.verifyRequest, so your endpoint answers requests from Varis.

It's small, it has no runtime dependencies, and it never sends your data anywhere.

Requirements

The SDK runs on any of these:

  • Node.js 22.13 or later
  • Deno
  • Bun
  • Cloudflare Workers

Install

npm install @usevaris/sdk

You don't install the generator, @usevaris/build. varis build downloads and runs it for you.

Create a client

import { Varis } from "@usevaris/sdk";

const varis = new Varis();

Create one Varis instance per process, such as in a shared lib/varis.ts file, and import it wherever you need it. Each instance caches the Varis signing keys, so a new instance per request fetches them every time.

new Varis() never needs a credential. Your sign-in belongs to the CLI, not your server.

Options

You rarely need these. They exist for testing.

OptionTypeDefaultDescription
keysUrlstringThe Varis APIWhere to fetch signing keys from.
publicKeysVarisPublicKey[]NoneUses these keys instead of fetching any. Use it in unit tests.
maxClockSkewSecondsnumber300How far a request's timestamp can be from your server's clock.

new Varis().services.define

Declares a service. It does nothing when your code runs. The CLI finds it in your source code when you run varis build or varis publish.

import { Varis } from "@usevaris/sdk";

const varis = new Varis();

varis.services.define<Input, Output>({
  slug: "convert-currency",
  name: "Convert currency",
  description: "Converts an amount from one currency to another at the current exchange rate.",
  service_type: "data",
  categories: ["finance", "forex"],
  path: "/currency/convert",
  price_cents: 10,
});

Pass two type arguments:

  • Input: the input your service takes. Use void if it takes none.
  • Output: the output your service returns.

The CLI turns both into JSON Schemas. Comments on fields become descriptions that agents read.

Fields

FieldRequiredDefaultDescription
slugYesA permanent ID, unique within your owner.
nameYesA readable name.
descriptionYesWhat the service does, in 20 to 1,024 characters.
instructionsNoHow agents should use the service, up to 2,000 characters.
service_typeYesThe kind of service, such as data or tool.
categoriesYesOne or more categories.
endpoint_urlOne of theseThe full https address Varis calls.
pathOne of theseA path joined to base_url in varis.json.
methodNo"GET""GET" or "POST".
price_centsNo0The price per call in whole US cents. You receive all of it.
versionNo"1.0.0"The service's version.
statusNo"published""published" or "disabled".

Your editor completes service_type and categories for you, because the SDK types every allowed value.

Every value must be written out in full, not computed. For the full rules, see Declare services in code.

new Varis().verifyRequest

Checks that a request comes from Varis.

You don't need a separate route for Varis. If a route already serves your own users, and you're happy to give agents the same output, add verifyRequest beside your existing authorisation check. Let the request through if either check passes:

import { Varis } from "@usevaris/sdk";
import { isAuthorisedUser } from "@utils/auth";

const varis = new Varis();

export async function GET(request: Request) {
  const isAuthorised = (await varis.verifyRequest(request)) || (await isAuthorisedUser(request));
 
  if (!isAuthorised) {
    return new Response("Unauthorized", { status: 401 });
  }
 
  // The rest of your route, unchanged.
}

Replace isAuthorisedUser with however your route checks its users today.

A request from Varis comes from an agent, not one of your users, so it carries no user session. If the rest of your route reads the current user, give Varis requests a path that doesn't need one. For more information, see Handle requests.

If a route serves only Varis, the check is shorter:

if (!(await varis.verifyRequest(request))) {
  return new Response("Unauthorized", { status: 401 });
}

Arguments

verifyRequest takes the incoming request, in either form:

  • A Web Request, as in Next.js route handlers, Hono, Deno, Bun, and Cloudflare Workers.
  • A Node.js req, as in Express, Fastify, and Next.js Pages Router API routes.

Call verifyRequest before anything reads the request body. It clones the request and verifies against the cloned copy. So, the actual request remains intact for you to read afterwards.

If you read the request body before calling verifyRequest, the request arrives tampered and the signature verification fails with TypeError. If you really need to read the request body earlier, read a clone.

Return value

  • true if the request comes from Varis.
  • false if it doesn't, such as a missing or wrong signature, or a timestamp more than 5 minutes out.

Errors

  • VarisKeyFetchError: it couldn't load the Varis signing keys. That's a temporary problem on your side, not a bad request, so respond with 503, not 401.
  • TypeError: your code read the request body before calling verifyRequest. Call it first.

Test requests

verifyRequest also verifies requests from varis test on your machine, with no changes to your code. For more information, see Local development.

Exports

ExportKindDescription
VarisClassThe client.
VarisKeyFetchErrorClassThrown when the signing keys can't load.
VarisOptionsTypeThe options for new Varis().
VarisPublicKeyTypeA signing key: { kid, public_key_pem }.
ServiceDefinitionTypeThe fields define accepts.
ServiceTypeTypeEvery allowed service_type.
ServiceCategoryTypeEvery allowed category.
ServiceMethodType"GET" | "POST".
ServiceStatusType"draft" | "published" | "disabled".

Instructions for coding agents

The SDK ships with instructions for coding agents in node_modules/@usevaris/sdk/docs/agents.md. varis init points your project's AGENTS.md at them, so Claude Code, Cursor, and other agents can declare and verify services correctly without reading these docs.

What's next