A Go API client needs to bound simultaneous requests, retry attempts and caller deadlines independently. Waiting for a work slot must remain cancellable, and a retry must release response resources before another request. Account concurrency is not a fixed requests-per-second guarantee.
A complete local-contract client example
package fixtures
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
)
type Organic struct {
Position int `json:"position"`
Title string `json:"title"`
Link string `json:"link"`
}
type Response struct {
Code int `json:"code"`
Data struct { Organic []Organic `json:"organic"` } `json:"data"`
}
// Search uses caller cancellation, bounded attempts and one acquired lane.
// A fixed delay is used for the fixture; production should respect Retry-After.
func Search(ctx context.Context, client *http.Client, endpoint, key, query string,
lanes chan struct{}, maxAttempts int) ([]Organic, error) {
if maxAttempts < 1 { return nil, errors.New("positive retry budget required") }
select {
case lanes <- struct{}{}: defer func() { <-lanes }()
case <-ctx.Done(): return nil, ctx.Err()
}
body, err := json.Marshal(map[string]any{"t":"google", "s":query, "country":"us", "language":"en"})
if err != nil { return nil, err }
for attempt := 0; attempt < maxAttempts; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(body))
if err != nil { return nil, err }
req.Header.Set("Authorization", "Bearer " + key)
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil { return nil, err }
retry := resp.StatusCode == 429 || resp.StatusCode >= 500
if retry {
io.Copy(io.Discard, io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if attempt + 1 == maxAttempts { return nil, fmt.Errorf("retry budget exhausted: HTTP %d", resp.StatusCode) }
select {
case <-time.After(10 * time.Millisecond): continue
case <-ctx.Done(): return nil, ctx.Err()
}
}
defer resp.Body.Close()
if resp.StatusCode != 200 { return nil, fmt.Errorf("HTTP %d", resp.StatusCode) }
var parsed Response
if err := json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(&parsed); err != nil { return nil, err }
if parsed.Code != 0 { return nil, fmt.Errorf("business code %d", parsed.Code) }
return parsed.Data.Organic, nil
}
return nil, errors.New("unreachable")
}
This client was exercised with local httptest servers using Go 1.27.2. The tests cover nested organic parsing, a transient 429 followed by success, a non-retried authentication failure, cancellation while waiting for capacity and peak concurrency no greater than three. No live-provider throughput test was performed.
Replace fixture timing before production use
The example uses a short fixed delay to make its local test deterministic. A production policy must respect Retry-After when supplied, apply a bounded backoff/jitter policy and keep the delay within the caller deadline. Decide which business errors are retryable from the actual documented contract.
Create the lane channel from the account capacity and application limit. A larger channel does not create additional account lanes. Preserve attempt count and final failure status for the caller.
Know what the example does not implement
Use the separate Go JSON guide for the response struct and the enterprise failure-budget guide for system-level fallback decisions.
- It is not a distributed account scheduler across multiple processes.
- It does not classify every provider-specific business error.
- It does not measure live latency or promise a throughput level.
- Its tests do not evaluate SERP relevance or generated-answer quality.
Reference documentation: pkg.go.dev — /net/http; pkg.go.dev — /context; www.searchcans.com — /google-search-api.