TypeScript SDK
Installation, configuration, and full reference for the TypeScript SDK.
The TypeScript SDK (@inndx-io/sdk) is ESM-only and targets Node 20 or newer. It handles MPP payment automatically for both per-request and session endpoints.
Installation
The package is not published to the npm registry. Install it directly from git:
pnpm add github:inndx-io/typescript-sdkPin a specific tag or commit for reproducible installs:
pnpm add github:inndx-io/typescript-sdk#v0.3.2
pnpm add github:inndx-io/typescript-sdk#<commit-sha>Or declare it in package.json:
{
"dependencies": {
"@inndx-io/sdk": "github:inndx-io/typescript-sdk#main"
}
}pnpm is recommended as it runs the package's prepare build step on install, giving you the compiled dist/ automatically.
Configuration
import { InndxClient } from '@inndx-io/sdk'
const client = new InndxClient({
baseUrl: 'https://api.inndx.io',
walletKey: process.env.WALLET_PRIVATE_KEY as `0x${string}`,
})new InndxClient(config) accepts:
| Option | Required | Description |
|---|---|---|
baseUrl | yes | Base URL of the inndx API. |
walletKey | one signer required | Wallet private key in 0x... hex form. Server-side use only. |
account | one signer required | A prebuilt viem account for custom or WebCrypto signers. |
getConnectorClient | one signer required | wagmi-style connector accessor for browser signing. |
maxDeposit | for sessions | Default escrow cap per session in human units (e.g. "10"). Overridable per session. |
chainId | for reclaim | Chain id needed by reclaimSession when working without a server. |
headers | no | Headers added to every request. |
fetch | no | Custom fetch implementation. |
rpcUrl | no | RPC endpoint applied to every chain. |
rpcUrls | no | Per chain id RPC endpoints. |
feePayerUrl | no | Fee-payer relay URL so a third party covers gas. |
escrowContract | no | Advanced. Escrow contract override. |
getClient | no | Advanced. Full control over the viem client per chain id. |
client | no | Advanced. A prebuilt viem client used for every chain id. |
Supply exactly one signer: walletKey, account, or getConnectorClient.
Browser and wagmi
In the browser, sign through a connected wallet using wagmi rather than holding a private key:
import { InndxClient } from '@inndx-io/sdk'
import { getConnectorClient } from 'wagmi/actions'
const client = new InndxClient({
baseUrl: 'https://api.inndx.io',
getConnectorClient: (parameters) => getConnectorClient(wagmiConfig, parameters),
maxDeposit: '10',
})Charges, sessions, and reclaim will prompt the wallet to sign rather than signing locally.
Scrape
client.scrape exposes the scraping operations. All Scrape API endpoints are session-based.
scrapeUrlMarkdown
Fetches a URL and returns the page as a markdown string.
const session = client.scrape.scrapeUrlMarkdown({ maxDeposit: '5' })
const markdown = await session.call('https://example.com')
await session.close()Pass scrape options as a second argument:
const markdown = await session.call('https://example.com', {
proxy: 'isp',
timeout_seconds: 30,
locale: 'en-US',
})scrapeUrl
Full control over output formats and options. Returns { url, results }.
const session = client.scrape.scrapeUrl({ maxDeposit: '5' })
const result = await session.call({
url: 'https://example.com',
formats: [{ kind: 'markdown' }],
proxy: 'isp',
timeout_seconds: 30,
locale: 'en-US',
})
for (const item of result.results) {
if (item.kind === 'markdown') console.log(item.content)
}
await session.close()Sessions
Opening and closing
const session = client.scrape.scrapeUrlMarkdown({ maxDeposit: '5' })
const page1 = await session.call('https://example.com')
const page2 = await session.call('https://example.com/about')
console.log('spent so far:', session.cumulative)
await session.close()Scoped sessions
Use scope to guarantee the session closes even if your code throws:
const result = await client.scrape.scrapeUrlMarkdown({ maxDeposit: '5' })
.scope(async (session) => {
return await session.call('https://example.com')
})await using
TypeScript 5.2+ resource disposal syntax settles the session automatically when the block exits:
await using session = client.scrape.scrapeUrlMarkdown({ maxDeposit: '5' })
const page = await session.call('https://example.com')Session options
| Option | Description |
|---|---|
maxDeposit | Escrow cap for this session in human units (e.g. "10"). |
escrowContract | Escrow contract override for this session. |
SessionScope members
| Member | Description |
|---|---|
call(...args) | Makes a request through the session. |
close() | Settles the channel on-chain and returns the receipt, or undefined if nothing was opened. |
scope(cb) | Runs cb(session) then settles regardless of outcome. |
channelId | The channel id once opened, otherwise undefined. |
cumulative | Cumulative amount spent so far. |
opened | Whether the channel has been opened. |
escrowContract | The escrow contract this session targets. |
Reclaiming a stranded channel
If a process exits before close() is called, the escrowed deposit stays on-chain. Persist the channel id as soon as the channel opens, then use reclaimSession to recover the funds later.
// Persist the channel id after the first call opens the channel.
if (session.channelId) {
await saveChannelId(session.channelId)
}Forced close is a two-step sequence with a roughly 15-minute grace period between steps:
const reclaim = client.reclaimSession({ channelId })
// Step 1: start the close timer.
await reclaim.requestClose()
// Step 2: after the grace period, withdraw.
const state = await reclaim.getState()
if (state.ready) {
await reclaim.withdraw()
}The two steps do not need to run in the same process. Both requestClose and withdraw are idempotent.
Calling withdraw before the grace period elapses throws ChannelNotReadyError, which carries a readyAt timestamp.
Error handling
Non-2xx responses throw ApiError, which carries the status, parsed error body, and raw Response:
import { ApiError } from '@inndx-io/sdk'
try {
const result = await session.call('https://example.com')
} catch (err) {
if (err instanceof ApiError) {
console.error(err.status, err.body)
} else {
throw err
}
}