# Runtime

`@sonordev/site-kit/runtime` is how a package that builds on site-kit talks to Sonor from the browser, so it never needs a transport of its own. [commerce-kit](https://sonor.dev/commerce-kit) is built on it.

Building a site? You don't need this entry. Forms, analytics, articles and the rest of site-kit already use the same transport.

## What it gives a kit

| Export                             | Use it to                                                                                                                                                                                                                                                                                                                                                                                |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sonorFetch(url, init?, options?)` | Call the Sonor API from the browser. It sends the site credential you pass as `apiKey`, retries transient failures with a timeout, refreshes an expired site token once, and pauses for a few minutes after an authentication failure, so a misconfigured site doesn't hammer the API.                                                                                                   |
| `getClientApiConfig(options?)`     | Find the API origin and credential. In the browser that's what `SiteKitLayout` published. During server rendering it's the server's `SONOR_API_URL`, and with `{ serverKey: true }` its `SONOR_API_KEY`.                                                                                                                                                                                 |
| `readPublishedApiConfig()`         | Exactly what `SiteKitLayout` published, with no fallbacks. For code that must stay quiet on a page without the layout.                                                                                                                                                                                                                                                                   |
| `DEFAULT_SONOR_API_URL`            | `https://api.sonor.io`.                                                                                                                                                                                                                                                                                                                                                                  |
| `readAnalyticsIdentity()`          | Get the visitor's analytics ids, `{ sessionId, visitorId }`, so what you send to Sonor ties to the visit analytics recorded. It only reads: either id is `null` during server rendering and before analytics has started a session in the tab. It returns null ids when current analytics permission blocks collection. It never throws, including when the browser blocks site storage. |

The types `SonorFetchOptions`, `FetchRetryOptions`, `ClientApiConfigOptions` and `AnalyticsIdentity` come with them.

```ts
import { getClientApiConfig, sonorFetch } from '@sonordev/site-kit/runtime'

export async function startCheckout(payload: unknown) {
  const { apiUrl, apiKey } = getClientApiConfig()
  // Anything that isn't safe to repeat (checkout, payments, uploads) passes retries: 0.
  const response = await sonorFetch(
    `${apiUrl}/api/public/example/checkout`,
    { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) },
    { apiKey, retries: 0 },
  )
  return response.json()
}
```

Pass `apiKey` from `getClientApiConfig()` and `sonorFetch` sets the `x-api-key` header itself, using the site's freshest token when the site uses one. Don't set `x-api-key` or `Authorization` yourself: on a site that uses tokens, a stale header you set is replaced by the fresh one.

## Rules for kits

- **Use this entry for browser calls.** Don't read `window.__SITE_KIT_*` and don't call `fetch` on the Sonor API directly. A second transport is a second copy of the credential, retry and breaker logic, and it drifts.
- **Get the visitor's ids from `readAnalyticsIdentity()`.** Where site-kit keeps them is its own business and can change.
- **Server data goes through `@sonordev/site-kit/server`.** `serverApiFetch` sends the key, bounds each call in time and retries transient failures. Tag what you fetch with your content's entry in `SITE_CACHE_TAGS` so an edit in Sonor refreshes it, and if you catch errors around your own server calls, rethrow the ones `isNextControlFlow` recognizes: they're Next steering the render (a page turning dynamic, `redirect()`, `notFound()`), not failures.
- **Events go through `trackEvent` and `trackConversion`** from `@sonordev/site-kit/analytics` (`@sonordev/site-kit/client` exports them too). They wait for the analytics provider, which mounts after the page. Don't use the `useAnalytics` hooks in a kit: under `SiteKitLayout` your components sit outside the provider, so the hooks have nothing to return.
- **Lead capture goes through `ManagedForm`** from `@sonordev/site-kit/forms`.
- **List site-kit as a peer dependency** and mark it external in your bundler, so every kit shares the site's one copy.

## Why it's its own entry

`@sonordev/site-kit/client` is stamped `'use client'`. A function imported from a stamped module into a server component becomes a client reference there and can't be called, and `getClientApiConfig` is built to run during server rendering. So `runtime` isn't stamped, imports neither React nor Next, and works the same from a server component, a route handler or a client component.

Analytics identity reads share the same current consent state as the layout and analytics runtime, including when separate bundles load them. Pending preferences, withdrawal and expired configured policy block reads synchronously. Reading an identity never grants advertising permission or creates an id.

## Stability

This entry is public API under semver. New exports arrive in minor releases; nothing in the list above is removed or changed in a minor or patch release.

## Shared website utilities (8.3)

`@sonordev/site-kit/shared` provides `withSiteParam`, `withSiteBody`, `resolveSiteHost`, `readPublishedSite`, `applyTrailingSlash`, `resolveTrailingSlash` and `safeJsonLd`. These are framework-free functions for kits and sites; the entry is never marked `use client`. Press Kit uses them to keep sub-site scoping, URL shape and JSON-LD escaping consistent without reaching into private Site Kit files. `readPublishedSite()` returns the layout's explicitly published host, or undefined on the server.

`sonorFetch` loads its shared execution path on use and keeps its existing Promise-based API. Optional dispatch checks run again after loading and before each attempt. The same token state, refresh and auth breaker serve every caller; loading failure rejects the Promise rather than using another transport. Synchronous unload telemetry doesn't wait for that execution module.
