> ## 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 Go SDK.

Requires `github.com/limitless-labs-group/limitless-exchange-go-sdk` **v1.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

The following snippets belong to the same Go package:

```go theme={null}
package packs

import (
    "context"
    "fmt"
    "os"
    "strconv"
    "time"

    "github.com/limitless-labs-group/limitless-exchange-go-sdk/limitless"
)

func newClient() *limitless.Client {
    return limitless.NewClient(limitless.WithHMACCredentials(limitless.HMACCredentials{
        TokenID: os.Getenv("LMTS_TOKEN_ID"),
        Secret: os.Getenv("LMTS_TOKEN_SECRET"),
    }))
}
```

Use `trading` and `delegated_signing` scopes, plus `account_creation` for allowances. Reuse the client and do not configure a global `x-on-behalf-of` header.

## Browse and check approval

```go theme={null}
func checkVaultApproval(ctx context.Context, client *limitless.Client, profileID int) error {
    state, err := client.PartnerAccounts.CheckAllowances(ctx, profileID)
    if err != nil {
        return err
    }
    for _, target := range state.Targets {
        if target.Type == "USDC_ALLOWANCE" && target.Label == "parlay-treasury-vault" {
            if target.Confirmed && target.Status == "confirmed" {
                return nil
            }
        }
    }
    return fmt.Errorf("confirm the Pack vault allowance before preparing a buy")
}
```

Use `client.Packs.List(ctx)`, `Search(ctx, "football")`, or `Get(ctx, packID)` to browse. If approval is missing, call `client.PartnerAccounts.RetryAllowances(ctx, 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).

## Prepare once, then submit the saved request

```go theme={null}
func preparePurchase(
    ctx context.Context, client *limitless.Client,
    profileID int, packID, purchaseKey string,
) (limitless.PackBuyParams, error) {
    if err := checkVaultApproval(ctx, client, profileID); err != nil {
        return limitless.PackBuyParams{}, err
    }
    quote, err := client.Packs.Quote(ctx, limitless.PackQuoteParams{
        PackID: packID, StakeAmount: "10000000",
    })
    if err != nil {
        return limitless.PackBuyParams{}, err
    }
    if !quote.Eligible {
        return limitless.PackBuyParams{}, fmt.Errorf("ineligible: %v", quote.IneligibleReasons)
    }
    minimum, err := limitless.PackMinPayout(quote.PotentialPayout, 100)
    if err != nil {
        return limitless.PackBuyParams{}, err
    }
    return limitless.PackBuyParams{
        OnBehalfOf: profileID,
        PackID: packID,
        StakeAmount: "10000000", // 10 USDC
        MinPayoutAmount: minimum,
        Deadline: strconv.FormatInt(time.Now().Unix()+120, 10),
        IdempotencyKey: purchaseKey,
    }, nil
}

func submitSavedPurchase(
    ctx context.Context, client *limitless.Client, saved limitless.PackBuyParams,
) (*limitless.PackPosition, error) {
    buy, err := client.Packs.Buy(ctx, saved)
    if err != nil {
        return nil, err
    }
    return client.Packs.WaitForPosition(ctx, saved.OnBehalfOf, buy.Position.ID)
}
```

Durably save the complete `PackBuyParams` before submission, using `encoding/json` and your application store. Associate the record with its environment, partner, and managed account. On restart, reload it; do not call preparation again for the same purchase.

`WaitForPosition` continues through position `FAILED` and stops on `VAULT_REVERTED`. It polls up to 60 times at 5-second intervals; the context can cancel the wait. On timeout or an ambiguous response, retain and replay the saved request unchanged, including its deadline. See [explicit renewal signals](/developers/partner-packs.md#recover-buy-errors).

## Read positions and claim

```go theme={null}
func claimWinningPosition(
    ctx context.Context, client *limitless.Client, profileID int, positionID string,
) (*limitless.PackClaimResponse, error) {
    position, err := client.Packs.GetPosition(ctx, profileID, positionID)
    if err != nil {
        return nil, err
    }
    if position.Status != "WON" {
        return nil, fmt.Errorf("position is not WON")
    }
    return client.Packs.Claim(ctx, profileID, positionID)
}
```

Use `ListPositions(ctx, profileID, 20, 0)` to list purchases, `ClaimOnce(ctx, profileID, positionID)` for one claim step, and `ConfirmClaim(ctx, profileID, positionID, redemptionTxHash)` to recover a lost submission response with the mined redemption receipt.

After claiming, the position remains `WON`; verify `ClaimTxHash` or the claim response's `CLAIMED` status. Log `position.Wallet` to identify the managed recipient.

## Classify errors

`limitless.ClassifyPackError(err)` returns typed `PackErrorDetails`. Its `Kind` is one of `PackErrorAllowanceRequired`, `PackErrorNotSubmitted`, `PackErrorPriceDrifted`, `PackErrorIntentExpired`, `PackErrorWalletBusy`, `PackErrorRateLimited`, `PackErrorSubmissionUnknown`, `PackErrorTransactionReverted`, or `PackErrorOther`.

The result includes `TokenAddress`, `Spender`, `RequiredAllowance`, `SubmissionState`, `ParlayID`, and `Recovery` when supplied by the API. Classification does not retry.

The claim helper surfaces API errors. Back off on rate limits and wallet-busy conflicts, then resume the same position. For `PackErrorSubmissionUnknown`, 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.