New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

x-tkn

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install
Package was removed
Sorry, it seems this package was removed from the registry

x-tkn

SDK for the X-TKN API — stateful tokens as a service

latest
Source
npmnpm
Version
2.0.0
Version published
Maintainers
1
Created
Source

x-tkn

TypeScript/JavaScript client for X-TKN — stateful tokens as a service. Mint a token, hand the code to someone, and control how many times and for how long it can be redeemed.

Get an API key by signing up at x-tkn.com.

npm install x-tkn

Node 18+, or any runtime with a global fetch — browsers, Deno, Bun, Cloudflare Workers, Vercel Edge. No dependencies.

Upgrading from 1.x? v2 is a breaking change and 1.x does not work against the current API. See Migrating from v1.

Quickstart

import { xtkn } from 'x-tkn'

const token = await xtkn.createToken({
  type: 'password-reset',
  refId: user.id,
  maxUses: 1,
  expiresIn: { minutes: 30 },
})

// token.code is returned once and never again. Persist or send it now.
await sendEmail(user.email, `https://app.example.com/reset?c=${token.code}`)

…and on the other side:

import { xtkn, XTknGoneError, XTknNotFoundError } from 'x-tkn'

try {
  const token = await xtkn.redeemToken(code, { refId: user.id, type: 'password-reset' })
  await resetPassword(user, token.payload)
} catch (err) {
  if (err instanceof XTknGoneError) return render('That link has already been used.')
  if (err instanceof XTknNotFoundError) return render('That link is not valid.')
  throw err
}

Authentication

The key is read on every request, so it can be set after import.

X_TKN_API_KEY=your_key_here

Or pass it explicitly — necessary if you talk to more than one account:

import { XTkn } from 'x-tkn'

const client = new XTkn({ apiKey: process.env.MY_KEY })

Keep the key server-side. It grants full token CRUD for your account, so a key in a browser bundle is a published key. v1 read REACT_APP_- and VUE_APP_-prefixed variables, whose entire purpose is to inline a value into a browser bundle; v2 does not.

Options

OptionDefault
apiKeyX_TKN_API_KEY, then X_TKN_API_KEY_IDResolved per request
baseUrlhttps://api.x-tkn.com
timeoutMs300000 disables
fetchglobalThis.fetchFor tests or a custom agent
headers—Merged into every request; cannot override Authorization

Methods

Every method throws on failure — see Errors. code is the token's public identifier, returned by createToken.

createToken(input?)

const token = await xtkn.createToken({
  type: 'handoff', // [a-z0-9_-], ≤64 chars, defaults to 'generic'
  refId: 'user_123', // your own identifier, ≤256 chars
  payload: { role: 'admin' }, // JSON-encoded, ≤64 KB
  maxUses: 1, // 1–1,000,000. omit for unlimited
  description: 'Admin invite', // operator-facing note
  expiresIn: { hours: 2 }, // or expiresAt: new Date(...) — not both
})

token.code is the only copy. The server stores sha256(code) and cannot return it later; every subsequent read leaves the field undefined.

With no expiresAt or expiresIn, the server expires the token 30 days after creation.

readToken(code)

Returns the token without consuming a use. Check isActive, isExpired, isUsed.

redeemToken(code, options?)

Consumes one use and returns the token. Throws XTknGoneError if it is revoked, expired or exhausted; XTknNotFoundError if the code is wrong.

await xtkn.redeemToken(code, { refId: user.id, type: 'handoff' })

Pass both refId and type where you can. refId confines the lookup to the identity the code was issued for, which is what bounds guessing; type stops the redemption consuming a different kind of code held by the same identity.

updateToken(code, updates)

Changes type, refId, payload, maxUses or the expiry. description is not updatable.

extendExpiration(code, duration)

Moves the expiry to duration from now, not from the existing expiry — so this revives an already-expired token rather than extending from a past date.

await xtkn.extendExpiration(sessionCode, { hours: 2 })

revokeToken(code) / revokeTokens(filter)

await xtkn.revokeToken(code)

// Log a user out everywhere. One call revokes a bounded batch.
let result
do {
  result = await xtkn.revokeTokens({ refId: user.id, type: 'session' })
} while (result.hasMore)

revokeTokens requires type or refId. An empty filter means "revoke everything", which is not allowed by omission.

deleteToken(code)

Permanent. Prefer revokeToken — a revoked token can still explain why a redemption failed, a deleted one is indistinguishable from one that never existed.

listTokens(options?)

const { tokens, count } = await xtkn.listTokens({
  where: { refId: 'user_123', isRevoked: false },
  sort: '-createdAt',
  page: 1,
  limit: 50, // capped at 100
})

count is the total matching the filter, not tokens.length.

Only type, refId and isRevoked are filterable, and the server silently drops anything else — so a typo widens the result rather than erroring. sort accepts createdAt, updatedAt, expiresAt, lastUsedAt or uses, each optionally prefixed with -; anything else is silently replaced with -createdAt, which the TokenSort type makes unreachable from TypeScript.

Codes are not exposed as a filter. The API accepts one and hashes it to match sha256(code), but it can only return the single token you already hold the code for — readToken(code) does that directly.

Errors

Every non-2xx response throws. Catch XTknError for all of them, or a subclass to tell them apart.

ClassStatusMeans
XTknRequestError400Malformed request or failed validation
XTknAuthError401, 403Key missing, unknown or revoked
XTknNotFoundError404No such token on this account
XTknGoneError410Exists but spent: revoked, expired or out of uses
XTknRateLimitError429Hourly guard or monthly quota exhausted
XTknServerError5xx
XTknConnectionError—Never reached the server: DNS, reset, timeout
XTknConfigError—Bad arguments; thrown before any request

Each carries status, and details when the API supplied a field-level map.

Payload encryption

The server never needs to read your payload, and accounts with requireEncryptedPayload set reject anything that is not already an xtkn.v1. or xtkn.v1r. envelope.

This SDK does not encrypt for you yet. Pass an already-encrypted string as payload if your account enforces it. Local encryption is planned; until it lands, the envelope format is documented in the X-TKN crypto contract.

Migrating from v1

1.x returns the HTTP envelope instead of the token, never throws on an error response, and calls routes with a parameter the API renamed — so it does not work against the current API. There is no compatibility shim; the changes are mechanical.

v1v2
setup(apiKey)new XTkn({ apiKey }), or X_TKN_API_KEY. v1's setup() assigned its argument to the base URL, so it never set a key at all
import { createToken } from 'x-tkn'import { xtkn } from 'x-tkn' → xtkn.createToken(...)
Any tokenId argumentcode
Result was { success, data }The token itself
A 401/404/410 arrived as dataThrows a typed error
createSecurityToken(refId, ttl)createToken({ refId, maxUses: 1, expiresIn: ttl })
createSessionToken(refId, ttl, payload, type)createToken({ refId, payload, type, expiresIn: ttl })
createShortCode(...)Removed. The API no longer issues short codes
redeemToken(id, shortCode)redeemToken(code, { refId, type })
listTokens(where, orderBy, skip, take)listTokens({ where, sort, page, limit })
revokeTokens(where)revokeTokens({ type, refId })
listTokens filtering by codeNot wrapped. Use readToken(code)
REACT_APP_ / VUE_APP_ key variablesRemoved — see Authentication
deleteToken returned the response bodyReturns void

ttl objects carry over unchanged as expiresIn, and seconds now works — v1 documented it and had no branch for it.

License

ISC

Keywords

x-tkn

FAQs

Package last updated on 30 Aug 2026

Related posts