Blog
 / 
September 29, 2026

How to Use the You.com Web Search API in Go

How to Use the You.com Web Search API in Go

TLDR: You call the You.com Web Search API from Go by POSTing a JSON body with your query to https://ydc-index.io/v1/search with your API key in the X-API-Key header, then decoding the results.web array into structs. This guide shows the first call with the standard library, typed response structs, error handling for the statuses that matter, and the retry and timeout patterns that keep a Go service stable.

Go services talk to search APIs constantly, and the failure modes are the same every time: no request timeout, swallowed non-2xx bodies, and retries that hammer a rate limit. This guide does the integration properly with only the standard library, so it works in any Go codebase with no dependencies. The endpoint, headers, and response shape are the same ones used across our Python guide and cURL guide, so you can cross-check the raw JSON in a terminal while you build. Get your API key from You.com first, and keep the Web Search API guide open for the full parameter reference.

What Do You Need Before You Call the API?

You need an API key, an environment variable to hold it, and nothing else. Export your key before running the service, and never hardcode it.

export YDC_API_KEY="your-key-here"

The request is a POST with a JSON body, so there is no URL encoding to get wrong and no query string to log by accident. The two required pieces are the query and the API key header.

How Do You Make the First Call?

The full call is about 40 lines with the standard library. The structs below decode the fields you normally consume, and extra response fields are ignored by the JSON decoder, which keeps the code stable as the API adds fields.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "time"
)

type SearchRequest struct {
    Query string `json:"query"`
    Count int    `json:"count,omitempty"`
}

type SearchResult struct {
    Results struct {
        Web []struct {
            Title       string `json:"title"`
            URL         string `json:"url"`
            Description string `json:"description"`
        } `json:"web"`
    } `json:"results"`
}

func search(query string) (*SearchResult, error) {
    payload, err := json.Marshal(SearchRequest{Query: query, Count: 5})
    if err != nil {
        return nil, fmt.Errorf("marshal request: %w", err)
    }

    req, err := http.NewRequest(http.MethodPost,
        "https://ydc-index.io/v1/search",
        bytes.NewReader(payload))
    if err != nil {
        return nil, fmt.Errorf("build request: %w", err)
    }
    req.Header.Set("X-API-Key", os.Getenv("YDC_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    // A client with a timeout is not optional in production.
    // The zero-value http.Client has no timeout at all.
    client := &http.Client{Timeout: 15 * time.Second}

    resp, err := client.Do(req)
    if err != nil {
        return nil, fmt.Errorf("search request: %w", err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("search failed with HTTP %d", resp.StatusCode)
    }

    var result SearchResult
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, fmt.Errorf("decode response: %w", err)
    }
    return &result, nil
}

func main() {
    result, err := search("go http client retry best practice")
    if err != nil {
        fmt.Fprintln(os.Stderr, "error:", err)
        os.Exit(1)
    }
    for i, r := range result.Results.Web {
        fmt.Printf("%d. %s\n   %s\n", i+1, r.Title, r.URL)
    }
}

How Do You Handle Errors and Retries?

Three statuses carry distinct meanings in any search API integration, and the HTTP status semantics that define them are specified in RFC 9110, with a readable summary of each code on MDN's HTTP status reference.

  • 401 Unauthorized: the key is missing, malformed, or revoked. Retrying with the same key cannot work, so fail fast and surface the problem to configuration, not to the user.
  • 429 Too Many Requests: the rate limit. Retry, but with a pause and preferably the retry guidance in the response headers, never immediately.
  • 5xx: a server-side failure. Retry with backoff, because it is usually transient.

A small retry helper with exponential backoff covers the retryable classes:

func searchWithRetry(query string, attempts int) (*SearchResult, error) {
    var lastErr error
    for i := 0; i < attempts; i++ {
        result, err := search(query)
        if err == nil {
            return result, nil
        }
        lastErr = err
        if strings.Contains(err.Error(), "HTTP 401") {
            return nil, err // config problem, do not retry
        }
        // exponential backoff: 1s, 2s, 4s ...
        time.Sleep(time.Duration(1<<uint(i)) * time.Second)
    }
    return nil, fmt.Errorf("search gave up after %d attempts: %w",
        attempts, lastErr)
}

The one line that matters most in that helper is the 401 early exit. A retry loop that treats authentication failures as transient errors burns your entire backoff schedule on a problem no amount of waiting can fix, and it turns a configuration mistake into a latency spike.

How Do You Use the Response in a Service?

The results.web array is the part your code consumes, and each entry carries the title, URL, and description snippet at minimum. Two service-level patterns matter beyond decoding.

First, pass a context. In a real service the request should carry a context.Context so an upstream timeout cancels the search call, which means using http.NewRequestWithContext instead of http.NewRequest, and passing the context through your function signatures. The client timeout is the outer guard, and the context is the caller's guard.

Second, return a typed error, not a boolean. A search that fails and a search that returns zero results are different events in a calling service. The error type above distinguishes them naturally, because zero results is a valid SearchResult with an empty slice, and any nil Result with a non-nil error is a genuine failure.

What Breaks in Production?

The zero-timeout client. A client built without a Timeout field waits indefinitely on a stalled connection, and stalled upstreams happen. One slow search then holds a goroutine and a connection forever. Detection: every http.Client in the codebase gets a timeout at construction, and code review checks for the zero-value client.

The swallowed error body. On a non-2xx, the response body often carries the actual reason, and a handler that only reads the status code throws it away. Detection: on error paths, read a bounded slice of the body and include it in the error message, as the status check above can be extended to do.

Retries without backoff. Three immediate retries triple your rate-limit pressure at exactly the moment you are already being limited. Detection: log the retry count and delay per request. A retry histogram with mass at zero delay is the bug's signature.

Connection churn. Creating a new http.Client per request discards Go's connection pooling and exhausts file descriptors under load. Detection: allocate the client once at service startup and reuse it, and watch descriptor counts if you inherit a codebase that does not.

Related Guides

FAQ

Is there a Go SDK for the You.com Web Search API? This guide uses the REST endpoint with the standard library, which is dependency-free and always current with the documented endpoint. If an official SDK lands, the endpoint and headers stay the same underneath, so the code here keeps working.

Why does my request return 401 when the key is set? The usual cause is the environment variable not being exported into the service's environment, so os.Getenv returns an empty string. Check the variable in the service's environment, not your shell, and check for whitespace around the key value.

Should I retry a 429 immediately? No. A 429 means the request was too soon, so an immediate retry repeats the problem. Pause with exponential backoff, honor any retry guidance in the response headers, and cap total attempts so a busy period degrades gracefully instead of compounding.

How do I add filters like date ranges or domain lists? The request body accepts filters such as freshness for recency windows and the domain steering parameters. The Web Search API guide documents every parameter, and our site restriction guide covers the domain filters in depth.

    Share Article:

  1. LI Test

  2. LI Test

Related resources.

No items found.
No items found.