Reference

@invisitdev/server reference

.mdllms.txt

Every function, option and error code in @invisitdev/server, the first-party proxy, crawler analytics and server-side events client.

@invisitdev/server
import { clientIp, createInvisit, InvisitError } from '@invisitdev/server'

createInvisit

@invisitdev/server
function createInvisit(options: InvisitOptions): Invisit
Option Type Default What it is
key string required An API key of the site with the ingest scope (invisit_sk_…).
baseUrl string https://api.invisit.dev The collector. Leave it unless support gives you another address.
clientIp (request: Request) => string | null clientIp Reads the visitor’s address from a request.
fetch typeof fetch global fetch The fetch used to reach the collector, for instance to instrument it.
onError (error: unknown) => void console.error Receives failures that are not thrown: proxy 502s, crawler reads not accepted, CLIENT_IP_UNRESOLVED once per client.
respectGpc boolean false Turned on, leaves out events of visitors whose browser sends Global Privacy Control.

Throws a TypeError when the key is missing or empty, or is not an invisit_sk_ key. Requests to the collector time out after 10 seconds.

handle

@invisitdev/server
handle(request: Request, options?: HandleOptions): Promise<Response | null>

The middleware one-liner. Proxies tracker requests and resolves to their response; for every other request it records a crawler read in the background and resolves to null.

Option Type What it is
prefix string The path the tracker is served under. Default '/_i'.
ip string | null The visitor’s address for this request. Overrides clientIp.
waitUntil (promise: Promise<unknown>) => void Keeps the crawler read alive after the response, such as ctx.waitUntil.

handle runs before your app, so crawler reads are recorded with status 200. Use proxy and crawlers separately for the real status.

proxy

@invisitdev/server
proxy(request: Request, options?: { prefix?: string; ip?: string | null }): Promise<Response | null>

Forwards a tracker request under prefix to the collector and resolves to its response, or to null for any other path. It never rejects:

Situation Response
Method other than GET, HEAD or POST 405
Body larger than 1 MiB 413
Collector unreachable or timed out 502, and the error goes to onError

crawlers

@invisitdev/server
crawlers(request: Request, options?: { status?: number }): Promise<void>

Records the request as a crawler read when it is one. status is what your server answered, 200 by default. Never rejects. See Crawler reads.

track

@invisitdev/server
track(name: string, options: TrackOptions): Promise<{ accepted: number; duplicate: number }>

Sends a custom event for the visitor behind options.request, or for options.visitor when there is no request of theirs. Rejects with an InvisitError. How and when to use it: Server-side events.

Option Type What it is
request Request The visitor’s own request; their address, user agent, Accept-Language and Sec-GPC come from it.
ip string | null With request: the visitor’s address when no header carries it. Overrides clientIp.
visitor { ip, userAgent, language?, gpc? } Instead of request, when only the visitor’s details were kept (a Convex action, a queue consumer, a WebSocket server).
url string The page the event belongs to.
properties Record<string, string | number | boolean | null> Event properties, under the browser’s rules.
userId string Your id for the signed-in user, stored hashed.
eventId string A UUID v7. Reuse it when retrying so the event counts once. Generated when absent.

Pass request or visitor, never both. A visitor whose ip is not an IP address rejects with CLIENT_IP_UNRESOLVED.

clientIp

@invisitdev/server
function clientIp(request: Request): string | null

The default address reader: the first present of CF-Connecting-IP, X-Real-IP, Fly-Client-IP, True-Client-IP, X-Vercel-Forwarded-For and the first entry of X-Forwarded-For. A malformed value gives null rather than falling through to a weaker header. IPv4-mapped IPv6 is reduced to IPv4.

@invisitdev/server/node

Adapters for Node http, in their own entry so the main one loads on edge runtimes.

Export What it does
middleware(invisit, options?) Express and Connect middleware: proxies tracker requests and records crawler reads with your app’s real status once the response finishes.
toRequest(req, options?) An IncomingMessage as a web Request. Reads the body of anything but GET and HEAD, up to 1 MiB.
sendResponse(res, response) Writes a web Response to a ServerResponse, streaming with backpressure.

middleware and toRequest take { trustProxy?: boolean }, and middleware also takes { prefix?: string }. Without trustProxy they use the socket address and drop forwarding headers.

Errors

track rejects with an InvisitError; proxy, handle and crawlers never reject and report to onError.

src/routes/checkout.ts
import { InvisitError } from '@invisitdev/server'

try {
  await invisit.track('purchase', { request })
} catch (error) {
  if (error instanceof InvisitError && error.code === 'RATE_LIMITED') {
    // back off
  }
}
Property Type What it is
code string A stable, machine-readable code.
message string A human-readable explanation.
status number | null The collector’s HTTP status, null with no response.
requestId string | null The collector’s request id. Quote it to support.
details Record<string, unknown> Extra context, such as validation details.
Code Raised when
VALIDATION_FAILED The collector rejected the event’s shape or values.
UNAUTHENTICATED The API key is wrong, revoked, or lacks the ingest scope.
RATE_LIMITED Too many requests. Retry later.
NETWORK_ERROR The collector could not be reached or timed out. The cause is in error.cause.
CLIENT_IP_UNRESOLVED No visitor address could be read. Pass ip, or a clientIp reader.
USER_AGENT_MISSING The request has no User-Agent header.
HTTP_<status> The collector answered with an error and no recognizable body.

The Node adapter adds BODY_CONSUMED (a body parser ran first) and PAYLOAD_TOO_LARGE (a body over 1 MiB).

Troubleshooting

  • createInvisit: no key given. The variable is missing where the code runs: check build steps, preview deploys and your runtime’s secret store.
  • key must be an API key with the ingest scope. You passed the site id. The server needs an invisit_sk_… key; the site id belongs in the browser.
  • Tracker POSTs get a 403. Your framework’s CSRF check rejects them; see the SvelteKit and Astro notes in the server frameworks.
  • Crawler reads all show status 200. You are using handle, which runs before the page. Use proxy before and crawlers after your app.