Recur Docs

Server-side helpers

Drive subscriptions from a backend or Node script using RecurClient.

Server-side helpers

The same high-level operations available in the browser are exposed directly on RecurClient. Use these from a Node service, an API route, a test fixture, or a custom signing flow.

import { RecurClient, getClusterDefaults } from "@recur/sdk";
 
const defaults = getClusterDefaults("devnet");
const client = new RecurClient({
  apiBaseUrl: process.env.RECUR_API_URL!,
  rpcUrl: defaults.rpcUrl,
  apiKey: process.env.RECUR_API_KEY, // optional, only for merchant routes
});

All methods take a RecurWallet (anything with publicKey, signTransaction, and signMessage). A keypair on the server can implement this interface directly; a browser passes the wallet adapter.

import { Keypair } from "@solana/web3.js";
import type { RecurWallet } from "@recur/sdk";
 
const keypair = Keypair.fromSecretKey(/* ... */);
const serverWallet: RecurWallet = {
  publicKey: keypair.publicKey,
  signTransaction: async (tx) => { tx.sign(keypair); return tx; },
  signMessage: async (msg) => nacl.sign.detached(msg, keypair.secretKey),
};

client.authenticate(wallet)

const token = await client.authenticate(wallet);

Runs nonce → signMessage → verify against /auth/verify and returns a JWT string.

client.subscribe(wallet, options, token)

const { subscriptionPda, signature, subscription } = await client.subscribe(
  wallet,
  {
    appId: "app_xxx",
    planId: "plan_xxx",
    merchantWallet: plan.app.merchant.walletAddress,
    planSeed: plan.planSeed,
    amount: Number(plan.amountBaseUnits),
    intervalSeconds: plan.intervalSeconds,
    delegationCycles: 12,
  },
  token,
);

Builds the approve + initialize_subscription transaction, signs and confirms it, then registers the new subscription with the Recur API. Returns the on-chain PDA, the transaction signature, and the registered SubscriptionInfo.

client.cancel(wallet, options)

await client.cancel(wallet, {
  merchantWallet,
  planSeed,
  mode: "request", // default; "instant" forfeits prepaid time
});
  • "request"request_cancel instruction (prepaid period honored)
  • "instant"subscriber_cancel instruction (immediate close)

client.reapprove(wallet, options)

await client.reapprove(wallet, {
  merchantWallet,
  planSeed,
  amount: Number(plan.amountBaseUnits),
  delegationCycles: 24,
});

Re-runs spl-token approve for the existing subscription PDA so the keeper can pull again.

Errors

Every method throws a typed subclass of RecurError:

ErrorWhen
WalletRejectedErrorUser declined the prompt
InsufficientFundsErrorSubscriber's USDC balance can't cover the next payment
DelegationExhaustedErrorApproval used up — call reapprove()
PlanInactiveErrorPlan was disabled by the merchant
SubscriptionAlreadyExistsErrorSubscriber already has an active sub on this plan
NetworkErrorRPC or API unreachable
AuthErrorJWT invalid/expired

Check with instanceof. See the full error reference.

On this page