npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kywi-software/sdk

v0.24.0

Published

Standalone HTTP client for the Kywi CMS REST API — headless/decoupled frontends, no dependency on @kywi-software/core.

Readme

@kywi-software/sdk

A standalone HTTP client for the Kywi CMS REST API (/api/v1/*). Pure fetch under the hood, no runtime dependency on @kywi-software/core — usable from a headless frontend, a separate Node service, or any React app under ./hooks.

Every export below is read from packages/sdk/src/*.ts and its tests (packages/sdk/src/__tests__/*.test.ts); if this file and the source ever disagree, the source wins.

Install

npm install @kywi-software/sdk

Inside this monorepo the package is linked via the workspace protocol. pnpm --filter @kywi-software/sdk build compiles src/ to dist/ via tsc; exports in package.json points . at dist/index.js and ./hooks at dist/hooks/index.js. react/react-dom are optional peer dependencies, only needed for ./hooks.

Quick start

import { createKywiSdk } from '@kywi-software/sdk'

const kywi = createKywiSdk({
  baseUrl: 'https://example.com/api/v1',
  // one of:
  apiKey: 'kywi_xxxxxxxx',   // a Kywi API key (from /admin/web-services/api-keys)
  // token: '<jwt>',         // or a JWT access token (e.g. from kywi.auth.login())
  defaultSiteId: 'default',  // used when a call omits siteId
})

const page = await kywi.content.getBySlug('about')
const nav = await kywi.nav.getTree()

Authentication — how the API key / token actually rides on the request

KywiHttpClient (src/http-client.ts) puts both apiKey and token on the Authorization: Bearer <value> header — there is no x-api-key header. This matches the server: resolveAuth reads Authorization: Bearer <token> and treats a value with the kywi_ prefix as an API key, anything else as a JWT. If both apiKey and token are set, token wins (it's applied last in the constructor).

// API key
createKywiSdk({ baseUrl, apiKey: 'kywi_xxxxxxxx' })

// JWT (e.g. after auth.login())
const { accessToken } = await kywi.auth.login('[email protected]', 'password')
createKywiSdk({ baseUrl, token: accessToken })

auth.refresh() and manual token rotation update the client via http.setToken(newToken) (not exposed on the scope object directly — call it on a KywiHttpClient instance you construct yourself if you need to swap tokens on a long-lived client without rebuilding the whole SDK).

Every success response unwraps { data }

The Kywi API wraps every success body in { data: … } (list endpoints add { data, meta }) and every error in { error: { code, message, details? } } (jsonOk/jsonError server-side). Every SDK client — including auth, added after the original scopes — unwraps res.data so callers get plain values (ContentItem, AuthResult, etc.), never the envelope. A 404 is returned as the parsed { error } body rather than thrown, which most clients turn into null (e.g. content.getById, content.getBySlug); a non-OK, non-404 response throws KywiSdkError with .status and .body.

siteId goes on the query string, not the request body

content.create() resolves siteId (explicit arg, else config.defaultSiteId) and appends it as a query param — POST /content/page?siteId=default — not as a body field. The server's create schema is .strict(), so a siteId key inside the JSON body is rejected with 400. Every read/list method (getBySlug, list, …) also sends siteId as a query param, for consistency.

// Correct — siteId is threaded through as a query param internally
await kywi.content.create('page', { title: 'New Page' }, 'site-b-corp')

// Do NOT do this — { siteId } inside `data` would be a plain field write,
// and the server itself would 400 on an unknown strict-schema key.

Scopes

createKywiSdk() returns a KywiSdkScope (src/types.ts) with these sub-scopes, all HTTP-backed except i18n (client-side only, from pre-loaded bundles):

| Scope | Methods | Notes | |---|---|---| | auth | login, register, refresh, forgotPassword, resetPassword, logout | src/auth-client.ts | | content | getBySlug, getById, getChildren, list, create, update, delete, publish, unpublish | type-agnostic ops (getById/delete/publish/unpublish/getChildren) hit /content/by-id/:id*; typed ops (list/create/update) hit /content/:type* | | nav | getTree, getBreadcrumbs, getChildren | | | site | getConfig, list, updateSettings | | | user | getCurrent, getById, list, create | | | feeds | query, get, list, create, update, delete | | | media | upload, list, getById, delete | upload takes a File \| Buffer | | layout | get, update, updateRegion | page layout documents | | search | search | | | versions | list, get, restore | content version history | | workflow | publish, unpublish, restore, permanentDelete | trash/publish workflow, ID-based | | categories | list, getById, create, update, delete, tree | | | forms | submit, listSubmissions, getCsrfToken | | | tree | getChildren, getBreadcrumbs, move, reorder | site-tree drag/drop operations | | hooks | listWebhooks, createWebhook, updateWebhook, deleteWebhook | webhook endpoints | | i18n (optional) | get, getLocale | only present when config.bundles and config.locale are both set |

The full method signatures are the source of truth in src/types.ts (KywiSdk*Scope interfaces) — read them before wiring a new call, since several return null on 404 rather than throwing (content.getById, content.getBySlug, content.update, site.getConfig, user.getCurrent, user.getById) while others throw KywiSdkError.

React hooks (@kywi-software/sdk/hooks)

import { KywiSdkProvider, useKywiSdkContent, useKywiSdkNav } from '@kywi-software/sdk/hooks'
import { createKywiSdk } from '@kywi-software/sdk'

const sdk = createKywiSdk({ baseUrl: '/api/v1', defaultSiteId: 'default' })

function App() {
  return (
    <KywiSdkProvider sdk={sdk}>
      <Page />
    </KywiSdkProvider>
  )
}

function Page() {
  const { data, loading, error } = useKywiSdkContent('about')
  const nav = useKywiSdkNav()
  // ...
}

Exported from src/hooks/index.ts: KywiSdkProvider/useKywiSdk (context), useSdkQuery (generic fetch-on-mount-and-deps-change hook), plus thin wrappers useKywiSdkContent/useKywiSdkContentById/useKywiSdkContentList, useKywiSdkNav/useKywiSdkBreadcrumbs, useKywiSdkFeed/useKywiSdkFeedQuery. react/react-dom are optional peer dependencies — only needed if you import from @kywi-software/sdk/hooks.

Errors

import { KywiSdkError } from '@kywi-software/sdk'

try {
  await kywi.content.create('page', { title: 'x' })
} catch (err) {
  if (err instanceof KywiSdkError) {
    console.error(err.status, err.body)
  }
}

Testing

pnpm --filter @kywi-software/sdk test

All client tests stub global.fetch (vi.stubGlobal('fetch', mockFetch)) — no network or database access required.