@fynlink/sdk
v0.2.0-beta.7
Published
TypeScript SDK for Fynlink
Readme
FynLink TypeScript SDK
Official TypeScript SDK for FynLink.
Use @fynlink/sdk to create and manage short links, read analytics, work with custom domains, and manage team resources from Node.js and TypeScript applications.
Install
npm install @fynlink/sdkQuick Start
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);Client Setup
new Fyn(config) accepts the following options:
| Option | Type | Required | Description |
| --- | --- | --- | --- |
| token | string | Yes | API token used for authenticated requests. |
| secret | string | No | Required for encrypted link reads, lists, and updates. |
| baseUrl | string | No | Custom API base URL. Defaults to https://api.fyn.link/v1. |
| timeoutMs | number | No | Request timeout in milliseconds. Defaults to 10000. |
| debug | boolean | No | Enables HTTP and cache diagnostics. Defaults to false. |
| retry.maxAttempts | number | No | Maximum retry attempts. Defaults to 2. |
| retry.initialDelayMs | number | No | Initial retry delay in milliseconds. Defaults to 1000. |
| cache.enabled | boolean | No | Enables response caching. Defaults to true. |
| cache.ttlMs | number | No | Cache TTL in milliseconds. Defaults to 300000. |
| cache.storage | 'memory' \| 'disk' | No | Cache backend. Defaults to 'disk'. |
| cache.path | string | No | Disk cache file path. Defaults to .fynlink-sdk-cache.json. |
| cache.warnOnDisk | boolean | No | Emits a disk-cache security warning. Defaults to true. |
| defaults.links.create.domain | string | No | Default domain for fyn.links.create(...) when per-request domain is omitted. |
| defaults.links.create.safeMode | boolean | No | Default safeMode for fyn.links.create(...). |
| defaults.links.create.analytics | AnalyticsMode | No | Default analytics mode for fyn.links.create(...). |
| defaults.links.create.privateLink | boolean | No | Default privacy mode for fyn.links.create(...). |
| defaults.links.create.expirySeconds | number | No | Default TTL (seconds) for fyn.links.create(...). |
Example:
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
}
}
}
});Modules
fyn.infofyn.linksfyn.analyticsfyn.domainsfyn.membersfyn.teams
Links
Create a Link
Important Team defaults set in the web app (such as default domain, private link mode, safe mode, analytics mode, and expiry) are not automatically respected by the SDK when creating links. Set these explicitly per request or configure
defaults.links.createduringnew Fyn(...).
import { AnalyticsMode } from '@fynlink/sdk';
const link = await fyn.links.create({
domain: 'go.example.com',
slug: 'launch-2026',
target: 'https://example.com',
title: 'Launch Page',
notes: 'Primary campaign link',
tags: ['launch', 'campaign'],
iosTarget: 'https://apps.apple.com/app/id123456789',
androidTarget: 'https://play.google.com/store/apps/details?id=com.example.app',
geoTargets: {
US: 'https://us.example.com',
IN: 'https://in.example.com'
},
password: 'optional-password',
safeMode: true,
privateLink: false,
analytics: AnalyticsMode.Full,
expirySeconds: 86_400
});Possible SDK response:
{
id: 'link_123',
userId: 'user_99',
teamId: 'team_7',
domain: 'go.example.com',
slug: 'launch-2026',
shortUrl: 'https://go.example.com/launch-2026',
target: {
default: 'https://example.com',
ios: 'https://apps.apple.com/app/id123456789',
android: 'https://play.google.com/store/apps/details?id=com.example.app',
geo: {
US: 'https://us.example.com',
IN: 'https://in.example.com'
},
options: {
password: 'optional-password'
}
},
title: 'Launch Page',
notes: 'Primary campaign link',
tags: ['launch', 'campaign'],
isPrivate: false,
safeMode: true,
viaApi: true,
passwordEnabled: true,
clicks: 0,
createdAt: '2026-03-20T10:15:30.000000Z',
updatedAt: '2026-03-20T10:15:30.000000Z',
expiresAt: '2026-03-21T10:15:30.000000Z',
tracking: 'full',
status: 'active'
}fyn.links.create(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | Conditionally | Domain used for the short link. Required unless defaults.links.create.domain is configured in new Fyn(...). |
| slug | string | No | Custom slug. Auto-generated when omitted. |
| target | string | Yes | Default destination URL. |
| title | string | No | Human-readable title. |
| notes | string | No | Internal notes. |
| tags | string[] | No | Arbitrary tags for organization. |
| iosTarget | string | No | iOS-specific destination URL. |
| androidTarget | string | No | Android-specific destination URL. |
| geoTargets | Record<string, string> | No | Country-specific destination URLs keyed by country code. |
| password | string | No | Password required to access the link. |
| safeMode | boolean | No | Safe mode flag. Falls back to SDK default (true) when omitted. |
| privateLink | boolean | No | Marks the link as private. |
| analytics | AnalyticsMode | No | Analytics level. Falls back to SDK default (AnalyticsMode.Full) when omitted. |
| expirySeconds | number | No | Time to live in seconds. |
Defaulted create behavior example:
import { AnalyticsMode, Fyn } from '@fynlink/sdk';
const fyn = new Fyn({
token: process.env.FYN_API_TOKEN!,
defaults: {
links: {
create: {
domain: 'go.example.com',
safeMode: true,
analytics: AnalyticsMode.Full,
privateLink: false,
expirySeconds: 86_400
}
}
}
});
// domain/safeMode/analytics/privateLink/expirySeconds come from defaults
const link = await fyn.links.create({
target: 'https://example.com'
});Available analytics modes:
AnalyticsMode.NoneAnalyticsMode.FullAnalyticsMode.PartialAnalyticsMode.Clicks
Get, List, Update, and Delete
import { AnalyticsMode, LinkStatus } from '@fynlink/sdk';
const link = await fyn.links.get('link_123');
const links = await fyn.links.list({
page: 1,
limit: 25,
sortBy: 'created_at',
sortOrder: 'desc',
filters: [
{
domain: 'go.example.com',
tracking: AnalyticsMode.Full,
safeMode: true,
isPrivate: false,
password: false,
status: LinkStatus.Active
}
]
});
const updated = await fyn.links.update({
id: 'link_123',
target: 'https://example.com/updated',
title: 'Updated title',
notes: 'Updated notes',
tags: ['updated'],
iosTarget: 'https://ios.example.com/updated',
androidTarget: 'https://android.example.com/updated',
geoTargets: {
US: 'https://us.example.com/updated'
},
password: null,
safeMode: true,
privateLink: false,
analytics: AnalyticsMode.Clicks,
expirySeconds: null
});
await fyn.links.delete('link_123');Possible SDK response (fyn.links.get):
{
id: 'link_123',
userId: 'user_99',
teamId: 'team_7',
domain: 'go.example.com',
slug: 'launch-2026',
shortUrl: 'https://go.example.com/launch-2026',
target: {
default: 'https://example.com',
ios: null,
android: null,
geo: {}
},
title: 'Launch Page',
notes: 'Primary campaign link',
tags: ['launch', 'campaign'],
isPrivate: false,
safeMode: true,
viaApi: true,
passwordEnabled: false,
clicks: 42,
createdAt: '2026-03-20T10:15:30.000000Z',
updatedAt: '2026-03-21T09:00:00.000000Z',
expiresAt: null,
tracking: 'full',
status: 'active'
}Possible SDK response (fyn.links.list):
{
data: [
{
id: 'link_123',
userId: 'user_99',
teamId: 'team_7',
domain: 'go.example.com',
slug: 'launch-2026',
shortUrl: 'https://go.example.com/launch-2026',
target: {
default: 'https://example.com',
ios: null,
android: null,
geo: {}
},
title: 'Launch Page',
notes: 'Primary campaign link',
tags: ['launch', 'campaign'],
isPrivate: false,
safeMode: true,
viaApi: true,
passwordEnabled: false,
clicks: 42,
createdAt: '2026-03-20T10:15:30.000000Z',
updatedAt: '2026-03-21T09:00:00.000000Z',
expiresAt: null,
tracking: 'full',
status: 'active'
}
],
meta: {
current_page: 1,
from: 1,
to: 1,
per_page: 25,
last_page: 1,
total: 1
}
}Possible SDK response (fyn.links.update):
{
id: 'link_123',
userId: 'user_99',
teamId: 'team_7',
domain: 'go.example.com',
slug: 'launch-2026',
shortUrl: 'https://go.example.com/launch-2026',
target: {
default: 'https://example.com/updated',
ios: 'https://ios.example.com/updated',
android: 'https://android.example.com/updated',
geo: {
US: 'https://us.example.com/updated'
}
},
title: 'Updated title',
notes: 'Updated notes',
tags: ['updated'],
isPrivate: false,
safeMode: true,
viaApi: true,
passwordEnabled: false,
clicks: 42,
createdAt: '2026-03-20T10:15:30.000000Z',
updatedAt: '2026-03-21T10:22:00.000000Z',
expiresAt: null,
tracking: 'clicks',
status: 'active'
}fyn.links.delete(id) resolves with no return value when successful.
fyn.links.list(input) parameters:
| Field | Type | Description |
| --- | --- | --- |
| page | number | Page number. |
| limit | number | Page size. |
| sortBy | 'created_at' \| 'updated_at' \| 'clicks' | Sort field. |
| sortOrder | 'asc' \| 'desc' | Sort direction. |
| filters | LinkListFilter[] | Filter rules applied by the API. |
LinkListFilter fields:
| Field | Type | Description |
| --- | --- | --- |
| domain | string | Filter by domain. |
| tracking | AnalyticsMode | Filter by analytics mode. |
| safeMode | boolean | Filter by safe mode setting. |
| isPrivate | boolean | Filter by privacy setting. |
| password | boolean | Filter by password protection status. |
| status | LinkStatus | Filter by link status. |
viaApi is response metadata on LinkRecord and is set by the backend. It is not a request/filter input.
Available link statuses:
LinkStatus.ActiveLinkStatus.InactiveLinkStatus.FlaggedLinkStatus.ReportedLinkStatus.ReportedBannedLinkStatus.NeedsUpgradeLinkStatus.Quarantined
fyn.links.update(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| id | string | Yes | Link ID to update. |
| target | string | No | New default destination URL. |
| title | string | No | Updated title. |
| notes | string | No | Updated notes. |
| tags | string[] | No | Updated tags. |
| iosTarget | string | No | Updated iOS destination URL. |
| androidTarget | string | No | Updated Android destination URL. |
| geoTargets | Record<string, string> | No | Updated country-specific targets. |
| password | string \| null | No | New password, or null to remove it. |
| safeMode | boolean | No | Updated safe mode setting. |
| privateLink | boolean | No | Updated privacy setting. |
| analytics | AnalyticsMode | No | Updated analytics mode. |
| expirySeconds | number \| null | No | Updated expiry, or null to clear it. |
Analytics
Available methods:
fyn.analytics.clicks(linkId)fyn.analytics.timeseries(linkId, query?)fyn.analytics.browsers(linkId, query?)fyn.analytics.devices(linkId, query?)fyn.analytics.countries(linkId, query?)fyn.analytics.referrers(linkId, query?)
MetricsQuery parameters:
| Field | Type | Description |
| --- | --- | --- |
| start | string | Start timestamp. |
| end | string | End timestamp. |
| granularity | 'minute' \| 'hour' \| 'day' \| 'week' \| 'month' | Aggregation granularity. |
| country | string[] | Country filters. |
| browser | string[] | Browser filters. |
| device | string[] | Device filters. |
| referrer | string[] | Referrer filters. |
Example:
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']
});Possible SDK responses:
// fyn.analytics.clicks('link_123')
{
id: 'link_123',
clicks: 120
}
// fyn.analytics.timeseries('link_123', query)
{
data: [
{
date: '2026-03-01T00:00:00+00:00',
clicks: 14,
uniqueClicks: 12
}
],
meta: {
totalClicks: 120,
totalUniqueClicks: 95,
count: 10
}
}
// fyn.analytics.browsers('link_123', query)
{
data: [
{ browser: 'Chrome', clicks: 58, percentage: 48.33 },
{ browser: 'Safari', clicks: 29, percentage: 24.17 }
],
meta: {
totalClicks: 120,
count: 2
}
}
// fyn.analytics.devices('link_123', query)
{
data: [
{ device: 'desktop', clicks: 73, percentage: 60.83 },
{ device: 'mobile', clicks: 47, percentage: 39.17 }
],
meta: {
totalClicks: 120,
count: 2
}
}
// fyn.analytics.countries('link_123', query)
{
data: [
{ country: 'United States', countryCode: 'US', clicks: 64, percentage: 53.33 },
{ country: 'India', countryCode: 'IN', clicks: 28, percentage: 23.33 }
],
meta: {
totalClicks: 120,
count: 2,
countriesCount: 2
}
}
// fyn.analytics.referrers('link_123', query)
{
data: [
{ referrer: 'google.com', clicks: 66, percentage: 55.0 },
{ referrer: 'direct', clicks: 22, percentage: 18.33 }
],
meta: {
totalClicks: 120,
count: 2,
sourcesCount: 2
}
}Domains
const domains = await fyn.domains.list({
page: 1,
limit: 10
});
const domain = await fyn.domains.get('domain_123');
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
});
const verification = await fyn.domains.verify(created.id);
const updated = await fyn.domains.update({
id: created.id,
description: 'Updated description',
notFoundUrl: 'https://example.com/not-found',
rootRedirect: 'https://example.com/home',
gpc: true
});
await fyn.domains.remove(created.id);Possible SDK responses:
// fyn.domains.list(...)
{
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
}
}
// fyn.domains.get('domain_123')
{
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'
}
// fyn.domains.create(...)
{
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'
}
// fyn.domains.verify('domain_123')
{
id: 'domain_123',
domain: 'go.example.com',
status: 'active',
verified: true,
cname: 'cname.fyn.link'
}
// fyn.domains.update(...)
{
id: 'domain_123',
domain: 'go.example.com',
description: 'Updated description',
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'
}fyn.domains.remove(id) resolves with no return value when successful.
fyn.domains.list(input) parameters:
| Field | Type | Description |
| --- | --- | --- |
| page | number | Page number. |
| limit | number | Page size. |
fyn.domains.create(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| domain | string | Yes | Domain name to register. |
| description | string | No | Optional description. |
| notFoundUrl | string | No | URL used for unmatched slugs. |
| rootRedirect | string | No | URL used for the bare domain root. |
| gpc | boolean | No | Global privacy control setting. |
When you create a domain, the backend automatically associates it with the current team from the token used for the request. You do not pass a team in fyn.domains.create().
fyn.domains.update(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| id | string | Yes | Domain ID to update. |
| description | string | No | Updated description. |
| notFoundUrl | string | No | Updated not found URL. |
| rootRedirect | string | No | Updated root redirect URL. |
| gpc | boolean | No | Updated global privacy control setting. |
Domain team ownership is not changed through fyn.domains.update().
Members
const members = await fyn.members.list({
includeOwner: true
});
const member = await fyn.members.get('member_123');
await fyn.members.invite({
email: '[email protected]',
role: 'editor'
});
const updated = await fyn.members.update({
id: 'member_123',
role: 'admin'
});
await fyn.members.remove('member_123');Possible SDK responses:
// fyn.members.list(...)
{
data: [
{
id: 'user_123',
name: 'Alex Doe',
email: '[email protected]',
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
}
}
// fyn.members.get('member_123')
{
id: 'user_123',
name: 'Alex Doe',
email: '[email protected]',
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'
}
// fyn.members.update(...)
{
id: 'user_123',
name: 'Alex Doe',
email: '[email protected]',
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.members.invite(...) and fyn.members.remove(...) both resolve with no return value on success.
fyn.members.list(input) parameters:
| Field | Type | Description |
| --- | --- | --- |
| includeOwner | boolean | Includes the team owner in the result set. |
fyn.members.invite(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| email | string | Yes | Member email address. |
| role | string | Yes | Member role. |
For security, only already-registered users can be invited.
fyn.members.update(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| id | string | Yes | Member ID to update. |
| role | string | Yes | Updated role. |
Teams
const team = await fyn.teams.get();
const renamed = await fyn.teams.update({
name: 'Growth Team'
});Possible SDK response (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'
}fyn.teams.update(input) parameters:
| Field | Type | Required | Description |
| --- | --- | --- | --- |
| name | string | Yes | New team name. |
Info
const info = await fyn.info.get();
const freshInfo = await fyn.info.get(true);Use fyn.info.get() to fetch bootstrap information for the current token and team. Pass true to force a refresh.
Possible SDK response:
{
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'
}Caching
Caching is enabled by default with ETag revalidation support for GET requests.
const fyn = new Fyn({
token: process.env.FYN_API_TOKEN!,
secret: process.env.FYN_SECRET_KEY,
cache: {
enabled: true,
ttlMs: 60_000,
storage: 'disk',
path: '.fynlink-sdk-cache.json',
warnOnDisk: true
}
});Security notes:
- Disk cache stores raw GET response bodies and headers.
- Link-sensitive fields (for example
target,password,notes,tags, short URL envelope) remain encrypted inside cached payload envelopes. - Metadata and headers are cached as returned; they are not encrypted by the SDK cache layer.
- If an attacker obtains both cache contents and your
FYN_SECRET_KEY, encrypted link payload fields may be decrypted. - Keep
cache.warnOnDiskenabled and use disk cache only in trusted environments.
Error Handling
The SDK throws typed errors:
FynAuthErrorFynPermissionErrorFynNotFoundErrorFynValidationErrorFynRateLimitErrorFynTimeoutErrorFynError
import { FynValidationError } from '@fynlink/sdk';
try {
await fyn.links.create({
domain: 'go.example.com',
target: 'https://example.com'
});
} catch (error) {
if (error instanceof FynValidationError) {
console.error(error.message);
}
}Local Examples
cp examples/.env.example examples/.envExample environment variables:
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=trueAvailable scripts:
npx tsx examples/links/create.ts
npx tsx examples/links/get.ts <linkId>
npx tsx examples/links/list.ts
npx tsx examples/links/update.ts <linkId>
npx tsx examples/links/delete.ts <linkId>
npx tsx examples/info/get.ts
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/members/list.ts
npx tsx examples/members/get.ts <memberId>
npx tsx examples/members/invite.ts <email> <role>
npx tsx examples/members/update.ts <memberId> <role>
npx tsx examples/members/remove.ts <memberId>
npx tsx examples/teams/get.ts
npx tsx examples/teams/update.ts <new-team-name>
npx tsx examples/domains/list.ts
npx tsx examples/domains/get.ts <domainId>
npx tsx examples/domains/create.ts <domain>
npx tsx examples/domains/update.ts <domainId>
npx tsx examples/domains/verify.ts <domainId>
npx tsx examples/domains/delete.ts <domainId>More examples: examples/README.md
Development
npm install
npm run typecheck
npm test
npm run buildLicense
MIT
