Qeet Docs
SDKs

TypeScript & Next.js

@qeetid/sdk for server-side token verification and management, and @qeetid/nextjs for the hosted-login flow with silent refresh.

There are two TypeScript packages, for two jobs:

  • @qeetid/sdk — a server-side client: verify access tokens, run authorization checks, and manage users/tenants. Zero runtime dependencies (built-in fetch + node:crypto, Node ≥18; also Bun, Deno, edge).
  • @qeetid/nextjs — drives the hosted-login OAuth flow for App Router apps: route handlers, Edge middleware, and silent refresh.

@qeetid/sdk (server)

terminal
Bash
pnpm add @qeetid/sdk
lib/qeetid.ts
import { Qeetid } from "@qeetid/sdk";

export const qeetid = new Qeetid({
  apiKey: process.env.QEETID_API_KEY!,   // qk_… — server only
  // baseUrl: "https://api.qeetid.com",  // default
  // timeoutMs: 10_000, maxRetries: 2,
});

Verify a session

Local — checks the ES256 signature against the cached JWKS, then expiry/issuer/audience.

TypeScript
const claims = await qeetid.sessions.verify(accessToken);
// claims.userId, claims.tenantId, claims.sessionId

Authorize

TypeScript
if (await qeetid.can({ user: claims.userId, tenant: claims.tenantId!, permission: "billing:write" })) {
  // …
}

Manage users & tenants

TypeScript
const user = await qeetid.users.create({ email: "new@acme.com", display_name: "New User" });
for await (const u of qeetid.users.listAll({ tenant: "acme" })) console.log(u.email);

Errors

TypeScript
import { RateLimitError, InvalidCredentialsError } from "@qeetid/sdk";

try {
  await qeetid.users.get("usr_missing");
} catch (err) {
  if (err instanceof RateLimitError) await wait(err.retryAfterSeconds);
  else if (err instanceof InvalidCredentialsError) rotateApiKey();
  else throw err;
}

429 and idempotent 5xx are retried automatically with backoff, honoring Retry-After.

@qeetid/nextjs (hosted login)

terminal
Bash
pnpm add @qeetid/nextjs

Environment

.env.local
Bash
QEETID_CLIENT_ID=qci_…
QEETID_CLIENT_SECRET=
QEETID_API_URL=https://api.qeetid.com
QEETID_APP_URL=https://app.acme.com
QEETID_COOKIE_SECRET=        # ≥32 random chars
# QEETID_SCOPES="openid profile email"

Register ${QEETID_APP_URL}/api/auth/callback as a redirect URI and ${QEETID_APP_URL} as a post-logout URI on your OIDC client.

Mount the auth routes

app/api/auth/[...qeetid]/route.ts
import { handleAuth } from "@qeetid/nextjs";
export const GET = handleAuth(); // /api/auth/login | /callback | /logout

Protect routes (Edge middleware)

middleware.ts
TypeScript
import { qeetidMiddleware } from "@qeetid/nextjs/middleware";

export default qeetidMiddleware({ publicRoutes: ["/", "/pricing"] });

export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };

Unauthenticated requests are redirected to hosted login; near-expiry sessions are silently refreshed (the rotated refresh token is persisted) so users aren't bounced.

Read the user

app/page.tsx
TypeScript
import { auth, currentUser } from "@qeetid/nextjs";

export default async function Page() {
  const { isAuthenticated, userId, tenantId } = await auth();
  if (!isAuthenticated) return null;
  const user = await currentUser(); // OIDC userinfo, or null
  return <p>Hello {user?.sub}</p>;
}
ExportUse
handleAuth()Route handler for /api/auth/[...qeetid].
qeetidMiddleware(opts)Route protection + silent refresh (Edge).
auth(){ isAuthenticated, userId, tenantId, sessionId, accessToken }.
currentUser()OIDC userinfo, or null.
getToken()Current access token to call your own APIs.

How it splits: the Edge middleware uses Web Crypto only (no Node code in the Edge bundle); auth() runs in the Node runtime and verifies the ES256 token via @qeetid/sdk. Pair with @qeetid/react for UI.

On this page