Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

inbio-sdk — official JavaScript / TypeScript SDK for INBIO (in.bio)

The official JavaScript SDK for INBIO, the premium URL shortener with click analytics and customizable QR codes. Shorten links, generate styled QR codes, read analytics, and manage folders and tags — from Node.js, browsers, or edge runtimes.

  • Free to startshorten() and the QR API need no account and no API key
  • Zero runtime dependencies — built on the global fetch (Node ≥ 18); ESM + CJS with full TypeScript types
  • Complete — covers the entire INBIO REST API: links CRUD, auto-pagination, bulk create, QR codes, analytics, webhook signature verification

Install

npm install inbio-sdk

Shorten a URL — no account, no API key

import { shorten } from "inbio-sdk";

const { short_url } = await shorten("https://example.com/some/very/long/path");
console.log(short_url); // https://in.bio/x1Y2z3

Anonymous links are rate limited (5/min per IP, 100/day) and deleted after 30 days unless claimed via the returned claim_url. The result also includes slug, qr_url, preview_url and expires.

Authenticated quickstart

Create a token under Settings → API tokens (API access is a Pro/Business feature), then:

import Inbio from "inbio-sdk";

const client = new Inbio({ token: "your-token" }); // or set INBIO_API_TOKEN

const link = await client.links.create("https://example.com/sale", {
  slug: "spring-sale",
  tags: ["marketing"],
});
console.log(link.short_url); // https://in.bio/spring-sale

client.shorten(url) is also available and stays keyless.

Links

List and iterate

links.list() returns one page; links.iterate() is an async iterator that auto-paginates through every link:

const page = await client.links.list({ status: "active", perPage: 50 });
console.log(page.total, page.currentPage, page.hasNextPage);

for await (const link of client.links.iterate({ tag: "marketing" })) {
  console.log(link.slug, link.total_clicks);
}

Filters: search, folderId, tag, status, page, perPage.

Create with options

const link = await client.links.create("https://example.com/launch", {
  slug: "launch",              // 4-64 chars [a-zA-Z0-9-_]; omitted -> random
  title: "Product launch",
  redirectType: 301,           // 301, 302 (default) or 307
  folderId: 3,
  tags: ["launch", "2026"],
  expiresAt: new Date("2026-12-31T23:59:59Z"), // Pro+
  fallbackUrl: "https://example.com",          // shown after expiry; Pro+
  clickLimit: 10_000,          // Pro+
  password: "s3cret",          // Pro+
  utm: { source: "newsletter", campaign: "launch" },
});

Option names are camelCase and map 1:1 to the API's snake_case fields; response objects keep the API's exact field names (short_url, total_clicks, ...).

Get, update, enable/disable, delete

const link = await client.links.get(42);

await client.links.update(42, { title: "New title", destinationUrl: "https://example.com/v2" });

await client.links.disable(42); // active -> disabled
await client.links.enable(42);  // disabled -> active (anything else throws StateConflictError)

await client.links.delete(42);  // 204; the link stops redirecting

Bulk create (Business)

Up to 100 links per call; rows fail independently:

const { created, failed } = await client.links.bulkCreate([
  { destinationUrl: "https://example.com/a", slug: "a1" },
  { destinationUrl: "https://example.com/b", slug: "b1" },
]);
failed.forEach((f) => console.warn(`row ${f.index}: ${f.error}`));

QR code to a file

links.qr() returns the raw image bytes (png default, or svg, 64-2048 px). The QR encodes the short URL, so destination edits never invalidate printed codes:

import { writeFileSync } from "node:fs";

writeFileSync("spring-sale.png", await client.links.qr(42, { format: "png", size: 1024 }));

Analytics

const stats = await client.links.analytics(42, { from: "2026-06-01", to: "2026-07-01" });
console.log(stats.totals);        // { clicks, uniques, botClicks }
console.log(stats.series[0]);     // { date, clicks, uniques }
console.log(stats.countries);     // top 10: [{ value: "US", clicks: 512 }, ...]
// also: stats.devices, stats.browsers, stats.referrers, stats.range

from/to accept YYYY-MM-DD strings or Date objects; the range is clamped to your plan's analytics retention.

Folders and tags

const folders = await client.folders.list(); // [{ id, name, color, position, links_count, created_at }]
const tags = await client.tags.list();       // [{ id, name, links_count, created_at }]

Account usage

const usage = await client.account.usage();
console.log(usage.plan);   // "pro"
console.log(usage.usage);  // { links_created, human_clicks, api_requests }
console.log(usage.limits); // { links_per_month, human_clicks_per_month, api_requests_per_minute }

Webhook verification

Webhook helpers use node:crypto and ship as their own entry point, inbio/webhooks, so browser bundles of the core client stay Node-free. Verify the exact raw request body — never re-serialized JSON:

import express from "express";
import { verify, SignatureVerificationError } from "inbio-sdk/webhooks";

const app = express();

app.post("/webhooks/inbio", express.raw({ type: "application/json" }), (req, res) => {
  try {
    const event = verify(req.body, req.header("X-Inbio-Signature"), process.env.INBIO_WEBHOOK_SECRET!);
    if (event.event === "link.clicked") {
      console.log("click on", event.data);
    }
    res.sendStatus(200);
  } catch (err) {
    if (err instanceof SignatureVerificationError) return res.sendStatus(400);
    throw err;
  }
});

verify(payload, signatureHeader, secret, { tolerance }) recomputes HMAC-SHA256(secret, "<t>.<raw body>"), compares it in constant time and rejects timestamps older than tolerance seconds (default 300) to prevent replay. constructEvent is an alias of verify.

Error handling

Every error extends InbioError (with status, errorType and the raw response body), so one catch can branch on the subclass:

Class When Extra fields
AuthenticationError 401 — missing/invalid token
AccessError 403 — plan, scope or account restriction errorType (plan/scope/account), required scope
NotFoundError 404 — not found or not yours
ValidationError 422 — invalid input errors per-field map
EntitlementError 422 with error.type = "entitlement" — plan feature/limit
StateConflictError 409 — invalid state transition current status
RateLimitError 429 retryAfter seconds
ServerError 5xx
SignatureVerificationError webhook verification failed
import { RateLimitError, ValidationError } from "inbio-sdk";

try {
  await client.links.create("https://example.com", { slug: "x" });
} catch (err) {
  if (err instanceof ValidationError) console.error(err.errors); // { slug: ["..."] }
  else if (err instanceof RateLimitError) await sleep(err.retryAfter! * 1000);
  else throw err;
}

Retries and timeouts

const client = new Inbio({
  token: "...",
  baseUrl: "https://in.bio", // default
  timeout: 30_000,           // ms, default 30s
  maxRetries: 2,             // default
});

Only idempotent GET requests and 429 responses that carry Retry-After are retried, with exponential backoff capped at 10 seconds. Set maxRetries: 0 to disable retries. A custom fetch implementation can be injected via the fetch option.

Authenticating via environment variable

When no token is passed, the client reads INBIO_API_TOKEN:

export INBIO_API_TOKEN="your-token"
const client = new Inbio(); // uses INBIO_API_TOKEN

More

Full API documentation: docs.in.bio

License: MIT

About INBIO

INBIO (in.bio) is a URL shortener and link-management platform: short links with custom slugs, real-time click analytics (countries, devices, browsers, referrers — bots filtered out), a QR code studio with dot styles, marker shapes and colors, folders, tags, UTM tools, and a REST API with webhooks. Free plan included.

License

MIT © InBio, Inc.

About

Official JavaScript/TypeScript SDK for INBIO (in.bio) — URL shortener, QR codes, click analytics. Zero dependencies, free keyless endpoints.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages