* upgrade deps sep-26 * add changeset * pnpm biome check fixes
@stripe/link-sdk
Node.js SDK for agents that use Link. It provides typed resources for Link APIs and accepts an access token from your application.
The SDK does not perform login, persist credentials, or own refresh tokens. Authentication state and user-facing authorization flows belong to the CLI or application embedding the SDK.
Install
npm install @stripe/link-sdk
The package is ESM-only and requires Node.js 20 or newer.
Quick start
Pass an access token when creating the client:
import Link from '@stripe/link-sdk';
const link = new Link({ accessToken: process.env.LINK_ACCESS_TOKEN! });
const paymentMethods = await link.paymentMethods.list();
Use a fixed token for a short-lived job or when the caller replaces the entire client as credentials change.
Credentials
Exactly one credential option is required.
Dynamic access tokens
When your application manages expiring credentials, provide getAccessToken.
The SDK calls it for each request. After a 401 response, the SDK calls it once
with forceRefresh: true and retries the request with the returned token.
const link = new Link({
getAccessToken: async ({ forceRefresh } = {}) =>
credentialManager.getLinkAccessToken({ forceRefresh }),
});
The credential manager should coalesce concurrent refreshes if several
requests can receive a 401 at the same time. A client configured with a fixed
accessToken does not retry a 401 because it cannot obtain a different token.
User-approved purchase flow
Amounts are expressed in the currency's minor unit, such as cents for USD.
context must be at least 100 characters and should tell the user what the
agent is buying and why.
const methods = await link.paymentMethods.list();
const paymentMethod = methods.find((method) => method.is_default) ?? methods[0];
if (!paymentMethod) {
throw new Error('The user needs to add a Link payment method.');
}
const spendRequest = await link.spendRequests.create({
payment_details: paymentMethod.id,
credential_type: 'card',
amount: 2599,
currency: 'usd',
merchant_name: 'Acme',
merchant_url: 'https://acme.example',
context:
'The user asked the agent to buy the selected item from Acme for $25.99, including the displayed shipping cost.',
});
const approval = await link.spendRequests.requestApproval(spendRequest.id);
await sendToUser(`Approve this purchase: ${approval.approval_url}`);
await saveRunState({ spendRequestId: spendRequest.id });
Retrieve the request in a later agent run:
const result = await link.spendRequests.retrieve(state.spendRequestId);
if (!result) {
throw new Error('Spend request not found.');
}
switch (result.status) {
case 'approved':
// Use the returned credential without placing it in logs or chat.
break;
case 'created':
return { status: 'approval_not_requested' };
case 'pending_approval':
return { status: 'waiting_for_user_approval' };
case 'requires_action': {
const action = result.status_details?.requires_action?.next_action;
return action?.resolution === 'auto_resume'
? { status: 'retry_later' }
: action;
}
case 'denied':
case 'expired':
case 'succeeded':
case 'failed':
case 'canceled':
return { status: result.status };
}
Only a requires_action result whose resolution is auto_resume should be
polled automatically. For any other resolution, surface the action to the user
and follow its instructions. Keep returned card or shared-payment-token
credentials out of model context, logs, and user-visible messages.
Configuration
const link = new Link({
accessToken,
fetch,
defaultHeaders: { 'X-Agent-Version': 'acme-agent/1.0' },
verbose: true,
logger: { debug: (message) => diagnostics.debug(message) },
apiBaseUrl: 'https://api.link.com',
spendRequestBaseUrl: 'https://api.link.com',
});
Verbose logging includes request methods, URLs, and response status codes. It does not include authorization headers or request and response bodies.
Errors
The SDK throws typed errors:
LinkConfigurationErrorfor invalid or missing client configurationLinkTransportErrorwhen a request cannot reach LinkLinkApiErrorfor non-success API responsesLinkResponseErrorwhen a successful response has an invalid shape
LinkApiError includes status, code, and structured details fields for
programmatic handling.
import { LinkApiError } from '@stripe/link-sdk';
try {
await link.spendRequests.retrieve('spend_request_id');
} catch (error) {
if (error instanceof LinkApiError) {
diagnostics.error({
status: error.status,
code: error.code,
details: error.details,
});
}
throw error;
}
Client resources
spendRequests— create, approve, retrieve, update, cancel, and listpaymentMethods— list Link payment methodsshippingAddresses— list shipping addressesuserInfo— retrieve Link user informationtransactions— list transactionssources— list connected sourcesbalances— list balanceswebBotAuth— sign URLs for Web Bot Authreports— report agent outcomes