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

# Error Handling and Retries with the Go SDK

> Configure automatic retries in the ZenRows Go SDK and handle its custom error types.

The Go SDK separates two kinds of failure: errors returned before a request is even sent (bad configuration, invalid parameters, malformed URLs), and unsuccessful HTTP responses that still come back as a valid `*Response`. Automatic retries only apply to the second kind.

## Basic usage

Retries are **off by default** (`maxRetryCount` is `0`). Pass `WithMaxRetryCount` to turn them on:

```go theme={null} theme={null}
client := scraperapi.NewClient(
    scraperapi.WithAPIKey("YOUR_ZENROWS_API_KEY"),
    scraperapi.WithMaxRetryCount(3),
)

response, err := client.Get(context.Background(), "https://www.scrapingcourse.com/ecommerce/", nil)
```

With `WithMaxRetryCount(3)`, a failed request is retried up to 3 additional times before the SDK returns the last response or error it received.

## When to enable retries

Enable retries (2 to 3 is a reasonable starting point) whenever you're running requests at scale or in production, anywhere a transient `429` or `500` shouldn't fail your whole job. Leave retries off (the default) for quick one-off scripts or debugging, where you'd rather see the raw failure immediately.

## Which failures get retried

The SDK retries a request when either of these is true:

* The request failed at the network level (DNS failure, connection reset, timeout), regardless of status code.
* The response came back with one of these status codes:

| Status code | Meaning               |
| ----------- | --------------------- |
| `422`       | Unprocessable Entity  |
| `429`       | Too Many Requests     |
| `500`       | Internal Server Error |

<Warning>
  `502`, `503`, and `504` are **not** retried automatically by the Go SDK. If your use case needs to retry those too, check `response.StatusCode()` yourself and re-issue the request in a loop.
</Warning>

Between attempts, the SDK waits using `WithRetryWaitTime` as a starting point (default 5 seconds), doubling on each subsequent attempt, capped at `WithRetryMaxWaitTime` (default 30 seconds):

```go theme={null} theme={null}
client := scraperapi.NewClient(
    scraperapi.WithAPIKey("YOUR_ZENROWS_API_KEY"),
    scraperapi.WithMaxRetryCount(5),
    scraperapi.WithRetryWaitTime(20*time.Second),
    scraperapi.WithRetryMaxWaitTime(25*time.Second),
)
```

<Tip>
  You aren't charged for retried attempts that fail. Only successful responses count toward your usage. See the [Retry Failed Requests](/zenrows-academy/retry-failed-requests) guide for more on how this works.
</Tip>

## Handling failures

<Tabs>
  <Tab title="HTTP error status">
    Even after retries are exhausted, the SDK returns a `*Response` with `err == nil`. It doesn't turn a non-2xx status into a Go `error` on its own. Always check the response before trusting its content:

    ```go theme={null} theme={null}
    response, err := client.Get(context.Background(), url, nil)
    if err != nil {
        log.Fatal(err)
    }

    if response.IsSuccess() {
        fmt.Println(response.String())
    } else {
        fmt.Println("Failed after retries:", response.StatusCode())
        if prob := response.Problem(); prob != nil {
            fmt.Println("Detail:", prob.Detail)
        }
    }
    ```
  </Tab>

  <Tab title="SDK and network errors">
    The other four cases return a non-`nil` `error` instead of a response, before any HTTP call is attempted (except for genuine network failures, which happen mid-request):

    ```go theme={null} theme={null}
    response, err := client.Get(ctx, targetURL, params)
    if err != nil {
        var notConfigured scraperapi.NotConfiguredError
        var invalidMethod scraperapi.InvalidHTTPMethodError
        var invalidURL scraperapi.InvalidTargetURLError
        var invalidParam scraperapi.InvalidParameterError

        switch {
        case errors.As(err, &notConfigured):
            log.Fatal("missing API key")
        case errors.As(err, &invalidMethod):
            log.Fatal("unsupported HTTP method")
        case errors.As(err, &invalidURL):
            log.Fatal("bad target URL: ", err)
        case errors.As(err, &invalidParam):
            log.Fatal("bad request parameters: ", err)
        default:
            log.Fatal("network or unexpected error: ", err)
        }
    }
    ```

    <Note>
      Use `context.WithTimeout(ctx, 3*time.Minute)` if you want a request canceled after a fixed duration. We recommend not going below 3 minutes, since canceling a request client-side doesn't immediately free up the concurrency slot on ZenRows' side.
    </Note>
  </Tab>
</Tabs>

## The four SDK error types

| Error type               | When it happens                                                                                                                                                   |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NotConfiguredError`     | No API key was set (neither `WithAPIKey` nor `ZENROWS_API_KEY`).                                                                                                  |
| `InvalidHTTPMethodError` | You called `Scrape` with a method other than `GET`, `POST`, or `PUT`.                                                                                             |
| `InvalidTargetURLError`  | The target URL is empty or fails to parse; wraps the underlying `url.Parse` error via `Unwrap()`.                                                                 |
| `InvalidParameterError`  | A `RequestParameters` field fails validation, for example, `ScreenshotQuality` outside 1 to 100, or `Screenshot: false` combined with `ScreenshotFullPage: true`. |

All four are returned as values (not pointers), and `InvalidTargetURLError` supports `errors.Unwrap` so you can inspect the underlying parse error with `errors.As` or `errors.Is`.

## Troubleshooting

* **Response still fails after retries are exhausted**: Check `response.StatusCode()` and look it up in [API Error Codes](/api-error-codes). Some failures, like an invalid API key, aren't fixed by retrying.
* **Getting `InvalidParameterError` for a parameter you didn't set**: Several `RequestParameters` fields depend on each other, for example, `ScreenshotFullPage` requires `Screenshot: true`, and `WaitForSelector` requires `JSRender: true`. Check `Validate()`'s rules in the SDK source if the message isn't specific enough.
* **`429` errors even with retries enabled**: You're likely exceeding your plan's concurrency limit, not hitting a one-off rate limit. See [Concurrency](/universal-scraper-api/sdk/go/async-concurrency).
* **Retries seem to take a long time**: That's the doubling backoff by design, capped at `WithRetryMaxWaitTime`. Lower `WithMaxRetryCount` or `WithRetryMaxWaitTime` if latency matters more than success rate.

## Pricing

Retried attempts that fail are **not** billed. Only the final successful response counts:

| Configuration                                | Cost multiplier vs. basic |
| -------------------------------------------- | ------------------------- |
| Basic request                                | 1x                        |
| `JSRender: true`                             | 5x                        |
| `UsePremiumProxies: true`                    | 10x                       |
| `JSRender: true` + `UsePremiumProxies: true` | 25x                       |

<Note>`404` and `410` responses count as successful and are billed, since the request itself completed correctly.</Note>

## FAQ

<AccordionGroup>
  <Accordion title="What's the default number of retries?">
    Zero. Retries are disabled unless you explicitly pass `WithMaxRetryCount(n)` with `n > 0`.
  </Accordion>

  <Accordion title="Which status codes trigger an automatic retry?">
    `422`, `429`, and `500`, plus any network-level failure. `502`, `503`, and `504` are not retried automatically.
  </Accordion>

  <Accordion title="Does the SDK ever return a Go error for a non-2xx response?">
    No. A non-2xx HTTP response is still a successful round trip from the SDK's point of view, it returns a `*Response` with `err == nil`. Check `response.IsError()` or `response.StatusCode()` yourself.
  </Accordion>
</AccordionGroup>

## Look up a specific error code

For a full breakdown of ZenRows error codes (like `AUTH002` or `RESP001`) and how to resolve each one, see the [API Error Codes](/api-error-codes) reference.
