@codasignal/routo
v0.2.0
Published
TypeScript SDK for the Routo link management API
Maintainers
Readme
@codasignal/routo
TypeScript SDK for the Routo link management API.
Install
npm install @codasignal/routo
# or
bun add @codasignal/routoRequires Node ≥20, or any modern browser/Cloudflare Workers/Bun/Deno runtime that ships fetch and AbortSignal.any.
Quickstart
import { Routo } from "@codasignal/routo";
const routo = new Routo(process.env.ROUTO_API_KEY!);
const link = await routo.links.create({
url: "https://example.com/products/42",
projectId: "proj_abc",
path: "summer-sale",
socialMedia: { title: "Summer Sale!", description: "50% off" },
});
console.log(link.id, link.url);In Node, you can call new Routo() with no arguments and the SDK reads ROUTO_API_KEY from process.env.
Resources
| Resource | Methods |
| ------------------ | ---------------------------------------------------------------------------------------------------- |
| links | create, createDynalinks, list (Page), update, delete |
| links.variants | create, list, update, delete |
| analytics | get, aggregate, aggregateUnique, breakdown, variantPerformance |
| console | get |
| customDomains | create, list (Page), get, update, delete, verify |
| projectApps | create, list (Page), listByProject, update, delete |
| projects | list, get |
| organizations | list, current |
Pagination
list() returns a Page<T> that is both an AsyncIterable<T> and a manual paginator:
// Iterate everything
for await (const link of routo.links.list({ projectId: "p1" })) {
process(link);
}
// Manual paging
let page = routo.links.list({ projectId: "p1", limit: 50 });
let current = await page.next();
while (current) {
process(current.data);
current = current.hasMore ? await current.next() : null;
}Errors
import { RoutoValidationError, RoutoRateLimitError } from "@codasignal/routo";
try {
await routo.links.create({ url: "", projectId: "", path: "" });
} catch (err) {
if (err instanceof RoutoValidationError) {
for (const fe of err.fieldErrors) console.error(`${fe.path}: ${fe.message}`);
} else if (err instanceof RoutoRateLimitError) {
console.error(`rate limited, retry in ${err.retryAfterMs}ms`);
} else {
throw err;
}
}| Class | Status | Notes |
| ------------------------ | ---------- | -------------------------------- |
| RoutoValidationError | 400 | exposes fieldErrors[] |
| RoutoAuthError | 401, 403 | code is unauthorized/forbidden |
| RoutoNotFoundError | 404 | |
| RoutoRateLimitError | 429 | exposes retryAfterMs |
| RoutoServerError | 5xx | retryable |
| RoutoAbortError | n/a | from caller-provided signal |
| RoutoTimeoutError | n/a | from timeoutMs |
| RoutoNetworkError | n/a | wraps the underlying error in cause |
Retries and Idempotency
Network failures, 408, 429, and 5xx responses are retried automatically up to maxRetries (default 2). For POST / PATCH requests, the SDK generates an Idempotency-Key on first attempt and reuses it across retries. You can pass your own:
await routo.links.create(params, { idempotencyKey: "create-link-42" });AbortSignal & Timeouts
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5_000);
await routo.links.list({ projectId: "p1" }, { signal: ctrl.signal });
// per-call timeout
await routo.analytics.get(range, { timeoutMs: 5_000 });Edge runtime notes
The SDK is pure-fetch and ships a single ESM+CJS build. It runs unmodified in:
- Cloudflare Workers (use
wrangler dev) - Vercel Edge Functions
- Bun / Deno
- React Native (Hermes ≥0.74)
No Node-specific imports.
Examples
See examples/ in the repository:
node-quickstart.tscf-worker.tsnext-route-handler.tspagination.tsabort-and-timeout.tsretry-and-idempotency.ts
License
MIT
