API & automation
TypeScript SDK
@ranksify/sdk is a typed client for the same REST API documented in the API reference: one method per endpoint, one error class, and a retry policy that stops. Zero dependencies, Node 18 or newer.
Release status
@ranksify/sdk is not published on npm yet, so it cannot be installed today. The API below is the prerelease reference, not a working install command. The REST API it wraps is live — call it directly with curl or your own client using the API reference in the meantime.Authentication
Every call carries an rk_ key with the read scope, minted under Settings → API keys. Keys belong to a workspace, and API access is a paid-plan feature — see API keys.
import { Ranksify } from "@ranksify/sdk";
const ranksify = new Ranksify({ apiKey: process.env.RANKSIFY_API_KEY! });| Option | Default |
|---|---|
baseUrl | https://app.ranksify.ai |
maxRetries — extra attempts after a 429 | 2 |
maxRetryDelayMs — ceiling on one wait | 60,000 |
timeoutMs — per request | 30,000 |
fetchImpl / sleepImpl — injection seams for tests | global fetch, setTimeout |
One method per endpoint
Credits and spend
const usage = await ranksify.getUsage();
// { plan, credits: { available, allowance, consumed, enforced, … },
// pricing: { usdPerCredit, perEngineCheck, perAction }, dailySpend }The price table ships with the balance so an automated caller can price its next scan before running it. credits.enforced says out loud whether running out currently stops anything.
Visibility leaderboard
const { rows } = await ranksify.getVisibilityRanking(projectId, {
from: "2026-08-01",
to: "2026-08-15",
});Your brand plus every tracked competitor, re-aggregated over the range — each row carries its sample size and confidence interval, never a bare percentage.
Visibility over time
const { series } = await ranksify.getVisibilityTimeseries(projectId, {
subjectType: "competitor",
subjectId: competitorId,
});One point per day, for your brand by default. Days with no data are absent rather than reported as zero.
Cited sources
const { domains } = await ranksify.getCitations(projectId);Change events
const { events } = await ranksify.getChanges(projectId, { from: "2026-08-01" });The same day-over-day events behind the alert feed. Only from and to narrow this read. For push delivery rather than polling, see webhooks.
Bot registry
const { bots } = await ranksify.getBots();The public AI-crawler registry — unauthenticated and identical for everyone, which makes it the cheapest call to test connectivity with.
The reads that take a projectId share one filter object: from, to, engineId, topicId, location and branded. Omitted dates fall back to the server’s default window, and the window it actually used comes back on every response as filters.
Errors and retries
Any non-2xx response throws a RanksifyError carrying code, status, retryAfter and details. code is the API’s own error code — unauthorized, forbidden, not_found, rate_limited, quota_exceeded, bad_request — or http_error when something other than the API answered, such as a proxy or a gateway.
A request that never got an answer at all throws the same class: timeout when it outlived timeoutMs, network_error otherwise, both with status: 0 and the underlying DOMException or TypeError kept as cause. So err instanceof RanksifyError catches every failure mode — a timeout cannot slip past it unhandled.
import { RanksifyError } from "@ranksify/sdk";
try {
await ranksify.getUsage();
} catch (err) {
if (err instanceof RanksifyError && err.code === "rate_limited") {
console.warn(`still limited; try again in ${err.retryAfter}s`);
}
}Rate limits are per key and per minute. A 429 carrying Retry-After is retried automatically, waiting the advertised delay, up to maxRetries extra attempts. That cap is deliberate: a client that retries a rate limit forever turns the limit into its own outage. Once the attempts are spent the error is thrown with retryAfter intact, so you decide what happens next.
Nothing else is retried. A 5xx is not repeated automatically, because whether a failed read is safe to retry is your call rather than the client’s.