Handle Go SDK errors and recover interrupted tasks

By solvethree4 min read

A timeout doesn't mean task creation failed. Keep the original task inputs and the SDK's request ID. If you have a task ID, resume with GetTask; otherwise, replay CreateTask with the exact original inputs and the same request ID.

Cancellation stops the Go client from waiting, not the remote task. Recover that task before paying for a new solve.

On this page

Run the error-handling example

Use Go 1.22 or later with the official solvethree.com/sdk module, version v0.1.0. See the Go integration instructions for setup.

Set the server-side environment variables SOLVETHREE_API_KEY and SOLVETHREE_PROXY. Replace the example's Site and Action with your target hostname and action. The site needs an active profile in your workspace; sending its hostname doesn't create one. This reCAPTCHA request uses a single proxy and doesn't need Type or URL.

Go · inspect errors and retain recovery IDs
package main

import (
    "context"
    "fmt"
    "errors"
    "log"
    "os"
    "time"
    solvethree "solvethree.com/sdk"
)

func main() {
    client, err := solvethree.NewClient(os.Getenv("SOLVETHREE_API_KEY"), nil)
    if err != nil { log.Fatal(err) }
    ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
    defer cancel()
    input := solvethree.TaskRequest{Site: "accounts.example.com", Action: "login", Proxy: os.Getenv("SOLVETHREE_PROXY")}
    result, err := client.Solve(ctx, input, solvethree.SolveOptions{})
    if err != nil {
        var failed *solvethree.TaskFailedError
        var api *solvethree.APIError
        var recovery *solvethree.Error
        switch {
        case errors.As(err, &failed):
            log.Printf("Failed task %s: %s", failed.TaskID, failed.ErrorCode)
        case errors.As(err, &recovery):
            log.Printf("Recovery request=%s task=%s", recovery.RequestID, recovery.TaskID)
            if errors.As(err, &api) { log.Printf("HTTP %d; retry delay %s", api.StatusCode, api.RetryAfter) }
            // Save input plus recovery.RequestID. Resume GetTask for a known TaskID;
            // otherwise replay CreateTask with identical inputs and that RequestID.
        }
        return
    }
    fmt.Println("Ready task:", result.ID)
    // Submit result.Solution.Token with its browser identity and original proxy.
}

The example sets a 180-second context deadline and logs recovery IDs when an SDK error exposes them. Logging isn't durable recovery: save the original input, the returned RequestID, and any TaskID before discarding the error.

“Ready task” means a solution is available, not that the target accepted it. The SDK doesn't submit it for you. Submit Solution.Token using the returned browser identity and, for this single-proxy example, the original proxy.

Distinguish a failed task from an interrupted request

Inspect wrapped errors with errors.As and cancellation with errors.Is. These checks answer different questions: one error can contain recovery IDs, wrap an HTTP error, and still need to be handled as an interrupted operation.

What each error tells you
What each error tells you
CheckMeaning and next action
errors.As for *solvethree.TaskFailedErrorThe task reached terminal failure. Read TaskID, RequestID, and ErrorCode. Retrieve the failed task with GetTask for its Error text, then stop polling that task. Don't blindly resubmit unchanged inputs.
errors.Is for context.DeadlineExceeded or context.CanceledThe caller stopped waiting. This doesn't establish whether creation succeeded or the remote task finished. Retain the recovery information and resume with a fresh context when appropriate.
errors.As for *solvethree.ErrorThe SDK wrapper exposes RequestID and TaskID and wraps its underlying Cause. Save the IDs alongside the exact original inputs.
errors.As for *solvethree.APIErrorInspect the HTTP StatusCode and RetryAfter duration. An HTTP error is different from a task whose status is failed; don't assume you received a successful task payload.

The example checks for a nested APIError inside its recovery branch. Check cancellation separately with errors.Is if your application needs to distinguish a deadline from an explicit cancellation.

Solve creates the task once. It retries only temporary polling responses with HTTP 429, 502, 503, or 504, honoring Retry-After within the context deadline. CreateTask never automatically retries its POST.

Resume the existing task

  1. 1.

    Keep the original request intact

    Save all task inputs, including the proxy or candidate list, together with the request ID. CreateTask generates a request ID when you omit it; capture the generated value exposed by the recovery error. Don't reconstruct the request later from configuration that may have changed.

  2. 2.

    Use GetTask when you know the task ID

    Create a fresh context if the old one expired or was canceled, then retrieve the saved TaskID with GetTask. Continue waiting for pending or solving, consume the solution only for ready, and stop on failed.

  3. 3.

    Replay creation only when you don't know the task ID

    Set RequestID on the saved original TaskRequest to the recovered request ID, then call CreateTask with those same inputs. Reusing the same ID and request returns the existing task. Changing the inputs while reusing that ID causes a conflict.

Don't use a new request ID merely because the connection timed out. That starts a new logical task rather than recovering the uncertain one. First establish what happened to the original task; if it failed, inspect its error before deciding whether a corrected request is needed.

Run your first task.

Start with $5 in workspace credit.

Create a workspace