Wrappers cover the OpenAI, Anthropic, and Vercel AI SDKs. For anything else — custom inference stacks, other providers, backfills — construct canonical events yourself and capture them directly.
Capture methods
carbon.captureLlmCall({ event }); // one LLM event
carbon.captureToolCall({ event }); // one tool event
carbon.captureEvents({ events }); // a mixed batch
All three validate events synchronously against the canonical schema and throw on invalid input, then buffer the events for delivery like any wrapper-captured event. The full field reference is in the event schema.
LLM event example
import { randomUUID } from "node:crypto";
const startTimeMs = Date.now();
const result = await myInferenceCall(prompt);
const endTimeMs = Date.now();
carbon.captureLlmCall({
event: {
type: "llm",
id: randomUUID(),
startTimeMs,
endTimeMs,
durationMs: endTimeMs - startTimeMs,
status: { state: "ok" },
instrumentation: {
provider: "carbon",
sourceProvider: "openai",
sourcePackage: "openai",
},
context: { userId: "user-481" },
additionalProperties: {},
properties: {
llm: {
model: "gpt-5.4-nano",
input: { system: "You are concise.", prompt, tools: [] },
output: { mode: "generate", response: result.text, toolCalls: [] },
usage: {
inputTokens: result.usage.inputTokens,
inputTokenDetails: {
uncachedTokens: result.usage.inputTokens,
cacheReadTokens: 0,
cacheWriteTokens: 0,
},
outputTokens: result.usage.outputTokens,
outputTokenDetails: {
reasoningTokens: 0,
responseTokens: result.usage.outputTokens,
},
totalTokens: result.usage.inputTokens + result.usage.outputTokens,
},
},
},
},
});
Omit spaceId and cost — the ingest API sets the space from your API key
and computes cost from its model catalog when it recognizes the model.
const startTimeMs = Date.now();
const output = await searchIndex({ query });
const endTimeMs = Date.now();
carbon.captureToolCall({
event: {
type: "tool",
id: randomUUID(),
traceId,
startTimeMs,
endTimeMs,
durationMs: endTimeMs - startTimeMs,
status: { state: "ok" },
instrumentation: {
provider: "carbon",
sourceProvider: "vercel",
sourcePackage: "ai",
},
context: { agentId: "search-agent" },
additionalProperties: {},
properties: {
tool: {
name: "search_index",
input: JSON.stringify({ query }),
output: JSON.stringify(output),
},
},
},
});
Recording failures
Set status.state to "error" and include what you know about the failure:
status: {
state: "error",
error: {
code: "rate_limit_exceeded",
httpStatus: 429,
message: "Provider rate limit hit",
},
},
Failed calls appear in the dashboard with error status, so error rates can be sliced by model, agent, and user like any other dimension.
Sending without the SDK
If you are not on JavaScript, post the same event shapes to the ingest API directly — see the API reference.