Skip to main content
This guide walks through migrating from the @honeyhive/logger package to @honeyhive/api-client.

What changes for you

@honeyhive/logger targets HoneyHive v1 and is being replaced by @honeyhive/api-client, the typed v2 SDK. The two are not drop-in compatible, but you get a lot more in return:
  • Full typed access to the v2 API. Beyond logging events, you can manage datasets, datapoints, experiments, metrics, and event search from the same client.
  • One Client instance, not per-call config. Set your API key once via HH_API_KEY instead of passing apiKey (and project) on every call.
  • Project scope is automatic. v2 API keys are scoped to a single project, so the SDK no longer needs a project parameter. If you write to multiple projects, construct one Client per key.
  • Errors surface instead of being swallowed. Failed calls throw typed errors you can catch, rather than logging to console.error and returning undefined.
Logger’s three functions (start, log, update) map onto two namespaces on the new client: client.sessions and client.events.

Install and import

npm uninstall @honeyhive/logger
npm install @honeyhive/api-client
Old:
import { start, log, update } from "@honeyhive/logger";
New:
import { Client } from "@honeyhive/api-client";

const client = new Client(); // reads HH_API_KEY from env

Configuration

See the full ClientConfig reference for every option. The most common settings and mechanism for setting them are below.
ConceptLogger (@honeyhive/logger)API client (@honeyhive/api-client)
API keyapiKey param on every call.Set the HH_API_KEY environment variable.
Projectproject param on every call.Determined by the API key’s project scope. No project parameter exists.
Data Plane URL (was serverUrl)serverUrl param, default https://api.honeyhive.ai.dataPlaneUrl in the Client constructor or HH_DATA_PLANE_URL environment variable, default https://api.dp1.us.honeyhive.ai. serverUrl and HH_API_URL are deprecated aliases.
Source / environmentsource param, default "dev".source on the session/event body. Both sessions and events default to "unknown" if omitted. Set it explicitly (e.g. "prod", "staging") if you want to filter by environment.
Verboseverbose per-call.HH_VERBOSE=true env var (or ClientConfig.verbose). One-shot startup log of resolved URL and masked key.

TLS / certificate verification

Logger exposed a per-call verify flag (Node-only). The new SDK is isomorphic - it runs in Node and the browser - and TLS verification isn’t something you can flip from JavaScript in the browser, so the SDK doesn’t expose a flag. In Node, if you genuinely need to disable verification (for example, to talk to an internal HoneyHive deployment with a self-signed cert), use the standard Node environment variables: prefer NODE_EXTRA_CA_CERTS=/path/to/ca.pem to register the internal CA, or NODE_TLS_REJECT_UNAUTHORIZED=0 as a last resort. Do not disable verification against the public HoneyHive endpoint. In the browser, certificate trust is browser-managed - trust the cert at the OS or browser level.

Retry behavior changed

Logger had built-in retry (3 attempts, exponential backoff with jitter, retrying on 5xx / 408 / 429 / network errors). The new SDK does not retry. If you relied on logger’s retry behavior in production, you’ll need to add it yourself - wrap calls in a small retry helper or use a library like p-retry.

Function-by-function migration

start(...)client.sessions.create(...)

See the sessions namespace for the full method reference. Before:
const sessionId = await start({
  apiKey: "hh_...",
  project: "my-project",
  sessionName: "my-session",
  source: "dev",
  config: {
    /* ... */
  },
  inputs: {
    /* ... */
  },
  metadata: {
    /* ... */
  },
  userProperties: {
    /* ... */
  },
});
After:
const { session_id } = await client.sessions.create({
  session_name: "my-session",
  source: "dev",
  config: {
    /* ... */
  },
  inputs: {
    /* ... */
  },
  metadata: {
    /* ... */
  },
  user_properties: {
    /* ... */
  },
});
LoggerNew SDKNotes
apiKeyClient constructor / HH_API_KEYOnce per client, not per call
project-Determined by API key
sessionNamesession_name
sourcesource
sessionIdsession_idUUID still optional; auto-generated if omitted
configconfig
inputsinputs
metadatametadata
userPropertiesuser_properties
serverUrldataPlaneUrl in the Client constructor / HH_DATA_PLANE_URLOnce per client, not per call. serverUrl and HH_API_URL are deprecated aliases
verboseClient constructor / HH_VERBOSEOne-shot startup log, not per-call verbosity
The new SDK returns the full CreateSessionResponse (which includes both session_id and a separate event_id - see Updating a session below) and throws on failure rather than swallowing the error. See Error handling below.

log(...)client.events.create(...)

See the events namespace for the full method reference. Before:
const eventId = await log({
  apiKey: "hh_...",
  project: "my-project",
  sessionId,
  eventName: "model_inference",
  eventType: "model",
  durationMs: 120,
  inputs: { prompt: "..." },
  outputs: { response: "..." },
  config: {
    /* ... */
  },
  metadata: {
    /* ... */
  },
});
After:
const { event_id } = await client.events.create({
  session_id,
  event_name: "model_inference",
  event_type: "model",
  duration: 120,
  inputs: { prompt: "..." },
  outputs: { response: "..." },
  config: {
    /* ... */
  },
  metadata: {
    /* ... */
  },
});
LoggerNew SDKNotes
apiKey / project-Set on the client / API key, not per call
sessionIdsession_id
eventNameevent_name
eventTypeevent_typeSame union: 'model' | 'tool' | 'chain'. New SDK additionally accepts 'session'.
durationMsdurationRenamed: still milliseconds.
sourcesource
config, inputs, outputs, metadatasame names
-parent_id, children_ids, error, start_time, end_time, feedback, metrics, user_propertiesNew optional fields you didn’t have before
If you log many events back-to-back, take a look at client.events.createBatch({ events: [...] }) - it submits them in a single request.

update(...)client.events.update(...)

Before:
await update({
  apiKey: "hh_...",
  eventId, // logger accepted either an event_id OR a session_id
  metadata: {
    /* ... */
  },
  feedback: { rating: 5 },
  metrics: { latency: 100 },
  outputs: {
    /* ... */
  },
  config: {
    /* ... */
  },
  userProperties: {
    /* ... */
  },
  durationMs: 120,
});
After:
await client.events.update({
  event_id,
  metadata: {
    /* ... */
  },
  feedback: { rating: 5 },
  metrics: { latency: 100 },
  outputs: {
    /* ... */
  },
  config: {
    /* ... */
  },
  user_properties: {
    /* ... */
  },
  duration: 120,
});
Same renames as above: durationMsduration, userPropertiesuser_properties.

Updating a session

This is a meaningful behavior change. In v1, the legacy server treated session_id and event_id interchangeably for updates, so logger’s update({ eventId }) would accept either. In v2 it does not - passing a session_id to events.update returns a 400 Bad Request with the message no event with event_id - <id> found. A session is itself an event with event_type: 'session'. client.sessions.create returns both identifiers:
  • session_id - the session correlation ID. Use this as the session_id on child events to associate them with the session.
  • event_id - the row ID of the session-typed event itself. Use this when you want to update the session row.
const { session_id, event_id: session_event_id } = await client.sessions.create(
  {
    session_name: "my-session",
  },
);

// Use session_id as the parent on child events
await client.events.create({
  session_id,
  event_type: "model",
  event_name: "chat",
  /* ... */
});

// Use session_event_id when you want to update the session row itself
await client.events.update({
  event_id: session_event_id,
  metadata: {
    /* ... */
  },
  outputs: {
    /* ... */
  },
});
events.update only merges nine specific fields: metadata, feedback, metrics, outputs, config, user_properties, duration, end_time, children_ids. Other fields are accepted by the server but silently ignored.

End-to-end example

A complete flow - start a session, log a model event, update it with the response - looks like this:
import { Client } from "@honeyhive/api-client";
import OpenAI from "openai";

const client = new Client();
const openai = new OpenAI();

const { session_id } = await client.sessions.create({
  session_name: "openai-example",
  source: "dev",
});

const startTime = Date.now();
const { event_id } = await client.events.create({
  session_id,
  event_type: "model",
  event_name: "chat-completion",
  config: { model: "gpt-4o-mini" },
  inputs: {
    messages: [{ role: "user", content: "What is the meaning of life?" }],
  },
});

const completion = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "What is the meaning of life?" }],
});
const { content } = completion.choices[0].message;

if (content) {
  await client.events.update({
    event_id,
    outputs: { content },
    duration: Date.now() - startTime,
    metadata: { model: completion.model, usage: completion.usage },
  });
}
The @honeyhive/api-client README has the same example with more commentary.

Error handling

Logger swallowed errors by default - when a call failed, it logged to console.error and returned null (set verbose: true to make it throw instead). The new SDK throws on failure:
  • ApiError - the server returned a non-2xx response. Carries .status, .response, .error, and a .parseError() helper that returns a typed ErrorResponse when the body matches.
  • NetworkError - the request never made it to the server (DNS, connection reset, TLS failure, etc.). Carries .error with the underlying cause.
Both extend HoneyHiveError, so a single catch (err) { if (err instanceof HoneyHiveError) ... } handles both:
import { Client, ApiError, NetworkError } from "@honeyhive/api-client";

try {
  await client.sessions.create({ session_name: "my-session" });
} catch (err) {
  if (err instanceof ApiError) {
    console.error(`HoneyHive API error ${err.status}:`, err.parseError());
  } else if (err instanceof NetworkError) {
    console.error("HoneyHive network error:", err.error);
  } else {
    throw err;
  }
}
If you previously relied on logger’s silent-failure behavior (your code expected null on failure and kept going), you’ll need to add a try/catch to preserve that behavior - or, better, decide whether failing loud is what you actually want.

What else you get

Migrating opens up a much larger API surface. None of these have a logger equivalent: For worked examples see the @honeyhive/api-client README. For the full reference, see the API SDK reference site.