Integrations
Language SDKs
There is no bespoke SDK to install — the API is plain JSON over HTTPS, so any language with an HTTP client works. Below is the same submit-and-poll pattern in the languages people ask for most.
The pattern
Every integration is the same three steps, regardless of language:
POST /v1/searchwith your keyword and location → get ajob_id.GET /v1/search/{job_id}?output=serpapiin a loop →202while running,200with the parsed body when terminal.- Read the results. Back off on
429, retry with backoff on5xx.
Server-side only
X-API-Key grants full access to your quota. Call the API from your backend and proxy results to your frontend — never ship the key in browser code, a mobile app, or a public repo. See Authentication.Prefer a typed client generated from the schema? See OpenAPI & Swagger.
Node.js / TypeScript
Uses the built-in fetch (Node 18+). No dependencies.
const API_BASE = 'https://serp-api.hoangha.shop'
const API_KEY = process.env.SERP_API_KEY!
async function search(keyword: string, location: string) {
// 1. submit
const submit = await fetch(`${API_BASE}/v1/search`, {
method: 'POST',
headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ keyword, location }),
})
if (!submit.ok) throw new Error(`submit failed: ${submit.status}`)
const { job_id } = await submit.json()
// 2. poll
for (;;) {
const res = await fetch(`${API_BASE}/v1/search/${job_id}?output=serpapi`, {
headers: { 'X-API-Key': API_KEY },
})
if (res.status === 200) return res.json() // done / partial
if (res.status === 202) { // still running
await new Promise((r) => setTimeout(r, 1500))
continue
}
throw new Error(`fetch failed: ${res.status}`)
}
}
const data = await search('best running shoes', 'Hanoi, Vietnam')
console.log(data.organic_results?.slice(0, 3))Python
Uses the requests library (pip install requests).
import os
import time
import requests
API_BASE = "https://serp-api.hoangha.shop"
API_KEY = os.environ["SERP_API_KEY"]
HEADERS = {"X-API-Key": API_KEY}
def search(keyword: str, location: str) -> dict:
# 1. submit
r = requests.post(
f"{API_BASE}/v1/search",
headers=HEADERS,
json={"keyword": keyword, "location": location},
timeout=30,
)
r.raise_for_status()
job_id = r.json()["job_id"]
# 2. poll
while True:
r = requests.get(
f"{API_BASE}/v1/search/{job_id}",
headers=HEADERS,
params={"output": "serpapi"},
timeout=30,
)
if r.status_code == 200: # done / partial
return r.json()
if r.status_code == 202: # still running
time.sleep(1.5)
continue
r.raise_for_status()
data = search("best running shoes", "Hanoi, Vietnam")
for row in data.get("organic_results", [])[:3]:
print(row["position"], row["title"])PHP
Uses cURL from the standard library.
<?php
$API_BASE = 'https://serp-api.hoangha.shop';
$API_KEY = getenv('SERP_API_KEY');
function serp_call(string $method, string $url, ?array $body = null): array {
global $API_KEY;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $API_KEY, 'Content-Type: application/json'],
]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return ['code' => $code, 'json' => json_decode($res, true)];
}
function search(string $keyword, string $location): array {
global $API_BASE;
// 1. submit
$r = serp_call('POST', "$API_BASE/v1/search", ['keyword' => $keyword, 'location' => $location]);
$jobId = $r['json']['job_id'];
// 2. poll
while (true) {
$r = serp_call('GET', "$API_BASE/v1/search/$jobId?output=serpapi");
if ($r['code'] === 200) return $r['json']; // done / partial
if ($r['code'] === 202) { sleep(2); continue; } // still running
throw new Exception("fetch failed: {$r['code']}");
}
}
$data = search('best running shoes', 'Hanoi, Vietnam');
print_r(array_slice($data['organic_results'] ?? [], 0, 3));Go
Standard library only.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const apiBase = "https://serp-api.hoangha.shop"
var apiKey = os.Getenv("SERP_API_KEY")
func do(method, url string, body []byte) (int, []byte, error) {
req, _ := http.NewRequest(method, url, bytes.NewReader(body))
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return 0, nil, err
}
defer res.Body.Close()
b, _ := io.ReadAll(res.Body)
return res.StatusCode, b, nil
}
func search(keyword, location string) (map[string]any, error) {
// 1. submit
body, _ := json.Marshal(map[string]string{"keyword": keyword, "location": location})
_, b, err := do("POST", apiBase+"/v1/search", body)
if err != nil {
return nil, err
}
var sub struct{ JobID string `json:"job_id"` }
json.Unmarshal(b, &sub)
// 2. poll
for {
code, b, err := do("GET", apiBase+"/v1/search/"+sub.JobID+"?output=serpapi", nil)
if err != nil {
return nil, err
}
if code == 200 {
var out map[string]any
json.Unmarshal(b, &out)
return out, nil
}
if code == 202 {
time.Sleep(1500 * time.Millisecond)
continue
}
return nil, fmt.Errorf("fetch failed: %d", code)
}
}
func main() {
data, err := search("best running shoes", "Hanoi, Vietnam")
if err != nil {
panic(err)
}
fmt.Println(data["organic_results"])
}Ruby
Standard library only.
require 'net/http'
require 'json'
require 'uri'
API_BASE = 'https://serp-api.hoangha.shop'
API_KEY = ENV.fetch('SERP_API_KEY')
def serp_request(req, uri)
req['X-API-Key'] = API_KEY
req['Content-Type'] = 'application/json'
Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
end
def search(keyword, location)
# 1. submit
uri = URI("#{API_BASE}/v1/search")
post = Net::HTTP::Post.new(uri)
post.body = { keyword: keyword, location: location }.to_json
job_id = JSON.parse(serp_request(post, uri).body)['job_id']
# 2. poll
uri = URI("#{API_BASE}/v1/search/#{job_id}?output=serpapi")
loop do
res = serp_request(Net::HTTP::Get.new(uri), uri)
return JSON.parse(res.body) if res.code == '200' # done / partial
raise "fetch failed: #{res.code}" unless res.code == '202'
sleep 1.5 # still running
end
end
data = search('best running shoes', 'Hanoi, Vietnam')
data['organic_results'].first(3).each { |r| puts "#{r['position']} #{r['title']}" }Other languages
Java, C#, Rust, and everything else
The contract is just two HTTPS endpoints returning JSON, so any language works with its standard HTTP client. Rather than hand-write models for a statically-typed language, generate a client from the schema:
# Java, C#, Rust, Kotlin, Swift, Dart, …
openapi-generator-cli generate \
-i https://serp-api.hoangha.shop/openapi.json \
-g java \
-o ./serp-api-clientSee OpenAPI & Swagger for the full codegen options and the interactive explorer.
Scaling to batches
To run many keywords, submit them all first (collect the job ids), then poll in parallel — this keeps many crawls in flight instead of serializing on each result. Keep concurrent submissions under your key’s rate_limit_per_min and back off on 429. The Guides page has a worked batch example and caching tips.