Headless InFlow client. Same surface the CLI uses, with no Ink, no React, no command framework — Node only.
Workspace-internal: not currently published to npm. Imported by @inflowpayai/inflow (the CLI) via the pnpm workspace.
Use this package when developing the InFlow CLI itself or embedding its headless flows inside this workspace. External
applications should use the signed inflow binary or its MCP server; this package has no independent npm compatibility
promise while it remains private.
The package exposes three things:
-
Augmented resource handles — one per command group, hung off the
Inflowinstance. Each handle carries both the typed HTTP primitives and the command-shaped operations the CLI runs:inflow.auth(IAuth) — protocol primitives (initiateDeviceAuth/pollDeviceAuth/refreshToken/revokeToken) pluslogin/loginApiKey/logout/snapshot/probeStatus/pollStatus.inflow.user(IUser) —retrieve()(raw payload) plusget()(agent-mode projection that dropscreated/updated).inflow.balances(IBalanceResource) —list().inflow.depositAddresses(IDepositAddressResource) —list().inflow.subscriptions(ISubscriptionResource) —authorize(),list(),get(), andcancel().inflow.x402(IX402) —client()(lazy buyer client) pluspay/status/cancel/inspect/supported.inflow.mpp(IMpp) —client()(lazyMppClientfor MPP, the Machine Payments Protocol, from@inflowpayai/mpp) pluspay/status/cancel/inspect/supported; the pure-codecdecodeMppValuedecodes aWWW-Authenticate: Paymentheader or a base64url credential / receipt.inflow.odp(IOdpResource) — canonical directory search and suggestions, Service inspection and catalog clients, plus bounded multi-Service Offering discovery. The InFlow environment selects ODP production or sandbox; the directory endpoint cannot be overridden.
Every handle is sanitized through an ANSI-stripping Proxy so server-controlled strings can never carry terminal escape codes into the consumer. Stateful operations (
pay,inspect,auth.login) return aFlowRun<E>whoseeventsis an async- iterable — drive them with your own reducer or just consume the terminal event. Auth-side methods that need storage throwInflowConfigurationErrorat call time when noauthStoragewas configured. -
Top-level Inflow members —
inflow.hasApiKey()predicate and theinflow.resolvedApiBaseUrl: stringgetter (the canonical URL the resources will actually hit after resolvingapiBaseUrl,INFLOW_BASE_URL, and the environment-derived default). -
Helpers —
sanitizeDeep,sanitizeResource, theStorage/MemoryStorageclasses, thepollAsyncgeneric, the seller-request primitives (sellerProbe,sellerRequest,replayWithPayment,replayPaymentRequest,describeBody), the x402 decode helpers (decodeHeader,summarizeAccepts), plus theapprovalUrlFor/dashboardHostForURL helpers. All used inside the augmented handles; all re-exported for direct consumption.
import { Inflow, MemoryStorage } from '@inflowpayai/inflow-core';
const inflow = new Inflow({
apiKey: process.env.INFLOW_API_KEY,
environment: 'sandbox',
});
const balances = await inflow.balances.list();
const user = await inflow.user.retrieve();
const userAgent = await inflow.user.get();
for await (const service of inflow.odp.searchServices({ query: 'gpu' }).items) {
console.log(service.service_origin, service.name);
}
const storage = new MemoryStorage();
const sessionInflow = new Inflow({ authStorage: storage, environment: 'sandbox' });
const login = sessionInflow.auth.login({
clientName: 'My Tool',
connection: { environment: 'sandbox' },
});
for await (const event of login.events) {
if (event.type === 'initiated') console.log('Open', event.req.verification_url);
if (event.type === 'tokensReceived') console.log('Logged in');
}
console.log('Hitting', inflow.resolvedApiBaseUrl);ODP directory operations and Service-document inspection use the Inflow instance's base transport. Applications that
support authenticated catalogs can derive a Service-scoped resource with a separate transport:
const authenticatedOdp = inflow.odp.withServiceTransport({
transport: authenticatedFetch,
cachePartition: 'current-principal',
});
const offering = await authenticatedOdp.service({ serviceUrl: 'https://service.example' }).getOffering('offering-id');The application owns authentication and the partition value; the partition must be stable for one access context and must not contain credential material. A custom Service transport without a partition disables catalog caching. Public directory and inspection traffic remains on the base transport in either case.
For a deeper walk-through see examples/ (programmatic login + balances; programmatic x402 pay).
new Inflow({ ... }) accepts one of:
apiKey— static API key. Every authenticated call sends it asX-API-KEY.accessToken— static OAuth bearer. Sent asAuthorization: Bearer.getAccessToken— callback that returns a fresh token per call. Used for OAuth deployments where the caller manages the refresh cycle out-of-band.authStorage(alone) — when no static credential is set butauthStorageis, the data resources get a device-token provider auto-wired from the auth resource + storage. Runinflow.auth.loginonce, tokens land in storage, subsequent reads transparently refresh. This is the CLI's mode.- None of the above — anonymous. The data resources construct but fail at request time. Useful when only
inflow.auth.*is needed.
Seller SDK credentials are a separate concern. A Seller API key is required by the seller configuration endpoints in
inflow-node; a Developer account key does not authorize those endpoints. The headless CLI client primarily models
buyer authentication and should not be used as a replacement seller SDK.
Set INFLOW_HTTP_PROXY to route every outbound HTTP request through a proxy. The SDK lazy-loads undici's ProxyAgent
on first use; install it as a peer (npm install undici) when the env var is set. The proxy is ignored when the caller
passes a custom fetch — bring your own dispatcher in that case.
This package is the headless contract. It must not import any CLI-rendering library (react, ink, incur, etc.). The
repo's ESLint config has a no-restricted-imports rule scoped to packages/core/src/** that fails the lint step on any
such import. Add new bans there when promoting more CLI-only deps.
The CLI binary (@inflowpayai/inflow) is the only sanctioned consumer today; the package is workspace-internal
(private: true in package.json).