> ## Documentation Index
> Fetch the complete documentation index at: https://rain-sandbox-trial.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Configure error handling, retries, timeouts, pagination, and logging for the Rain SDK.

Configure retries, timeouts, pagination, and logging to fit your integration. This page covers all SDK options beyond the defaults.

## Error handling

The SDK throws typed errors for every HTTP status code. Catch them to handle specific failure modes:

<CodeGroup>
  ```ts TypeScript theme={null}
  import Rain from '@rainapi/rain-sdk';

  try {
    const card = await client.cards.retrieve('card_123');
  } catch (error) {
    if (error instanceof Rain.AuthenticationError) {
      // 401 — invalid or expired API key
      console.error('Check your API key in the Dashboard');
    } else if (error instanceof Rain.NotFoundError) {
      // 404 — resource doesn't exist
      console.error('Card not found');
    } else if (error instanceof Rain.RateLimitError) {
      // 429 — too many requests
      console.error('Rate limited, try again later');
    } else {
      throw error;
    }
  }
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"errors"
  	"fmt"

  	rainsdk "github.com/SignifyHQ/rain-sdk-go"
  )

  func main() {
  	_, err := client.Cards.Get(context.TODO(), "card_123")
  	var apierr *rainsdk.Error
  	if errors.As(err, &apierr) {
  		switch apierr.StatusCode {
  		case 401:
  			fmt.Println("Check your API key in the Dashboard")
  		case 404:
  			fmt.Println("Card not found")
  		case 429:
  			fmt.Println("Rate limited, try again later")
  		default:
  			panic(err)
  		}
  	}
  }
  ```

  ```python Python theme={null}
  from rain_sdk import Rain, AuthenticationError, NotFoundError, RateLimitError

  try:
      card = client.cards.retrieve("card_123")
  except AuthenticationError:
      # 401 — invalid or expired API key
      print("Check your API key in the Dashboard")
  except NotFoundError:
      # 404 — resource doesn't exist
      print("Card not found")
  except RateLimitError:
      # 429 — too many requests
      print("Rate limited, try again later")
  ```
</CodeGroup>

Each HTTP status code maps to a typed error class:

| Error class                | Status | When it happens                            |
| -------------------------- | ------ | ------------------------------------------ |
| `BadRequestError`          | 400    | Missing or invalid parameters              |
| `AuthenticationError`      | 401    | Invalid or expired API key                 |
| `PermissionDeniedError`    | 403    | Key lacks required permissions             |
| `NotFoundError`            | 404    | Resource doesn't exist                     |
| `ConflictError`            | 409    | Conflicting update (retried automatically) |
| `UnprocessableEntityError` | 422    | Valid syntax but unprocessable request     |
| `RateLimitError`           | 429    | Too many requests (retried automatically)  |
| `InternalServerError`      | 500+   | Server-side issue (retried automatically)  |

In Go, every API error shares the type `*rainsdk.Error`. Switch on `apierr.StatusCode` to branch on the HTTP status.

## Retries

By default, the SDK retries connection errors, 408, 409, 429, and 5xx responses up to 2 times with exponential backoff.

<CodeGroup>
  ```ts TypeScript theme={null}
  // Set globally
  const client = new Rain({ maxRetries: 5 });

  // Override per request
  const card = await client.cards.retrieve('card_123', { maxRetries: 0 });
  ```

  ```go Go theme={null}
  // Set globally
  client := rainsdk.NewClient(
  	option.WithAPIKey(os.Getenv("RAIN_API_KEY")),
  	option.WithMaxRetries(5),
  )

  // Override per request
  card, err := client.Cards.Get(context.TODO(), "card_123", option.WithMaxRetries(0))
  ```

  ```python Python theme={null}
  # Set globally
  client = Rain(max_retries=5)

  # Override per request
  card = client.cards.retrieve("card_123", max_retries=0)
  ```
</CodeGroup>

## Timeouts

Requests time out after 60 seconds by default. The SDK retries timed-out requests.

<CodeGroup>
  ```ts TypeScript theme={null}
  // Set globally (in milliseconds)
  const client = new Rain({ timeout: 30_000 });

  // Override per request
  const txns = await client.transactions.list({}, { timeout: 120_000 });
  ```

  ```go Go theme={null}
  // Set the per-attempt request timeout on the client
  client := rainsdk.NewClient(
  	option.WithAPIKey(os.Getenv("RAIN_API_KEY")),
  	option.WithRequestTimeout(30*time.Second),
  )

  // Use the request context for an overall deadline (covers all retries)
  ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
  defer cancel()
  txns, err := client.Transactions.List(ctx, rainsdk.TransactionListParams{})
  ```

  ```python Python theme={null}
  # Set globally (in seconds)
  client = Rain(timeout=30)

  # Override per request
  txns = client.transactions.list(timeout=120)
  ```
</CodeGroup>

<Info>
  The Go SDK does not apply a default request timeout. Always pass a `context.Context` with a deadline appropriate for your workload — `option.WithRequestTimeout` only governs each individual attempt.
</Info>

## Pagination

List endpoints use cursor-based pagination. Pass `cursor` and `limit` to page through results:

<CodeGroup>
  ```ts TypeScript theme={null}
  let cursor: string | undefined;

  do {
    const transactions = await client.transactions.list({
      companyId: 'company_123',
      type: ['spend'],
      limit: 50,
      cursor,
    });

    for (const txn of transactions) {
      console.log(txn.id, txn.type);
    }

    // Use the last item's ID as the cursor for the next page
    cursor = transactions.length === 50
      ? transactions[transactions.length - 1].id
      : undefined;
  } while (cursor);
  ```

  ```go Go theme={null}
  var cursor param.Opt[string]

  for {
  	page, err := client.Transactions.List(context.TODO(), rainsdk.TransactionListParams{
  		CompanyID: rainsdk.String("company_123"),
  		Type:      []string{"spend"},
  		Limit:     rainsdk.Int(50),
  		Cursor:    cursor,
  	})
  	if err != nil {
  		panic(err)
  	}

  	for _, txn := range *page {
  		fmt.Println(txn.ID, txn.Type)
  	}

  	// Stop once the API returns a partial page
  	if len(*page) < 50 {
  		break
  	}
  	cursor = rainsdk.String((*page)[len(*page)-1].ID)
  }
  ```

  ```python Python theme={null}
  cursor = None

  while True:
      transactions = client.transactions.list(
          company_id="company_123",
          type=["spend"],
          limit=50,
          cursor=cursor,
      )

      for txn in transactions:
          print(txn.id, txn.type)

      # Use the last item's ID as the cursor for the next page
      if len(transactions) == 50:
          cursor = transactions[-1].id
      else:
          break
  ```
</CodeGroup>

In Go, `List` returns `*[]T` — dereference the pointer to iterate. The cursor parameter is `param.Opt[string]`; import the helper from `github.com/SignifyHQ/rain-sdk-go/packages/param` and use `rainsdk.String(...)` to set it.

## Logging

Control SDK log output with the `RAIN_LOG` environment variable or the `logLevel` client option:

<CodeGroup>
  ```ts TypeScript theme={null}
  // Via environment variable
  // RAIN_LOG=debug node app.js

  // Or in code
  const client = new Rain({ logLevel: 'debug' });
  // Levels: 'debug' | 'info' | 'warn' | 'error' | 'off'
  ```

  ```go Go theme={null}
  client := rainsdk.NewClient(
  	option.WithAPIKey(os.Getenv("RAIN_API_KEY")),
  	option.WithDebugLog(log.Default()),
  )
  ```

  ```python Python theme={null}
  # Via environment variable
  # RAIN_LOG=debug python app.py

  # Or in code
  client = Rain(log_level="debug")
  ```
</CodeGroup>

The Go SDK exposes a single debug toggle — pass `option.WithDebugLog(nil)` to log to stderr, or any `*log.Logger` to direct output elsewhere. There are no log levels.

<Warning>
  Debug logging may expose sensitive request and response data. Only use `debug` level in development.
</Warning>

## Idempotency

Pass an `Idempotency-Key` header to safely retry write operations without duplicate side effects:

<CodeGroup>
  ```ts TypeScript theme={null}
  const card = await client.users.createCard(
    userId,
    { type: 'virtual' },
    { headers: { 'Idempotency-Key': 'unique-request-id' } },
  );
  ```

  ```go Go theme={null}
  card, err := client.Users.NewCard(
  	context.TODO(),
  	userID,
  	rainsdk.UserNewCardParams{Type: rainsdk.UserNewCardParamsTypeVirtual},
  	option.WithHeader("Idempotency-Key", "unique-request-id"),
  )
  ```

  ```python Python theme={null}
  card = client.users.create_card(
      user_id,
      type="virtual",
      extra_headers={"Idempotency-Key": "unique-request-id"},
  )
  ```
</CodeGroup>

See [Idempotency](/reference/idempotency) for how Rain processes idempotent requests.

## What's next

<Columns cols={3}>
  <Card title="Card Issuance" icon="credit-card" href="/sdks/card-issuance">
    Issue your first card end-to-end with the SDK.
  </Card>

  <Card title="Transaction Monitoring" icon="chart-line" href="/sdks/transaction-monitoring">
    List, filter, and manage transactions.
  </Card>

  <Card title="Funding & Transfers" icon="wallet" href="/sdks/funding-and-transfers">
    Fund cards with collateral deposits.
  </Card>
</Columns>
