> ## Documentation Index
> Fetch the complete documentation index at: https://docs.limitless.exchange/llms.txt
> Use this file to discover all available pages before exploring further.

# Curated Packs

> Quote, buy, track, and claim curated Packs with the TypeScript SDK.

Requires `@limitless-exchange/sdk` **1.3.0 or later**. Read the [Partner Packs workflow](/developers/partner-packs.md) for account ownership, durable retries, amounts, and recovery rules.

## Configure a server-side client

```typescript theme={null}
import {
  Client, PackBuyParams, packMinPayout, classifyPackError,
} from '@limitless-exchange/sdk';

const client = new Client({
  hmacCredentials: {
    tokenId: process.env.LMTS_TOKEN_ID!,
    secret: process.env.LMTS_TOKEN_SECRET!,
  },
});
```

Use `trading` and `delegated_signing` scopes, plus `account_creation` for allowance preflight. Do not configure a global `x-on-behalf-of` header.

## Browse and check approval

```typescript theme={null}
async function checkVaultApproval(profileId: number) {
  const state = await client.partnerAccounts.checkAllowances(profileId);
  const vault = state.targets.find(
    target => target.type === 'USDC_ALLOWANCE'
      && target.label === 'parlay-treasury-vault',
  );
  if (!vault?.confirmed || vault.status !== 'confirmed') {
    throw new Error('Confirm the Pack vault allowance before preparing a buy');
  }
  return state.walletAddress;
}

async function browse() {
  return client.packs.list();
  // Also: client.packs.search('football'), client.packs.get(packId)
}
```

If the target is missing approval, call `client.partnerAccounts.retryAllowances(profileId)` and poll `checkAllowances` with a bounded wait until the vault is confirmed. Repair uses the [unlimited-approval policy](/developers/partner-packs.md#2-confirm-the-pack-vault-allowance). Complete this before creating the short-lived intent.

## Prepare once, then submit the saved request

```typescript theme={null}
async function preparePurchase(
  profileId: number, packId: string, purchaseKey: string,
): Promise<PackBuyParams> {
  await checkVaultApproval(profileId);
  const quote = await client.packs.quote({ packId, stakeAmount: '10000000' });
  if (!quote.eligible) {
    throw new Error(`Ineligible: ${(quote.ineligibleReasons ?? []).join('; ')}`);
  }
  if (!quote.potentialPayout) throw new Error('Missing quoted payout');
  return {
    onBehalfOf: profileId,
    packId,
    stakeAmount: '10000000', // 10 USDC
    minPayoutAmount: packMinPayout(quote.potentialPayout, 100),
    deadline: String(Math.floor(Date.now() / 1000) + 120),
    idempotencyKey: purchaseKey,
  };
}

async function submitSavedPurchase(saved: PackBuyParams) {
  const buy = await client.packs.buy(saved);
  return client.packs.waitForPosition(saved.onBehalfOf, buy.position.id, {
    maxAttempts: 60,
    intervalMs: 5000,
  });
}
```

The application must durably save the result of `preparePurchase` before calling `submitSavedPurchase`. On restart, load that record; do not call preparation again for the same intended purchase. Associate the record with the environment, partner, and managed account.

`waitForPosition` continues through position `FAILED` and stops on `VAULT_REVERTED`. On timeout or an ambiguous buy response, retain and replay the saved request unchanged, even if its deadline has passed. Only [explicit rejection signals](/developers/partner-packs.md#recover-buy-errors) permit renewal.

## Read positions and claim

```typescript theme={null}
async function readPositions(profileId: number) {
  return client.packs.listPositions(profileId, 20, 0);
}

async function claimWinningPosition(profileId: number, positionId: string) {
  const position = await client.packs.getPosition(profileId, positionId);
  if (position.status !== 'WON') throw new Error('Position is not WON');
  const claim = await client.packs.claim(profileId, positionId, {
    maxAttempts: 60,
    intervalMs: 5000,
  });
  return { wallet: position.wallet, txHash: claim.txHash };
}

async function recoverClaim(
  profileId: number, positionId: string, redemptionTxHash: string,
) {
  return client.packs.confirmClaim(profileId, positionId, redemptionTxHash);
}
```

Use `claimOnce(profileId, positionId)` for one workflow step. Polling options also accept an `AbortSignal`. After claiming, the position remains `WON`; verify `claimTxHash` or the claim response's `CLAIMED` status.

## Classify errors

```typescript theme={null}
function recoveryDetails(error: unknown) {
  const details = classifyPackError(error);
  // details.kind: ALLOWANCE_REQUIRED, NOT_SUBMITTED, PRICE_DRIFTED,
  // INTENT_EXPIRED, WALLET_BUSY, RATE_LIMITED, SUBMISSION_UNKNOWN,
  // TRANSACTION_REVERTED, or OTHER.
  return details;
}
```

For allowance errors, the typed result exposes `tokenAddress`, `spender`, and `requiredAllowance`. For pre-broadcast failures it exposes `submissionState`, `parlayId`, and `recovery`. The classifier does not retry.

The claim helper surfaces API errors. Back off on rate limits and wallet-busy conflicts, then resume the same position. For `SUBMISSION_UNKNOWN`, reconcile the mined redemption transaction and use `confirmClaim`; do not blindly resubmit.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.