SERP API for C#
Open the related NovaDataHub page for deeper implementation guidance.
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.
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.
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);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()}");
}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);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);
});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();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);
}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.
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);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.
Create a free NovaDataHub account, enable the API you need, and test structured JSON responses before moving into production.
New trial accounts can start with Starter Pack capacity at no cost for a limited time. Create your account and test the APIs with a much stronger quota right away.