TypeScript SDK
A typed client with a method for every tool, typed errors and safe retries.
@seolyticize/sdk is a typed TypeScript client for the API. Every tool is a method, and its input types are generated from the registry that validates the calls, so the SDK cannot describe a tool differently from how the server runs it.
It has no runtime dependencies. It needs fetch, which Node 18+, Deno, Bun and browsers all have.
Installing
The SDK is not yet published to npm. Build it from the Seolyticize repository with pnpm --filter @seolyticize/sdk build, which writes ESM and type declarations to packages/sdk/dist/, and depend on that folder.
Quick start
import { createClient } from "@seolyticize/sdk";
const seo = createClient({ apiKey: process.env.SEOLYTICIZE_API_KEY! });
// Typed: the input is checked against the tool's schema at compile time.
const { clients } = await seo.tools.list_clients<{
clients: Array<{ id: string; name: string }>;
}>();
const summary = await seo.tools.get_rank_summary({
clientId: clients[0]!.id,
trackerId: "<trackerId>",
});Results are typed unknown unless you pass a type argument. The API publishes input schemas, not output schemas, and a guessed output type would be a promise nothing keeps.
Tools newer than your SDK
Tools are added to the platform continuously. Call one your version has never heard of by name:
const result = await seo.call("some_new_tool", { clientId: "<clientId>" });What may this key do?
const { caller, tools } = await seo.listTools();
// caller.role, caller.clientIds ("all", or the one client), and every tool
// the key may call with its JSON Schema.Options
createClient({
apiKey: "sk_seo_…",
baseUrl: "https://seolyticize.com", // the default
maxRetries: 2, // retries after the first attempt
timeoutMs: 30_000, // per attempt
maxRetryAfterSeconds: 60, // longest Retry-After the client will wait through
fetch: customFetch, // optional
});Pass { signal } as the last argument of any call to cancel it.
Errors
Every failure is a SeolyticizeError with status, tool and the server's own sentence as message. Branch on the subclass:
Class | Status | Meaning |
|---|---|---|
| 400 | Input did not match the tool's schema; the message names fields |
| 401 | Missing, unknown or revoked key |
| 403 | The key's role or client scope does not allow this |
| 404 | No tool by that name |
| 422 | Well-formed, but declined — duplicate name, plan limit, spending limit |
| 429 | Window used up; |
| 5xx | Could not be completed; the server never says why |
| — | No response at all (DNS, TLS, timeout) |
import { RefusedError, RateLimitError } from "@seolyticize/sdk";
try {
await seo.call("create_client", { name: "Acme", domain: "acme.com" });
} catch (error) {
if (error instanceof RefusedError) console.log("Declined:", error.message);
else if (error instanceof RateLimitError) console.log("Wait", error.retryAfterSeconds);
else throw error;
}Retries
429 is always retried, waiting as long as
Retry-Afterasks (up tomaxRetryAfterSeconds; a longer wait throwsRateLimitErrorstraight away). Nothing ran, so repeating is safe.5xx and network failures are retried with exponential backoff and jitter only for read-only tools. A write that failed with a
500may already have happened — repeatingcreate_clientcould create two clients — so writes surface the error instead. A tool the SDK has never heard of is treated as a write.400, 401, 403, 404 and 422 are never retried; they would fail the same way.
client.lastRateLimit holds the X-RateLimit-* headers from the latest response.