trackrev
v0.3.0
Published
The TrackRev SDK and CLI — tracking links, channel analytics, referrals and affiliate payouts, from Node or the terminal.
Maintainers
Readme
trackrev
TrackRev for Node and the terminal — create and manage tracking links, pull performance per channel and per link, walk the raw click stream, replay any visitor's journey from first click to paid conversion, and run a referral program end to end.
One package, two ways in: import { TrackRev } from "trackrev" in your app, or the trackrev
command in your shell. Zero dependencies. Node 20 or newer.
Install
In your project, for the SDK:
npm install trackrevGlobally, for the CLI:
npm install -g trackrevOr run the CLI once without installing:
npx trackrev channelsAuthenticate
Create a secret key under Settings → Developers, then either save it:
trackrev loginIt is stored at ~/.config/trackrev/config.json with mode 0600 and verified against the API
before it is written, so a typo fails immediately rather than on your next command.
Or set it in the environment, which always wins and is what CI should use:
export TRACKREV_KEY=lk_live_...TRACKREV_API_URL points the CLI at a different API base (self-hosted, staging). Use
--profile NAME on login to keep several environments side by side.
Plans
Link and account commands work on every plan, including free, and respect the same limits the
dashboard does (50 links on the free tier). Analytics commands — channels, links perf,
clicks, journey — need a paid plan; on a free workspace they exit 3 with an upgrade
message.
Node SDK
Everything below, as a client for your own code. Twenty resources, 71 methods, and a
hand-written index.d.ts, so an editor completes trackrev.links. and catches a misspelled
option before the code runs.
import { TrackRev } from "trackrev";
const trackrev = new TrackRev(process.env.TRACKREV_KEY);
const { channels } = await trackrev.channels.list({ days: 7 });
const launch = await trackrev.links.create("https://acme.com/launch", "Launch", {
channels: ["youtube", "newsletter"],
maxClicks: 1000,
});Required arguments are positional; everything optional goes in a trailing object, named in camelCase and sent as the snake_case the API expects.
trackrev.attribution .get .update
trackrev.channels .list
trackrev.clicks .list
trackrev.commissions .list .create .setStatus
trackrev.credits .list .markDelivered
trackrev.domains .list .get .add .verify .remove
trackrev.export .csv
trackrev.folders .list .get .create .update .remove .assign
trackrev.keys .list .create .revoke
trackrev.links .list .records .get .create .update .remove .createMany .qr
trackrev.orders .list
trackrev.partners .list .setStatus .setGroup
trackrev.payouts .list .settle
trackrev.programs .list .get .update .groups
trackrev.referrals .enroll .reportSignup .reportPurchase .stats
.setPayoutMethod .setRewardMode
trackrev.retargeting .list .set .remove
trackrev.revenue .connections .connection .providers .connect
.setWebhookSecret .disconnect .sync
trackrev.settings .notifications .setNotification .branding .updateBranding
trackrev.visitors .list .get .journey
trackrev.webhooks .list .events .get .create .update .remove
trackrev.me()A referral program, end to end
// Someone opts in. You get back their referral link.
const { referral_link } = await trackrev.referrals.enroll(user.id, user.email, "paid");
// Someone they invited signs up.
await trackrev.referrals.reportSignup(newUser.id, { refCode: "abc123" });
// That person pays. Idempotent on your own order id, so a replay never double-credits.
await trackrev.referrals.reportPurchase(newUser.id, order.id, 49.99, { currency: "usd" });Errors
Anything that is not a success rejects with a TrackRevError carrying the HTTP status and
the API's own code. A request that never got an answer — server unreachable, or timed out —
rejects with a TrackRevConnectionError, which is a TrackRevError with status 0.
import { TrackRev, TrackRevError, TrackRevConnectionError } from "trackrev";
try {
await trackrev.keys.create({ scope: "secret", label: "CI" });
} catch (e) {
if (e instanceof TrackRevConnectionError) retryLater();
else if (e instanceof TrackRevError) console.error(e.status, e.code, e.message);
else throw e;
}Retries
A request that is safe to repeat is retried three times on a 429, a 5xx or a connection
failure, backing off 400ms, 800ms, 1600ms with a little randomness so throttled clients do not
all return in the same instant. A Retry-After header wins over that schedule.
Safe to repeat means every GET, plus the three referral writes the API ignores a repeat of:
enroll, reportSignup and reportPurchase. Every other write — creating a link, minting a
key, settling a payout — is sent exactly once, because a retry there could do the work twice.
Options
new TrackRev(key, {
apiUrl: "https://app.trackrev.io/api/v1", // point at staging or self-hosted
timeoutMs: 15000, // per attempt; 0 means no limit
maxRetries: 3, // 0 turns retries off
});An endpoint the SDK has no method for yet is one call away:
await trackrev.request("GET", "/some/new/endpoint");Commands
Generated from apps/cli/src/registry.js — edit there, then run pnpm --filter trackrev sync-docs.
Analytics
trackrev channels
performance per traffic source (paid plan)
trackrev channels --days 7 --ltv| flag | meaning |
| --- | --- |
| --days N | last N days (default 30, max 365) |
| --from ISO | explicit start, e.g. 2026-01-01; wins over --days |
| --to ISO | explicit end (defaults to now) |
| --ltv | add all-time lifetime value per channel |
conversions is a decimal on purpose — attribution splits credit, so one sale touched by two channels counts 0.5 on each.
trackrev links perf
performance per short link (bare trackrev links still works) (paid plan)
trackrev links perf --limit 20| flag | meaning |
| --- | --- |
| --limit N | rows to return (max 500) |
| --days N | last N days (default 30, max 365) |
| --from ISO | explicit start, e.g. 2026-01-01; wins over --days |
| --to ISO | explicit end (defaults to now) |
| --settings | attach each link's URL, expiry and password flag |
trackrev clicks
the raw click stream — newest first, bots excluded (paid plan)
trackrev clicks --all --json| flag | meaning |
| --- | --- |
| --limit N | page size (default 100, max 500) |
| --all | follow the cursor to the end of the stream |
| --link ID | one link only |
| --bots | include bot traffic |
--all stops after 200 pages and says so on stderr, so a runaway cursor can never loop forever.
trackrev visitors journey <visitor-id>
one visitor's timeline — every click, identify event and order, in order (paid plan)
trackrev journey <visitor-id>The visitor caption goes to stderr, so the rows stay pipe-clean.
Links
trackrev links list
the links themselves — newest first, no window
trackrev links list --channel youtube| flag | meaning |
| --- | --- |
| --limit N | page size (default 100, max 500) |
| --all | follow the cursor to the end |
| --channel KEY | one channel only |
| --q TEXT | slug contains this text |
trackrev links create
create a campaign: one link per channel, or one Smart Link
trackrev links create --url https://acme.com/launch --name Launch --channel youtube --channel newsletter| flag | meaning |
| --- | --- |
| --url URL | destination (required) |
| --name TEXT | campaign name (required) |
| --channel KEY | a channel; repeat for several |
| --smart | one link that infers its channel per click |
| --tag TEXT | a tag; repeat for several |
| --folder ID | put the campaign in this folder |
| --external | destination is a site you can't put the pixel on |
| --slug SLUG | custom slug (2–64 chars; lowercase, digits, hyphens) |
| --campaign TEXT | utm_campaign (defaults to the name) |
| --expires ISO | expire at this time |
| --max-clicks N | expire after this many clicks |
| --expired-url URL | where to send visitors after expiry |
| --password TEXT | require this password before redirecting |
| --mobile-url URL | device targeting: send mobile here |
| --desktop-url URL | device targeting: send desktop here |
| --retarget on\|off | fire the workspace's ad pixels on click |
Prints one row per link created. Channels: facebook instagram youtube linkedin twitter tiktok newsletter website other.
trackrev links get <id-or-slug>
one link by id, slug or short code
trackrev links get black-fridaytrackrev links update <id>
change a link's slug, UTMs, expiry, password or targeting
trackrev links update <id> --expires 2026-12-31T23:59:59Z --max-clicks 1000| flag | meaning |
| --- | --- |
| --term TEXT | utm_term |
| --content TEXT | utm_content |
| --clear-password | remove the password |
| --clear-expiry | remove date and click-cap expiry |
| --slug SLUG | custom slug (2–64 chars; lowercase, digits, hyphens) |
| --campaign TEXT | utm_campaign (defaults to the name) |
| --expires ISO | expire at this time |
| --max-clicks N | expire after this many clicks |
| --expired-url URL | where to send visitors after expiry |
| --password TEXT | require this password before redirecting |
| --mobile-url URL | device targeting: send mobile here |
| --desktop-url URL | device targeting: send desktop here |
| --retarget on\|off | fire the workspace's ad pixels on click |
trackrev links delete <id>
delete one link (its campaign and other channels stay) (asks to confirm; --yes in scripts)
trackrev links delete <id> --yestrackrev links bulk
create up to 500 links from a CSV
trackrev links bulk --file links.csv| flag | meaning |
| --- | --- |
| --file PATH | CSV with url, name, channel columns (- for stdin) |
Optional columns: utm_campaign, utm_term, utm_content, tags, campaign_id. Rows that fail are listed with the reason; the rest are created.
trackrev links qr <id-or-slug>
the link's QR code as SVG
trackrev links qr black-friday --out black-friday.svg| flag | meaning |
| --- | --- |
| --out PATH | write here instead of stdout |
| --size N | size in px (default 512, max 2048) |
Developers
trackrev keys list
the workspace's API keys (prefixes only)
trackrev keys list --revoked| flag | meaning |
| --- | --- |
| --revoked | include revoked keys |
trackrev keys create
mint a key — the plaintext is shown once, never again
trackrev keys create --label 'CI deploy'| flag | meaning |
| --- | --- |
| --scope SCOPE | secret (servers, CLI) or public (browser) |
| --label TEXT | what this key is for |
The key goes to stdout and everything else to stderr, so trackrev keys create > key.txt captures only the key.
trackrev keys revoke <id>
revoke a key immediately (asks to confirm; --yes in scripts)
trackrev keys revoke <id> --yestrackrev webhooks list
outbound endpoints, with their last delivery status
trackrev webhooks listtrackrev webhooks events
every event an endpoint can subscribe to
trackrev webhooks eventstrackrev webhooks create
add an endpoint — the signing secret is shown once
trackrev webhooks create --url https://acme.com/hook --event sale.created| flag | meaning |
| --- | --- |
| --url URL | https endpoint (required) |
| --event NAME | an event; repeat for several |
https only. Run trackrev webhooks events for the valid names.
trackrev webhooks update <id>
change the URL or events, or pause and resume delivery
trackrev webhooks update <id> --pause| flag | meaning |
| --- | --- |
| --url URL | new endpoint URL |
| --event NAME | replace the event list; repeat |
| --pause | stop delivering |
| --resume | start delivering again |
trackrev webhooks delete <id>
remove an endpoint (asks to confirm; --yes in scripts)
trackrev webhooks delete <id> --yesSetup
trackrev attribution get
the model and lookback window this workspace uses
trackrev attribution get --models| flag | meaning |
| --- | --- |
| --models | list the three models and what each credits |
trackrev attribution set
change the model or the window
trackrev attribution set --model linear --window 60| flag | meaning |
| --- | --- |
| --model NAME | last_touch, first_touch or linear |
| --window N | lookback in days (1-365) |
Both settings apply retroactively — every past order is re-credited against them.
trackrev folders list
campaign folders, with how many campaigns each holds
trackrev folders listtrackrev folders create
create a folder to group campaigns under
trackrev folders create --name 'Q4 launch' --start 2026-10-01| flag | meaning |
| --- | --- |
| --name TEXT | folder name (required) |
| --description TEXT | what it covers |
| --start YYYY-MM-DD | start date |
| --end YYYY-MM-DD | end date |
trackrev folders update <id>
rename a folder or change its dates
trackrev folders update <id> --name 'Q1 launch'| flag | meaning |
| --- | --- |
| --name TEXT | new name |
| --description TEXT | new description |
| --start YYYY-MM-DD | start date |
| --end YYYY-MM-DD | end date |
trackrev folders delete <id>
delete a folder — its campaigns become ungrouped, not deleted (asks to confirm; --yes in scripts)
trackrev folders delete <id> --yestrackrev folders assign <destination-id>
file a campaign under a folder, or un-file it
trackrev folders assign <destination-id> --folder <folder-id>| flag | meaning |
| --- | --- |
| --folder ID | folder to file under; omit to un-file |
The id is a CAMPAIGN (the destination behind a set of links), not a single link.
Revenue
trackrev revenue list
connected payment providers and their last sync
trackrev revenue listtrackrev revenue providers
what can be connected, and the credentials each needs
trackrev revenue providersStripe is absent by design — its restricted key lives on the workspace, not here.
trackrev revenue connect
connect a provider — credentials are verified before saving
trackrev revenue connect --provider polar --field api_key=polar_oat_…| flag | meaning |
| --- | --- |
| --provider NAME | polar, lemonsqueezy, paddle, creem or dodo |
| --field K=V | credential as key=value; repeat per field |
| --sandbox | use the provider's sandbox host, where it has one |
trackrev revenue sync
pull charges now and attribute them (paid plan)
trackrev revenue sync| flag | meaning |
| --- | --- |
| --connection ID | one connection only; omit for all + Stripe |
Reports imported and attributed per provider. A provider that fails does not stop the others.
trackrev revenue disconnect <id>
disconnect a provider; imported orders are kept (asks to confirm; --yes in scripts)
trackrev revenue disconnect <id> --yesAudience
trackrev visitors list
visitors, most recently seen first (paid plan)
trackrev visitors list --email @acme.com| flag | meaning |
| --- | --- |
| --limit N | page size (default 100, max 500) |
| --all | follow the cursor to the end |
| --email TEXT | email contains this text |
trackrev visitors get <id>
one visitor (paid plan)
trackrev visitors get <id>trackrev orders list
synced purchases, newest first
trackrev orders list --status refunded| flag | meaning |
| --- | --- |
| --limit N | page size (default 100, max 500) |
| --all | follow the cursor to the end |
| --status NAME | paid or refunded |
| --email TEXT | email contains this text |
amount is blank on the free plan, where revenue figures are hidden.
trackrev export
any dataset as CSV (paid plan)
trackrev export --kind orders --days 90 --out orders.csv| flag | meaning |
| --- | --- |
| --kind NAME | channels, links, orders or visitors |
| --out PATH | write here instead of stdout |
| --days N | last N days (default 30, max 365) |
| --from ISO | explicit start, e.g. 2026-01-01; wins over --days |
| --to ISO | explicit end (defaults to now) |
Domains
trackrev domains list
branded short-link domains and their DNS status
trackrev domains listtrackrev domains add <domain>
attach a domain — prints the DNS records to add
trackrev domains add go.acme.comtrackrev domains verify <domain-or-id>
re-check DNS now; exits non-zero until it is active
trackrev domains verify go.acme.comExits 1 while still pending, so a deploy script can poll until it passes.
trackrev domains remove <domain-or-id>
detach a domain; links keep working on the default host (asks to confirm; --yes in scripts)
trackrev domains remove go.acme.com --yestrackrev retargeting list
the ad pixels fired on opted-in link clicks
trackrev retargeting list --providers| flag | meaning |
| --- | --- |
| --providers | show what can be configured instead |
trackrev retargeting set <provider>
set a provider's pixel id
trackrev retargeting set meta --id 1234567890123456| flag | meaning |
| --- | --- |
| --id ID | the pixel/tag id (required) |
The id must match that provider's shape — only validated ids are ever put into a loader snippet.
trackrev retargeting remove <provider>
remove a provider's pixel (asks to confirm; --yes in scripts)
trackrev retargeting remove meta --yesAffiliate
trackrev programs list
the workspace's affiliate programs and their terms
trackrev programs list --archived| flag | meaning |
| --- | --- |
| --archived | include archived programs |
trackrev programs get <id>
one program in full
trackrev programs get <id>trackrev programs update <id>
change commission terms, or pause and archive (paid plan)
trackrev programs update <id> --rate 0.25 --status paused| flag | meaning |
| --- | --- |
| --name TEXT | program name |
| --landing-url URL | where partner links point |
| --type TYPE | percent or flat |
| --rate N | 0-1 fraction for percent (0.25 = 25%), dollars for flat |
| --recurring N | months a commission keeps paying |
| --cookie N | attribution window in days |
| --min-payout N | minimum balance before a payout |
| --auto-approve on\|off | approve signups instantly |
| --status NAME | active, paused or archived |
Changes apply to NEW conversions; commissions already earned are untouched.
trackrev partners list
affiliates with their clicks, sales and earnings (paid plan)
trackrev partners list --status pending| flag | meaning |
| --- | --- |
| --program ID | one program only |
| --status NAME | pending, approved, rejected, banned or archived |
trackrev partners approve <partner-id>
approve a pending affiliate (paid plan)
trackrev partners approve <partner-id> --program <program-id>| flag | meaning |
| --- | --- |
| --program ID | the program id (required) |
Does NOT send the approval email the dashboard sends — a re-run would mail them again.
trackrev partners reject <partner-id>
reject an application (paid plan) (asks to confirm; --yes in scripts)
trackrev partners reject <partner-id> --program <program-id> --yes| flag | meaning |
| --- | --- |
| --program ID | the program id (required) |
trackrev partners ban <partner-id>
ban an affiliate (paid plan) (asks to confirm; --yes in scripts)
trackrev partners ban <partner-id> --program <program-id> --yes| flag | meaning |
| --- | --- |
| --program ID | the program id (required) |
trackrev partners group <partner-id>
move an affiliate into a group, or back to program terms (paid plan)
trackrev partners group <partner-id> --program <program-id> --group <group-id>| flag | meaning |
| --- | --- |
| --program ID | the program id (required) |
| --group ID | group to move them to; omit to clear |
trackrev groups list <program-id>
a program's tiers, showing the terms each one resolves to (paid plan)
trackrev groups list <program-id>Money
trackrev commissions list
the commission ledger, newest first (paid plan)
trackrev commissions list --status pending| flag | meaning |
| --- | --- |
| --limit N | rows to return (max 500) |
| --status NAME | pending, eligible, paid, refunded, void or fraud |
| --partner ID | one affiliate only |
level 1 is the affiliate who sold; 2+ is an upline earning from their network.
trackrev commissions add
record an off-platform deal by hand (paid plan)
trackrev commissions add --program <id> --partner <id> --amount 500 --earnings 100| flag | meaning |
| --- | --- |
| --program ID | program id (required) |
| --partner ID | affiliate id (required) |
| --amount N | gross sale value (required) |
| --earnings N | the affiliate's cut (required) |
| --currency CODE | defaults to usd |
| --notes TEXT | why this was entered by hand |
Earnings is not derived — a manual commission exists because the normal rate did not apply. It counts toward your monthly commission cap.
trackrev commissions void <id>
void a commission entered in error (paid plan) (asks to confirm; --yes in scripts)
trackrev commissions void <id> --yes| flag | meaning |
| --- | --- |
| --status NAME | set another status instead of void |
Refused if it is already on a payout batch — cancel the payout first.
trackrev payouts list
payout batches, with open and all-time totals (paid plan)
trackrev payouts list --status pending| flag | meaning |
| --- | --- |
| --status NAME | pending, processing, paid, failed or canceled |
| --limit N | rows to return (max 500) |
Creating a batch stays in the dashboard: it applies per-group payout floors and platform fees, and a second implementation would eventually pay someone wrong.
trackrev payouts mark-paid <id>
settle a payout sent off-platform (paid plan) (asks to confirm; --yes in scripts)
trackrev payouts mark-paid <id> --reference PAYPAL-BATCH-123 --yes| flag | meaning |
| --- | --- |
| --reference TEXT | the rail's own id (PayPal batch, Wise transfer) |
Does NOT email the affiliate — the dashboard sends that, and a re-run would send it twice.
Settings
trackrev settings notifications
every transactional email, and whether it is on
trackrev settings notificationsdefault=yes means no override is stored and the catalogue default applies.
trackrev settings notify <key>
turn one transactional email on or off
trackrev settings notify affiliate.approved --off| flag | meaning |
| --- | --- |
| --on | enable it |
| --off | disable it |
trackrev settings branding
the white-label settings affiliates see
trackrev settings brandingtrackrev settings set-branding
set the partner-facing logo and accent colour
trackrev settings set-branding --color '#e63e2e'| flag | meaning |
| --- | --- |
| --logo URL | https logo URL |
| --color HEX | hex accent, e.g. #e63e2e |
| --clear-logo | back to the TrackRev logo |
| --clear-color | back to the TrackRev colour |
The affiliate subdomain is read-only here — claiming one is a namespace reservation and belongs in one place.
Account
trackrev me
which workspace, plan, limits and key you're using
trackrev metrackrev login
save a secret key so you don't need TRACKREV_KEY
trackrev login --profile staging --api-url https://staging.example.com/api/v1| flag | meaning |
| --- | --- |
| --key lk_… | the key (prompted, hidden, when omitted) |
| --api-url URL | API base for this profile |
Stored at ~/.config/trackrev/config.json with mode 0600. TRACKREV_KEY in the environment always wins, for CI.
trackrev logout
forget a saved key
trackrev logoutGlobal flags
| flag | meaning |
| --- | --- |
| --json | print the API's JSON instead of a table |
| --profile NAME | use a saved login other than the current one |
| --yes | skip the confirmation on destructive commands |
| --version | print the version |
| --help | show help (also: trackrev --help) |
Output
The same data comes out three ways, so a command works both as something you read and as something you pipe:
trackrev channels # aligned table (a terminal)
trackrev channels > channels.tsv # tab-separated, raw values (a pipe or file)
trackrev channels --json | jq . # the API's own JSON bodyPiped output is deliberately unformatted — 2410.5, not 2,410.50, and true rather than
yes — so cut and awk see real values:
trackrev channels | cut -f1,5
trackrev links list | column -t
trackrev clicks --all --json | jq '.clicks[] | select(.country == "BD")'Warnings, errors, confirmations and the journey caption always go to stderr, never into
your pipe.
Recipes
# One link per channel for a launch, then print just the shareable URLs
trackrev links create --url https://acme.com/launch --name Launch \
--channel youtube --channel newsletter --channel twitter | cut -f3
# A link that dies after 1,000 clicks and sends latecomers to the waitlist
trackrev links create --url https://acme.com/beta --name Beta --channel newsletter \
--max-clicks 1000 --expired-url https://acme.com/waitlist
# Import a quarter's worth of links from a spreadsheet export
trackrev links bulk --file q4-links.csv
# Your five best links by revenue this month
trackrev links perf --limit 100 | tail -n +2 | sort -t$'\t' -k6,6nr | head -5
# Follow the newest click through to that visitor's whole journey
trackrev clicks --limit 1 | tail -1 | cut -f6 | xargs trackrev journey
# Nightly export
trackrev clicks --all --json > "clicks-$(date +%F).json"Exit codes
| code | meaning |
| --- | --- |
| 0 | success |
| 1 | no API key, auth failure, network error, not found, API error |
| 2 | usage error — unknown flag or command, bad value, missing argument, refused confirmation |
| 3 | plan required — the workspace needs a paid plan for this command |
Exit 3 is separate so CI can tell "you are on the wrong plan" apart from "the call broke".
Development
node --test test/*.test.js # the full suite, against a mock API — no network, no credentials
pnpm sync-docs # regenerate the command tables in this file and the docs siteThe test glob is pinned: a bare node --test also runs every other file under test/,
and mock-api.mjs is a server that never exits.
Commands are declared once in src/registry.js. The help text, the argument
parser, the tables above, the /cli marketing page and the Settings → Developers panel are all
derived from it — add a command there and a handler in src/commands/, then run pnpm sync-docs.
