@veezee/sdk
v0.7.0
Published
Typed REST client and CLI for the Veezee API: LinkedIn, Reddit, and X data for AI agents, metered in credits with per-call receipts.
Maintainers
Readme
@veezee/sdk
Typed REST client and CLI for the Veezee API: LinkedIn, Reddit, and X (formerly Twitter) data for AI agents, metered in credits with per-call receipts. Zero runtime dependencies, built on Node 20's global fetch. Current version: 0.6.1.
The key spends; signing in adds a management token for key management. vz init mints a key with no signup: that key alone is enough to call every data endpoint. vz login is a separate, optional step that proves an email and stores a vzm_ management token for listing, creating, revoking, and rotating keys, and for setting a low-balance alert. The first sign-in that confirms an email claimed on a trial key also adds a one-time 10,000-credit grant (once per email, once per key), spent after the free daily budget.
Install
npm install @veezee/sdkNode 20+ required.
Quickstart
Start with one command:
npx @veezee/sdk initvz init (same command, shorter once installed) POSTs to /v1/keys/mint with no signup and no card, and writes the returned key to ~/.veezee/config (mode 0600). It never prints the raw key, only a masked hint: the key lives in a file and an env var, not in your terminal history or an agent's chat transcript. After that, every vz command and every SDK call in the same environment just works:
import { VeezeeClient } from "@veezee/sdk";
const client = new VeezeeClient();
await client.mint(); // reuses the key from `vz init`, or mints one the first time
const profile = await client.linkedin.getProfile({ identifier: "williamhgates" });
console.log(profile.common.full_name, profile.common.headline);mint() is idempotent: it reuses VEEZEE_API_KEY or ~/.veezee/config if either already has a key, and only mints a new one when neither does. The trial key itself carries no balance; the free tier is a per-IP, per-day allowance (200 credits/day, recent data, first page only) checked against the calling IP, not the key. Claiming the key adds a free balance on top: attach an email, confirm it by signing in at veezee.io/login, and the account gets a one-time 10,000-credit grant (once per email, once per key) that spends at the same trial limits after each day's free allowance is used. Outgrow both and a call fails with TRIAL_CAP_EXCEEDED carrying upgrade_url: buy credits at veezee.io/upgrade (no login required) and they land on the same key you already have. Nothing to reconfigure: same key, same config file, same code, now with a real balance.
You can skip vz init and pass a key directly instead:
const client = new VeezeeClient({ apiKey: process.env.VEEZEE_API_KEY });Auth
Every call sends Authorization: Bearer <apiKey>. A call with no resolvable key throws VeezeeError with code: "KEY_REQUIRED" and a mint_url: POST there (no auth, no body) to mint a key, or just run vz init or call client.mint(), then retry. get_usage always needs a key too.
The API surface
Platform tools live under client.linkedin, client.reddit, and client.x; account/billing tools (platform-independent) are top-level:
await client.linkedin.getProfile({ identifier: "williamhgates", sections: ["experience", "education"] });
// -> PersonEnvelope { common: { full_name, headline, experience, ... } }
await client.linkedin.searchPeople({ keywords: "CTO", current_company: "anthropic", limit: 20 });
// -> PeopleSearchEnvelope { common: { results, cursor, total_matches } }
await client.linkedin.getCompany({ identifier: "microsoft" });
// -> CompanyEnvelope { common: { name, industry, employee_count, ... } }
await client.linkedin.getPosts({ identifier: "microsoft" });
// -> PostsEnvelope { common: { results, cursor, author_type } }
await client.reddit.search({ query: "notion alternative", type: "comments" });
// -> RedditSearchEnvelope { common: { type: "comments", comments, cursor, returned_count } }
await client.reddit.getSubredditPosts({ subreddit_name: "selfhosted", sort: "new" });
// -> RedditPostsEnvelope { common: { results, cursor, returned_count } }
await client.x.getProfile({ identifier: "nasa" });
// -> XProfileEnvelope { common: { screen_name, name, followers, description, ... } }
await client.x.search({ query: "veezee api", type: "recent" });
// -> XSearchEnvelope { common: { type: "recent", tweets, cursor, returned_count } }
await client.resolveUrl({ url: "https://www.linkedin.com/in/williamhgates" });
// -> UrlResolutionEnvelope { common: { type: "person", id, handle, canonical_url } }
// client.reddit.resolveUrl and client.x.resolveUrl work the same way for their own URLs.
await client.getUsage();
// -> UsageEnvelope { common: { plan, platforms, balance_remaining, recent_receipts, upgrade_url, manage_url } }
await client.checkout({ pack: "flex" | "production" });
// -> { checkout_url }. Needs no key: paying with none mints a fresh account and its key.Every response is an envelope: { entity, platform, canonical_url, data_as_of, schema_version, common, platform_fields, freshness, usage }. usage.credits_charged and usage.receipt_id are on every call; freshness tells you how old the data is. Free-tier trial-key calls also carry usage.free_tier_hint, explaining what budget the call drew from and that paying upgrades this same key.
Error handling
Non-2xx responses throw a typed VeezeeError (extends Error) carrying the API's problem+json body:
import { VeezeeError } from "@veezee/sdk";
try {
await client.linkedin.getProfile({ identifier: "someone" });
} catch (err) {
if (err instanceof VeezeeError) {
console.error(err.code, err.message); // message is a next-turn instruction
if (err.is_retriable) {
// safe to retry as-is; the client already retried transient errors internally
}
}
}The client retries RATE_LIMITED, CONCURRENCY_LIMIT, UPSTREAM_UNAVAILABLE, INTERNAL, and network/timeout failures up to 3 attempts with exponential backoff, honoring retry_after_seconds when present. Every metered call gets one Idempotency-Key (UUID), reused across its own retries: retries never double-charge. Terminal errors (bad input, insufficient credits, auth) throw immediately without retrying.
Errors a payment can fix also carry upgrade_url (give it to your human), credits_required (on INSUFFICIENT_CREDITS: the credits the call needed), and offer (typed OfferV1): pack prices, checkout_url (the same link as upgrade_url), and resume, which says exactly how to retry after payment.
A call made with no resolvable key throws code: "KEY_REQUIRED" carrying mint_url: run vz init or call client.mint(), then retry. A management call (listKeys, createKey, revokeKey, rotateKey, setBalanceAlert) made with no resolvable management token throws code: "AUTH_REQUIRED": run vz login, then retry.
CLI
The package ships a veezee bin, with vz as a short alias for the same binary (noun-verb, mirrors the methods above):
veezee init [--force]
veezee linkedin profile get <identifier> [--sections a,b] [--realtime]
veezee linkedin search [--keywords x] [--title x] [--company x] [--past-company x] [--school x] [--first-name x] [--last-name x] [--limit n] [--cursor x] [--freshness x]
veezee linkedin company get <identifier>
veezee linkedin posts get <identifier>
veezee reddit search <query> [--type posts|comments|subreddits|users] [--sort x] [--range x]
veezee reddit subreddit <name> [--include-settings]
veezee reddit subreddit-posts <name> [--sort x] [--range x] [--include-promoted]
veezee reddit user <username> [--sections comments,posts,subreddits]
veezee reddit post <id1,id2,...> [--detail concise|full] [--comment-id x]
veezee x search <query> [--type recent|popular|people]
veezee x profile <identifier> [--by screen_name|id]
veezee x tweets <identifier> [--mode posts|posts_and_replies|highlights] [--no-retweets]
veezee x tweet <tweet_id>
veezee resolve-url <url>
veezee usage
veezee login [--device] [--no-browser]
veezee logout
veezee keys list [--account <id>]
veezee keys create [--label <name>] [--account <id>]
veezee keys revoke <key_id> [--account <id>]
veezee keys rotate <key_id> [--account <id>]
veezee alert <threshold|off> [--account <id>]Run vz init first (once): it mints a free trial key and writes it to ~/.veezee/config, masked hint only ever printed to stdout, never the raw key. vz init --json prints the same result as JSON (still no raw key) for scripting. vz init --force re-mints and overwrites the stored key.
For example, vz reddit search "notion alternative" --type comments or vz x profile nasa. Every platform namespace also has its own resolve-url: vz reddit resolve-url <url>, vz x resolve-url <url>.
Add --json on any command for machine-readable output (the raw envelope; errors print the full error shape as JSON too). Every data command takes --max-credits <n> to cap spend on that call: if the quote exceeds the cap the API returns a typed error and charges nothing. Unknown flags are rejected with the valid-flag list (exit 2), never silently ignored. Auth resolves in order: --key <apiKey>, then the VEEZEE_API_KEY env var, then ~/.veezee/config from vz init. --base-url overrides the API host. Exit codes: 0 success, 1 failure, 2 usage or invalid-input error, 4 auth required. vz --version prints the SDK version. Run vz --help (or no args) for examples.
vz login is a separate, optional step: it proves an email (browser callback by default, or --device for a headless code-entry flow) and stores a vzm_ management token in ~/.veezee/config, masked hint only ever printed to stdout. vz keys list|create|revoke|rotate and vz alert <threshold|off> use that token, resolved in order: --key <vzm_...>, then VEEZEE_MGMT_TOKEN, then the stored token. vz keys create and vz keys rotate print the raw api_key to stdout once (the "shown once" warning goes to stderr): store it immediately, it cannot be retrieved again. vz logout clears the locally stored token; it does not revoke it server-side.
Agent skills
The package ships eight installable SKILL.md packs under skills/: prospect-enrich, candidate-sourcing, company-enrichment, account-research, post-voice-research, reddit-monitoring, launch-sentiment-sweep, and outreach-list-builder. After npm install @veezee/sdk, add them to your agent with the skills CLI local-path syntax:
npx skills add ./node_modules/@veezee/sdk/skillsThe same packs also install straight from GitHub (veezeehq/veezee-skills): npx skills add veezeehq/veezee-skills. Pack index and docs: veezee.io/docs/skills.
Development
npm install
npm run build # tsc -> dist/
npm test # compiles test/ and runs node --test
npm pack --dry-runCI should run exactly this sequence (npm ci && npm run build && npm test) on Node 20+; this package has no separate lint step and no network calls in its test suite.
Docs
Full API reference: https://veezee.io/docs. SDK page: https://veezee.io/docs/sdk. CLI page: https://veezee.io/docs/cli. Hosted MCP servers (same tools, no install): https://veezee.io/docs/clients, listed in the MCP Registry as io.veezee/linkedin, io.veezee/reddit, and io.veezee/x-twitter.
