TypeScript SDK

Beta
⚠️

Work in Progress

This SDK is currently in beta and under active development. It is not recommended for production use as APIs and functionality may change without notice.

Current Status:

  • Designed for server-side use in Node.js and similar TypeScript runtimes
  • Links, analytics, domains, members, teams, and bootstrap info are documented below
  • Examples on this page are aligned to the current @fynlink/sdk API shape
  • Local example scripts remain the fastest way to validate behavior against your own credentials

For the latest updates and changes, please check the GitHub repository.

Full operation-by-operation SDK response samples are documented in the fyn-ts README and examples README.

Installation

Install the SDK using your preferred package manager:

npm
bash
npm install @fynlink/sdk
yarn
bash
yarn add @fynlink/sdk
pnpm
bash
pnpm add @fynlink/sdk

SDK Dependencies

The SDK installs these external dependencies automatically:

  • axios - HTTP client for API requests
  • nanoid - cryptographically secure slug generation used in @fynlink/core
  • libsodium-wrappers-sumo - cryptographic operations (via @fynlink/crypto)

Note: npm, yarn, and pnpm resolve and install these dependencies automatically. No manual installation is required.

Environment Setup

The local examples in fyn-ts/examples read credentials from examples/.env.

bash
cp examples/.env.example examples/.env

FYN_API_TOKEN=...
FYN_SECRET_KEY=...
FYN_API_URL=https://api.fyn.link/v1
FYN_DEBUG=false
FYN_CACHE_ENABLED=true
FYN_CACHE_TTL_MS=300000
FYN_CACHE_STORAGE=disk
FYN_CACHE_PATH=.fynlink-sdk-cache.json
FYN_CACHE_WARN_ON_DISK=true

Note: FYN_SECRET_KEY is required for encrypted link reads, lists, and updates.

Cache security: Disk cache stores raw API responses. Link payload fields remain encrypted envelopes, but metadata and headers are cached as returned. If both cache data and FYN_SECRET_KEY are exposed, encrypted link fields may be decrypted.

Quick Start

Get started with the Fynlink SDK by following these examples:

Initialize Client

⚠️ Security Warning

This SDK is designed for server-side use only. Never expose your token or secret in client-side code or public repositories. Always store these credentials in secure environment variables on your server.

Quick Start
typescript
import { Fyn } from '@fynlink/sdk';

const fyn = new Fyn({
  token: process.env.FYN_API_TOKEN!,
  secret: process.env.FYN_SECRET_KEY
});

const link = await fyn.links.create({
  domain: 'go.example.com',
  target: 'https://example.com',
  title: 'Hello from FynLink'
});

console.log(link.shortUrl);
Advanced Configuration
typescript
import { AnalyticsMode, Fyn } from '@fynlink/sdk';

const fyn = new Fyn({
  token: process.env.FYN_API_TOKEN!,
  secret: process.env.FYN_SECRET_KEY,
  baseUrl: process.env.FYN_API_URL,
  timeoutMs: 10_000,
  debug: false,
  retry: {
    maxAttempts: 2,
    initialDelayMs: 1_000
  },
  cache: {
    enabled: true,
    ttlMs: 300_000,
    storage: 'disk',
    path: '.fynlink-sdk-cache.json',
    warnOnDisk: true
  },
  defaults: {
    links: {
      create: {
        domain: 'go.example.com',
        safeMode: true,
        analytics: AnalyticsMode.Full,
        privateLink: false,
        expirySeconds: 86_400
      }
    }
  }
});

Configuration Reference

Required Options
token

API token used for authenticated requests.

secret

Required for encrypted link reads, lists, and updates.

Optional Options
baseUrl

Custom API base URL. Default: https://api.fyn.link/v1

timeoutMs

Request timeout in milliseconds. Default: 10000

debug

Enables HTTP and cache diagnostics. Default: false

Retry Options
retry.maxAttempts

Maximum retry attempts. Default: 2

retry.initialDelayMs

Initial retry delay in milliseconds. Default: 1000

Cache Options
cache.enabled

Enable or disable caching. Default: true

cache.ttlMs

Cache TTL in milliseconds. Default: 300000

cache.storage

Cache backend. Default: 'disk'

cache.path

Disk cache file path. Default: .fynlink-sdk-cache.json

cache.warnOnDisk

Emit a disk-cache security warning. Default: true

Disk cache note: Cached link payload fields are encrypted envelopes, but metadata/headers are stored as returned. If both cache data and your secret key are exposed, encrypted link fields may be decrypted.

Create Defaults

These are SDK-level defaults. Team defaults from Fynlink settings are not auto-applied during SDK link creation.

defaults.links.create.domain

Default domain for fyn.links.create(...) when per-request domain is omitted.

defaults.links.create.safeMode

Default safe-mode value for link creation.

defaults.links.create.analytics

Default analytics mode for link creation.

defaults.links.create.privateLink

Default privacy mode for link creation.

defaults.links.create.expirySeconds

Default TTL in seconds for link creation.

Domains

List Domains

typescript
const domains = await fyn.domains.list({
  page: 1,
  limit: 10
});
FieldTypeDescription
pagenumberPage number.
limitnumberPage size.
Possible SDK Response
json
{
  "data": [
    {
      "id": "domain_123",
      "domain": "go.example.com",
      "description": "Primary branded short domain",
      "isSubdomain": true,
      "userId": "user_99",
      "teams": ["team_7"],
      "status": "pending",
      "notFoundUrl": "https://example.com/404",
      "rootRedirect": "https://example.com",
      "gpc": false,
      "createdAt": "2026-03-20T09:00:00.000000Z",
      "updatedAt": "2026-03-20T09:00:00.000000Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "from": 1,
    "to": 1,
    "per_page": 10,
    "last_page": 1,
    "total": 1
  }
}

Get Domain

typescript
const domain = await fyn.domains.get('domain_123');

console.log(domain.domain);
console.log(domain.status);
json
{
  "id": "domain_123",
  "domain": "go.example.com",
  "description": "Primary branded short domain",
  "isSubdomain": true,
  "userId": "user_99",
  "teams": ["team_7"],
  "status": "pending",
  "notFoundUrl": "https://example.com/404",
  "rootRedirect": "https://example.com",
  "gpc": false,
  "createdAt": "2026-03-20T09:00:00.000000Z",
  "updatedAt": "2026-03-20T09:00:00.000000Z"
}

Create Domain

typescript
const created = await fyn.domains.create({
  domain: 'go.example.com',
  description: 'Primary branded short domain',
  notFoundUrl: 'https://example.com/404',
  rootRedirect: 'https://example.com',
  gpc: false
});
FieldTypeRequiredDescription
domainstringYes on createDomain name to register.
descriptionstringNoOptional description.
notFoundUrlstringNoURL used for unmatched slugs.
rootRedirectstringNoURL used for the bare domain root.
gpcbooleanNoGlobal privacy control setting.

Team assignment: You do not pass a team when creating a domain. The backend automatically assigns the domain to the current team associated with the token used for the request.

json
{
  "id": "domain_123",
  "domain": "go.example.com",
  "description": "Primary branded short domain",
  "isSubdomain": true,
  "userId": "user_99",
  "teams": ["team_7"],
  "status": "pending",
  "notFoundUrl": "https://example.com/404",
  "rootRedirect": "https://example.com",
  "gpc": false,
  "createdAt": "2026-03-20T09:00:00.000000Z",
  "updatedAt": "2026-03-20T09:00:00.000000Z"
}

Update Domain

typescript
const updated = await fyn.domains.update({
  id: 'domain_123',
  description: 'Updated from SDK docs example',
  notFoundUrl: 'https://example.com/not-found',
  rootRedirect: 'https://example.com/home',
  gpc: true
});
FieldTypeRequiredDescription
idstringYesDomain ID to update.
descriptionstringNoUpdated description.
notFoundUrlstringNoUpdated not found URL.
rootRedirectstringNoUpdated root redirect URL.
gpcbooleanNoUpdated global privacy control setting.

Team ownership: Domain team association is not changed through update; this PATCH only updates mutable domain settings.

json
{
  "id": "domain_123",
  "domain": "go.example.com",
  "description": "Updated from SDK docs example",
  "isSubdomain": true,
  "userId": "user_99",
  "teams": ["team_7"],
  "status": "active",
  "notFoundUrl": "https://example.com/not-found",
  "rootRedirect": "https://example.com/home",
  "gpc": true,
  "createdAt": "2026-03-20T09:00:00.000000Z",
  "updatedAt": "2026-03-21T08:30:00.000000Z"
}

Verify Domain

typescript
const verification = await fyn.domains.verify('domain_123');

console.log(verification.verified);
console.log(verification.cname);
json
{
  "id": "domain_123",
  "domain": "go.example.com",
  "status": "active",
  "verified": true,
  "cname": "cname.fyn.link"
}

Delete Domain

typescript
await fyn.domains.remove('domain_123');
console.log('Removed domain_123');

Possible SDK output: the promise resolves with no return value (void).

Team

List Members

typescript
const members = await fyn.members.list({
  includeOwner: true
});
json
{
  "data": [
    {
      "id": "user_123",
      "name": "Alex Doe",
      "email": "member@example.com",
      "role": "editor",
      "displayPicture": "https://cdn.example.com/avatar/alex.png",
      "teamId": "team_7",
      "createdAt": "2026-03-01T11:00:00.000000Z",
      "updatedAt": "2026-03-15T09:00:00.000000Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "from": 1,
    "to": 1,
    "per_page": 15,
    "last_page": 1,
    "total": 1
  }
}

Invite Member

typescript
await fyn.members.invite({
  email: 'member@example.com',
  role: 'editor'
});

Security note: Only already-registered users can be invited, by design, to preserve the platform’s security architecture.

Possible SDK output: the promise resolves with no return value (void).

Get Member

typescript
const member = await fyn.members.get('member_123');

console.log(member.email);
console.log(member.role);
json
{
  "id": "user_123",
  "name": "Alex Doe",
  "email": "member@example.com",
  "role": "editor",
  "displayPicture": "https://cdn.example.com/avatar/alex.png",
  "teamId": "team_7",
  "createdAt": "2026-03-01T11:00:00.000000Z",
  "updatedAt": "2026-03-15T09:00:00.000000Z"
}

Update Permissions

typescript
const updatedMember = await fyn.members.update({
  id: 'member_123',
  role: 'admin'
});

const currentTeam = await fyn.teams.get();

const updatedTeam = await fyn.teams.update({
  name: 'Growth Team'
});
json
// fyn.members.update(...)
{
  "id": "user_123",
  "name": "Alex Doe",
  "email": "member@example.com",
  "role": "admin",
  "displayPicture": "https://cdn.example.com/avatar/alex.png",
  "teamId": "team_7",
  "createdAt": "2026-03-01T11:00:00.000000Z",
  "updatedAt": "2026-03-21T10:00:00.000000Z"
}

// fyn.teams.get() and fyn.teams.update(...)
{
  "id": "team_7",
  "type": "team",
  "attributes": {
    "name": "Growth Team",
    "privateLinks": false,
    "safeMode": true,
    "tracking": "full"
  },
  "owner": {
    "id": "user_99",
    "type": "user",
    "name": "Owner Name"
  },
  "defaultDomain": {
    "id": "domain_123",
    "value": "go.example.com"
  },
  "createdAt": "2026-01-01T00:00:00+00:00",
  "updatedAt": "2026-03-21T10:15:00+00:00"
}

The current SDK exposes member role updates via fyn.members.update() and team name updates via fyn.teams.update().

Remove Member

typescript
await fyn.members.remove('member_123');
console.log('Removed member_123');

Possible SDK output: the promise resolves with no return value (void).

Analytics

Breakdowns

typescript
const browsers = await fyn.analytics.browsers('link_123', {
  granularity: 'day'
});

const devices = await fyn.analytics.devices('link_123', {
  granularity: 'day'
});

const countries = await fyn.analytics.countries('link_123', {
  granularity: 'day'
});

const referrers = await fyn.analytics.referrers('link_123', {
  granularity: 'day'
});
json
{
  "browsers": {
    "data": [
      { "browser": "Chrome", "clicks": 58, "percentage": 48.33 }
    ],
    "meta": { "totalClicks": 120, "count": 1 }
  },
  "devices": {
    "data": [
      { "device": "desktop", "clicks": 73, "percentage": 60.83 }
    ],
    "meta": { "totalClicks": 120, "count": 1 }
  },
  "countries": {
    "data": [
      { "country": "United States", "countryCode": "US", "clicks": 64, "percentage": 53.33 }
    ],
    "meta": { "totalClicks": 120, "count": 1, "countriesCount": 1 }
  },
  "referrers": {
    "data": [
      { "referrer": "google.com", "clicks": 66, "percentage": 55.0 }
    ],
    "meta": { "totalClicks": 120, "count": 1, "sourcesCount": 1 }
  }
}

Metrics Query

typescript
const stats = await fyn.analytics.timeseries('link_123', {
  start: '2026-03-01T00:00:00.000Z',
  end: '2026-03-19T23:59:59.999Z',
  granularity: 'day',
  country: ['US', 'IN'],
  browser: ['Chrome', 'Safari'],
  device: ['desktop', 'mobile'],
  referrer: ['google.com']
});
FieldTypeDescription
startstringStart timestamp.
endstringEnd timestamp.
granularity'minute' | 'hour' | 'day' | 'week' | 'month'Aggregation granularity.
countrystring[]Country filters.
browserstring[]Browser filters.
devicestring[]Device filters.
referrerstring[]Referrer filters.
json
{
  "data": [
    {
      "date": "2026-03-01T00:00:00+00:00",
      "clicks": 14,
      "uniqueClicks": 12
    }
  ],
  "meta": {
    "totalClicks": 120,
    "totalUniqueClicks": 95,
    "count": 10
  }
}

Bootstrap & Commands

typescript
const info = await fyn.info.get();
const freshInfo = await fyn.info.get(true);

console.log(info.team.defaultDomain.value);
console.log(freshInfo.permissions);
bash
npx tsx examples/analytics/clicks.ts <linkId>
npx tsx examples/analytics/timeseries.ts <linkId>
npx tsx examples/analytics/browsers.ts <linkId>
npx tsx examples/analytics/devices.ts <linkId>
npx tsx examples/analytics/countries.ts <linkId>
npx tsx examples/analytics/referrers.ts <linkId>
npx tsx examples/info/get.ts
json
{
  "id": "token_abc",
  "user": { "id": "user_99", "name": "Owner Name" },
  "team": {
    "id": "team_7",
    "name": "Growth Team",
    "publicKey": "base64-public-key",
    "disableAnalytics": false,
    "safeMode": true,
    "privateLink": false,
    "defaultDomain": { "id": "domain_123", "value": "go.example.com" },
    "availableDomains": [
      { "id": "domain_123", "value": "go.example.com" },
      { "id": "domain_124", "value": "links.example.com" }
    ]
  },
  "permissions": ["link.read", "link.create", "member.read"],
  "tokenName": "SDK Token"
}

Features

Explore the practical capabilities of the TypeScript SDK:

Automatic Retries

Built-in retry handling for transient failures using the configured retry policy.

typescript
const fyn = new Fyn({
  token: process.env.FYN_API_TOKEN!,
  retry: {
    maxAttempts: 2,
    initialDelayMs: 1000
  }
});

const link = await fyn.links.get('link_123');

Caching Strategy

ETag-aware caching for GET requests, with memory or disk-backed storage and explicit disk-cache security warnings.

typescript
const fyn = new Fyn({
  token: process.env.FYN_API_TOKEN!,
  secret: process.env.FYN_SECRET_KEY,
  cache: {
    enabled: true,
    ttlMs: 60000,
    storage: 'disk',
    path: '.fynlink-sdk-cache.json',
    warnOnDisk: true
  }
});

Bootstrap Info

Fetch the current token, user, team, permissions, and available domains.

typescript
const info = await fyn.info.get();
const freshInfo = await fyn.info.get(true);

console.log(info.team.name);
console.log(freshInfo.permissions);

Type Definitions

The SDK ships with strong TypeScript definitions and re-exports the core types from @fynlink/core.

typescript
import type {
  AnalyticsMode,
  CreateDomainInput,
  CreateLinkInput,
  FynBootstrapInfo,
  FynClientConfig,
  LinkRecord,
  ListLinksResult,
  MetricsQuery,
  TeamRecord
} from '@fynlink/sdk';

const config: FynClientConfig = {
  token: process.env.FYN_API_TOKEN!,
  secret: process.env.FYN_SECRET_KEY
};

const createInput: CreateLinkInput = {
  domain: 'go.example.com',
  target: 'https://example.com'
};

type Link = LinkRecord;
type Team = TeamRecord;
type Query = MetricsQuery;
type Mode = AnalyticsMode;
type Bootstrap = FynBootstrapInfo;
type LinksResult = ListLinksResult;
type DomainCreate = CreateDomainInput;
E2EE
Your link data is encrypted, even before leaving the browser & can be decrypted only by you.
< 200ms
Average link redirection time, depends mainly on location of the end user.
99.99%
Uptime guarantee for our redirection services.
300+
For quick, uninterrupted URL redirection, our redirection service is available on all major cities worldwide.