@apifyhub/analytics
v0.5.0
Published
Drop-in instrumentation for Apify actors. One call: instrument({ apifyHubKey }).
Readme
@apifyhub/analytics
Drop-in analytics instrumentation for Apify actors. Wrap your actor body in one call and each run reports itself to your apifyHub dashboard — start, finish, status, timing, and (for pay-per-event actors) charges and revenue. No method patching, no platform webhooks.
Install
npm install @apifyhub/analyticsRequires Node 18+ (uses the built-in fetch). Zero runtime dependencies.
Usage
Wrap your actor's body in withAnalytics():
import { withAnalytics } from '@apifyhub/analytics';
await withAnalytics({ apifyHubKey: process.env.APIFYHUB_KEY }, async () => {
// ...the rest of your actor code runs normally.
});CommonJS works too:
const { withAnalytics } = require('@apifyhub/analytics');
withAnalytics({ apifyHubKey: process.env.APIFYHUB_KEY }, async () => {
// ...
});withAnalytics POSTs a start-of-run snapshot, runs your function, then — on
both success and failure — POSTs an end-of-run event with the terminal status,
timing, and charge/revenue data. The original return value is passed through,
and a thrown error is re-thrown after reporting, so wrapping never changes your
actor's behavior.
Start-only mode
instrument() is still exported for cases where you can't wrap the body. It
fires only the start beacon — it cannot capture terminal status, timing, or
revenue:
import { instrument } from '@apifyhub/analytics';
instrument({ apifyHubKey: process.env.APIFYHUB_KEY });Prefer withAnalytics when you can.
Cost tracking
If your actor spends money on external services during a run — LLM calls, paid
proxies, any third-party API — call trackCost() as many times as you like.
Totals per source are accumulated in-process and folded into the end-of-run
beacon automatically, so the dashboard can report profit margin, not just
revenue:
import { withAnalytics, trackCost } from '@apifyhub/analytics';
await withAnalytics({ apifyHubKey: process.env.APIFYHUB_KEY }, async () => {
const completion = await openai.chat.completions.create({ ... });
trackCost('openai', 0.0032);
// ...
});trackCost never throws — a bad amountUsd is silently dropped rather than
corrupting the run's totals or crashing the actor. Costs tracked before
withAnalytics/instrument runs (or without either ever running) are simply
never sent.
Setup
- Generate an apifyHub key on your apifyhub.com profile. It's shown once — copy it then.
- Add it to your actor as a secret environment variable named
APIFYHUB_KEY(Apify Console → your actor → Settings → Environment variables). - Deploy. Every run now reports itself.
Options
Both withAnalytics(options, fn) and instrument(options) take the same
options object:
{
apifyHubKey: string; // required — your apifyHub key (Bearer token)
endpoint?: string; // override the ingest URL (defaults to apifyHub prod)
debug?: boolean; // log to console.error on failures (default false)
forceOutsideApify?: boolean; // send even when not running on Apify (default false)
apifyMarginPct?: number; // Apify's margin fraction (e.g. 0.2 = Apify keeps 20%);
// if set, the server reports net revenue = gross × (1 − margin)
}What it sends
The key always travels as an Authorization: Bearer header and is never sent
in the body. Both events POST JSON to the same /v1/sdk-ingest endpoint; the
server merges the end event onto the start row by runId.
Start beacon — reads the APIFY_* environment variables the platform
injects: run id, actor id/full name, build/task ids, consumer userId,
APIFY_USER_IS_PAYING, origin, memory, timeout, max total charge, and
permission level.
End beacon (withAnalytics only) — terminal status (succeeded /
failed), start/finish timestamps and duration, plus charge data read from the
in-process Apify ChargingManager: pricing model, per-event counts and USD,
gross revenue, and result count. Charges are read synchronously in-process — no
Apify API call. If apify isn't importable or the actor doesn't charge, the
end event carries status and timing only.
It also carries an external-cost signal, so the dashboard can report profit margin, not just revenue:
- External service cost (
externalCostUsd,externalCostBreakdown) — whatever you reported viatrackCost()during the run, totaled by source.
Apify's own infra cost (compute/proxy/dataset) is not read by the SDK.
Reading usageTotalUsd in-process just before the actor exits returns $0 —
the platform hasn't finalized it yet at that instant. Instead the apifyhub
server fetches it from /v2/actor-runs/{runId} about 30 seconds after the end
beacon lands, using the Apify API token you save on your apifyhub profile, and
folds it into the run's cost then. No token, no infra-cost reconciliation —
revenue and external cost still work.
Guarantees
- Never throws. A bad key, network outage, or non-Apify environment can
never crash your actor, and the wrapper never swallows your function's own
error — it re-throws it after reporting (or logs when
debug: true). - Start is non-blocking. The start POST is fired with a 5s timeout and not awaited, so it never delays your actor's work. The end POST is awaited (briefly, with a timeout) so it survives container teardown.
- Idempotent start. Calling
instrument()— orwithAnalytics, which calls it internally — more than once in the same process is a silent no-op for the start beacon after the first call. - Outside Apify it no-ops (unless
forceOutsideApify: true), so local runs and tests don't send anything. - A hard kill bypasses the end beacon. OOM, timeout, or abort skips the
finally, so those runs report the start beacon only.
