# SerpPortal SERP API — Language integrations for LLMs > How to call the SERP API from any language. There is no bespoke SDK; the API is plain JSON over > HTTPS. The pattern is always: POST /v1/search to get a job_id, then GET > /v1/search/{job_id}?output=serpapi in a loop (HTTP 202 while running, 200 when terminal). - Base URL: https://serp-api.hoangha.shop - Auth header: `X-API-Key: sk_live_...` (server-side only; never ship in browser/mobile code). - OpenAPI schema (for client codegen): https://serp-api.hoangha.shop/openapi.json ## Node.js / TypeScript (built-in fetch, Node 18+) ``` const API_BASE = 'https://serp-api.hoangha.shop' const API_KEY = process.env.SERP_API_KEY async function search(keyword, location) { 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 }), }) const { job_id } = await submit.json() 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() if (res.status === 202) { await new Promise((r) => setTimeout(r, 1500)); continue } throw new Error(`fetch failed: ${res.status}`) } } ``` ## Python (requests) ``` import os, time, requests API_BASE = "https://serp-api.hoangha.shop" HEADERS = {"X-API-Key": os.environ["SERP_API_KEY"]} def search(keyword, location): 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"] while True: r = requests.get(f"{API_BASE}/v1/search/{job_id}", headers=HEADERS, params={"output": "serpapi"}, timeout=30) if r.status_code == 200: return r.json() if r.status_code == 202: time.sleep(1.5); continue r.raise_for_status() ``` ## PHP (cURL) ``` $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($keyword, $location) { global $API_BASE; $r = serp_call('POST', "$API_BASE/v1/search", ['keyword' => $keyword, 'location' => $location]); $jobId = $r['json']['job_id']; while (true) { $r = serp_call('GET', "$API_BASE/v1/search/$jobId?output=serpapi"); if ($r['code'] === 200) return $r['json']; if ($r['code'] === 202) { sleep(2); continue; } throw new Exception("fetch failed: {$r['code']}"); } } ``` ## Go (standard library) ``` 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) { 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) 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) } } ``` ## Ruby (standard library) ``` 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) 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'] 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' raise "fetch failed: #{res.code}" unless res.code == '202' sleep 1.5 end end ``` ## Other languages (Java, C#, Rust, Kotlin, Swift, …) Same two-endpoint contract. Generate a typed client from the OpenAPI schema: ``` openapi-generator-cli generate -i https://serp-api.hoangha.shop/openapi.json -g java -o ./serp-api-client # TypeScript types only: npx openapi-typescript https://serp-api.hoangha.shop/openapi.json -o serp-api.d.ts ``` ## Batching Submit all keywords first (collect job ids), then poll in parallel. Keep concurrent submissions under the key's rate_limit_per_min; back off on 429. Repeat identical searches within the cache window are free (`cached: true`).