Updated on 2026-05-12 NovaDataHub Engineering
Tutorial

How to Use a SERP API in C# and .NET

C# and .NET teams often need a pattern that starts simple but grows cleanly into ASP.NET Core services, background jobs, and queue-driven workflows. This tutorial shows a minimal console example first, then moves into HttpClientFactory, cancellation tokens, typed-model guidance, and retry behavior for production integrations.

Add x-api-key to HttpClientCall the SERP endpoint from .NETUse JsonDocument or JsonElement safelyKnow when to introduce typed modelsAdopt HttpClientFactory in ASP.NET CoreHandle rate limits, quota states, and sync timeoutsKnow when to move into async job polling

Prerequisites

Before you write code, create a NovaDataHub account, enable the Google SERP API, and keep the x-api-key available through configuration or secrets. Start with a console request first, then move into ASP.NET Core services once the payload shape is clear.

Start with a simple console request

Use a basic HttpClient example to prove the endpoint, authentication, and sync response shape before you wrap the call inside a larger application service.

using System.Text.Json;

using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("NOVADATAHUB_SERP_KEY"));
var json = await http.GetStringAsync("https://novadatahub.com/search?q=google+serp+api+csharp&gl=us&hl=en&device=desktop&sync=true");
Console.WriteLine(json);

Parse JSON with JsonDocument or JsonElement

JsonElement is a good starting point when you are still learning the payload shape. Once your workflow is stable, you can map the fields you care about into typed records or DTOs.

using var document = JsonDocument.Parse(json);
var root = document.RootElement;
var result = root.GetProperty("result");
foreach (var row in result.GetProperty("organic").EnumerateArray())
{
    Console.WriteLine($"{row.GetProperty("position").GetInt32()} {row.GetProperty("title").GetString()} {row.GetProperty("url").GetString()}");
}

Know when to introduce typed models

Typed models are useful once you know which fields your application actually depends on. Keep the first version narrow instead of trying to model every possible SERP block on day one.

public sealed record OrganicRow(int Position, string? Title, string? Url);
public sealed record SerpResult(IReadOnlyList<OrganicRow> Organic);
public sealed record SerpResponse(bool Ok, string? Status, string? JobId, SerpResult? Result);

Move into HttpClientFactory for ASP.NET Core

In ASP.NET Core, register a typed or named client so authentication, timeouts, and shared request behavior stay in one place instead of getting duplicated across controllers or workers.

builder.Services.AddHttpClient<SerpApiClient>(client =>
{
    client.BaseAddress = new Uri("https://novadatahub.com/");
    client.DefaultRequestHeaders.Add("x-api-key", configuration["NovaDataHub:SerpApiKey"]);
    client.Timeout = TimeSpan.FromSeconds(60);
});

Use cancellation tokens and clear error handling

Pass a CancellationToken through your request path so long-running jobs can stop cleanly. Treat 400, 401, 402, 429, and 504 responses differently instead of folding them into one generic exception bucket.

using var request = new HttpRequestMessage(HttpMethod.Get, "search?q=google+serp+api+dotnet&gl=us&hl=en&sync=true");
using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) throw new InvalidOperationException("Check q, gl, hl, or other request parameters.");
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) throw new InvalidOperationException("Verify x-api-key and enabled service access.");
if ((int)response.StatusCode == 402) throw new InvalidOperationException("Check plan or quota state before retrying.");
if ((int)response.StatusCode == 429) throw new InvalidOperationException("Back off before retrying.");
if (response.StatusCode == System.Net.HttpStatusCode.GatewayTimeout) Console.WriteLine("Switch to async workflow using returned job context.");
response.EnsureSuccessStatusCode();

Add retry and backoff carefully

For recurring jobs, wrap retryable failures in a measured backoff policy. Keep rate-limit handling, timeout handling, and non-retryable validation failures in separate paths so logs and operator alerts remain useful.

for (var attempt = 0; attempt < 5; attempt++)
{
    using var retryResponse = await http.GetAsync("https://novadatahub.com/search?q=google+serp+api+dotnet&gl=us&hl=en&sync=true", cancellationToken);
    if ((int)retryResponse.StatusCode != 429)
    {
        retryResponse.EnsureSuccessStatusCode();
        break;
    }

    var delay = TimeSpan.FromSeconds(Math.Min(60, Math.Pow(2, attempt)));
    await Task.Delay(delay, cancellationToken);
}

Know when to move into async job workflows

If sync requests become slow or you are running recurring batches, submit the request without sync=true, store the jobId with the request context, and poll until the terminal state is completed or failed instead of blocking one long request thread. Keep the exact poll path aligned with the docs and runtime you are using.

Store results with request context

For rank tracking or reporting systems, keep the query, locale, device, timestamp, and the structured SERP result together. That context is what makes later comparisons and debugging trustworthy.

var collectedAt = DateTimeOffset.UtcNow;
var record = new
{
    Query = "google serp api dotnet",
    Gl = "us",
    Hl = "en",
    Device = "desktop",
    CollectedAt = collectedAt,
    Payload = json
};
Console.WriteLine(record.CollectedAt);

Move from console test to production service

Once the first request works, centralize request building, response parsing, retries, and logging in one service abstraction. Then connect the same flow to docs, tutorials, and monitoring so production behavior stays consistent across workers, APIs, and operator tooling.

FAQ

Tutorial questions

Can I use this in ASP.NET?
Yes. The same pattern works well inside ASP.NET Core services, background jobs, and queue-driven workers.
Should I start with typed models or JsonDocument?
Start with JsonDocument or JsonElement while you validate the payload shape, then introduce typed models for the fields your application truly depends on.
When should I switch to async polling?
Move to async polling when you are running bulk or recurring jobs, or when the sync request path is too slow for a good application experience.
How should I handle cancellation and retries?
Pass CancellationToken through the request path, back off on retryable failures, and keep validation, auth, quota, and timeout handling separate so production behavior stays predictable.
Should I use HttpClientFactory in production?
Yes. It keeps auth, base address, timeout, and shared client behavior in one place for ASP.NET Core applications.
Related links

Continue with connected pages

Start with 2,000 free API calls

Create a free NovaDataHub account, enable the API you need, and test structured JSON responses before moving into production.