Developer docs
01 // Overview
Overview
ve-track is cost attribution for AI shaped apps. One install, one wrapper line, and every provider fetch your app makes is priced and attributed to your app, your Clerk org, your end user, and the action they ran. It shows up on your dashboard within seconds.
The fastest path is the Quickstart. Already integrated and curious what changed? Pricing and version history live in the CHANGELOG.
02 // Quickstart
Quickstart
Four steps, about a minute. No infra to spin up.
1. Install
Add the package to any Cloudflare Worker, Node, Bun, or Deno project.
bun add github:Handbook-Enterprises/ve-track
# or: npm install github:Handbook-Enterprises/ve-track2. Issue a key
Open the Keys page, click New key, and copy the vt_live_… value.
3. Add two env vars
Put these in your worker's .dev.vars, and as secrets in production.
VE_TRACK_KEY=vt_live_xxxxxxxxxxxxxxxxxxxxxxxx
VE_TRACK_BASE_URL=https://track.viewengine.ai4. Wrap your handler
import { trackHandler } from "@viewengine/track";
import app from "./api";
export default trackHandler<Env>(
{ app: "my-app" },
{
fetch: (req, env, ctx) => app.fetch(req, env, ctx),
},
);That is it. Every external provider fetch is now intercepted, priced, attributed to the signed in Clerk user and org, and shipped to your dashboard. No Clerk app? Pass resolveUser: "none" to skip user attribution.
03 // Worker shapes
Worker shapes
trackHandler wraps any worker entry point. Pick the shape that matches yours.
Plain HTTP
export default trackHandler<Env>(
{ app: "my-app" },
{ fetch: (req, env, ctx) => app.fetch(req, env, ctx) },
);Queue
Wrap each message with trackMessage. The producer stamps auth and action on the message body, and ve-track reads them automatically.
export default trackHandler<Env>(
{ app: "my-app" },
{
queue: async (batch, env, ctx) => {
await Promise.all(
batch.messages.map((message) =>
trackMessage(message, async () => {
await processOne(message.body, env);
}),
),
);
},
},
);
// producer side:
await env.MY_QUEUE.send({
...payload,
auth: { userId, orgId },
action: "rank-refresh",
});Scheduled and email
Cron ticks default to action: "scheduled" and email triggers to action: "email". Wrap with trackAction to give them a real name.
scheduled: async (controller, env, ctx) => {
await trackAction("nightly-rebuild", async () => {
await rebuildEverything(env);
});
},04 // Tagging actions
Tagging actions
Tagging lets the dashboard tell you what each kind of run costs, for exampleai-search · $0.014 avg/run. That is the number you base credit prices on. Three ways, by precedence.
Per queue message
env.MY_QUEUE.send({ ...payload, action: "ai-search" });Per HTTP block
await trackAction("ai-search", async () => {
await openai.chat.completions.create(...);
});Worker shape default
Untagged work falls back to queue, scheduled, or email based on the entry point.
05 // Manual events
Manual events
Hit a provider the lib does not recognize, or already know the cost? Call trackUsage from inside any tracked scope. It inherits the scope's app, user, org, and action. Outside a scope it is a silent no op, so it is safe to leave in.
import { trackUsage } from "@viewengine/track";
const res = await fetch("https://api.some-provider.com/run", { ... });
const body = await res.json();
trackUsage({
provider: "some-provider",
costUsd: body.cost,
model: body.model,
promptTokens: body.usage?.input,
completionTokens: body.usage?.output,
statusCode: res.status,
});06 // Configuration
Configuration
trackHandler(config, handler) accepts:
| Field | Default | Notes |
|---|---|---|
app | required | Stable slug shown on the dashboard, like ve-rank. |
apiKey | env.VE_TRACK_KEY | Override if your secret is named differently. |
baseUrl | track.viewengine.ai | Point at staging or a self hosted instance. |
resolveUser | "clerk" | Reads the Clerk session. Pass a custom resolver, or "none" to disable. |
Custom resolver for non Clerk auth:
trackHandler<Env>(
{
app: "my-app",
resolveUser: async (req, env) => ({
userId: parseSession(req)?.userId ?? null,
orgId: parseSession(req)?.tenantId ?? null,
}),
},
{ fetch: ... },
);07 // Providers
Providers
These domains are auto detected and priced for you, no config. Token based LLMs are priced server side from a live catalog; the rest use the cost the provider reports.
OpenAI
api.openai.com
Anthropic
api.anthropic.com
Gemini
generativelanguage.googleapis.com
OpenRouter
openrouter.ai/api
Perplexity
api.perplexity.ai
Cloro
api.cloro.dev
Fal
fal.run
Zyte
api.zyte.com
DataForSEO
api.dataforseo.com
Apify
api.apify.com
Firecrawl
api.firecrawl.dev
BrightData
brightdata.com
Add one with a single entry in src/providers.ts: match the URL, optionally enhance the request, and extract the cost and tokens from the response.
08 // Users and orgs
Users and orgs
With the default resolveUser: "clerk", ve-track reads the Authorization: Bearer header, verifies it with your CLERK_SECRET_KEY, and attributes the event to that user and org. If any step fails the request still runs, the event just is not user attributed.
For queue messages, the producer stamps body.auth = { userId, orgId } on the message and trackMessage picks it up. The dashboard resolves IDs to names server side, so you see real people and orgs instead of raw IDs.