Doc categories

SERP API

SERP API Documentation

SERP API returns Google search results as raw JSON. It runs on the same crawling infrastructure and the same unit price as SERP Checker. This guide covers authentication, every endpoint, the response format, the webhook and the full error code table.

The basics

  • Base URL: https://app.serpupdate.com/api/v1
  • Authentication: the X-API-KEY header
  • Search engine: Google, in this phase
  • Format: raw JSON with real HTTP status codes

1. Integration workflow

  1. Get your API key in the app, under SERP API, on the API Keys tab.
  2. Set your Pingback URL and Pingback Token on the same tab.
  3. Call POST /serp-api/trigger with a keyword and receive a snapshot_id.
  4. Wait for SERPUPDATE to call your webhook when the snapshot finishes.
  5. Call GET /serp-api/{snapshot_id} to read the data.

Prefer the webhook over polling

Polling burns requests and adds delay. The pingback webhook calls your server the moment a result is ready.

2. Authentication

Send the X-API-KEY header on every request. The key is a 32 character lowercase hex string with no hyphens.

curl -X GET https://app.serpupdate.com/api/v1/serp-api/languages 
  -H "X-API-KEY: 3f9a1c7e4b2d48a6915e0c83d7f6b214"

Generating a new key disables the old one immediately

Pressing “Get new key” invalidates the current key at once and cannot be undone. Any integration still using the old key will start failing until you update it.

3. Endpoints

3.1. Create a snapshot

POST /serp-api/trigger

This is the billable call. It accepts exactly one keyword per request and returns a snapshot_id straight away, with the data fetched later.

Parameter Type Required Description
keyword string Yes The keyword to crawl, 1 to 500 characters.
language string No Language code such as en or vi. Take it from the languages endpoint.
country string No Two letter ISO country code such as US or VN.
device string No desktop or mobile. Defaults to desktop.
start_page number No First SERP page, starting at 1. Defaults to 1.
end_page number No Last SERP page, 10 at most. Defaults to 10, which is the full Top 100.
location string No A location name from the locations endpoint. Leave empty if you send uule.
uule string No A pre-encoded UULE string. Leave empty to have it derived from location.
include_aio boolean No true also captures the AI Overview and its reference list.
custom_id string No Your own identifier, useful for reconciliation.

Pricing is flat per call

One page or all ten costs the same. There is nothing to save by lowering end_page. Only include_aio changes the unit price, and it replaces that price rather than adding a second charge.

Example request

curl -X POST https://app.serpupdate.com/api/v1/serp-api/trigger 
  -H "X-API-KEY: 3f9a1c7e4b2d48a6915e0c83d7f6b214" 
  -H "Content-Type: application/json" 
  -H "Idempotency-Key: order-8841" 
  -d '{
  "input": [
    {
      "keyword": "best protein powder for muscle gain",
      "language": "en",
      "country": "US",
      "device": "desktop",
      "start_page": 1,
      "end_page": 10,
      "include_aio": true
    }
  ]
}'

Response

{ "snapshot_id": "sd_abc123xyz" }

One keyword per call

The input field is an array, but only a single element is accepted in this phase. Sending more returns the TOO_MANY_INPUTS error code.

3.2. Avoiding duplicate charges

Send the optional Idempotency-Key header, up to 64 characters, with your trigger call. If the network fails and you retry with the same key, the system recognises it and does not run a second billable crawl. Use one unique value per business event, such as an order reference or a UUID.

3.3. Fetch the result

GET /serp-api/{snapshot_id}

HTTP status Meaning
200 Finished. The body is the JSON result array.
202 Still running, no data yet. Wait for the webhook or ask again later.
404 Snapshot not found.
410 The snapshot is past its retention window and the data has been deleted.
422 The crawl failed. Read error_code in the body.
curl https://app.serpupdate.com/api/v1/serp-api/sd_abc123xyz 
  -H "X-API-KEY: 3f9a1c7e4b2d48a6915e0c83d7f6b214"

3.4. Look up locations and languages

GET /serp-api/locations takes these query parameters: name (partial match), country (ISO-2), page (zero based) and size.

GET /serp-api/languages takes no parameters and returns every language code Google supports.

Use both endpoints to get valid values for location and language before you call trigger.

4. Result format

The body is an array with one entry per keyword. In this phase it always holds exactly one entry.

[
  {
    "keyword": "best protein powder for muscle gain",
    "organic": [
      {
        "rank": 1,
        "title": "Protein Powders For Muscle Growth Support",
        "url": "https://www.gnc.com/buy/protein-powders-for-muscle-growth",
        "domain": "gnc.com",
        "description": "Whey protein is often chosen for its rapid absorption..."
      }
    ],
    "aio_text": "AI Overview content in markdown, with citation markers [[1]](https://...)",
    "aio_reference": [
      {
        "rank": 1,
        "url": "https://www.healthline.com/nutrition/best-protein-powder-to-build-muscle",
        "domain": "healthline.com",
        "title": "The 10 Best Protein Powders to Build Muscle",
        "description": "Protein powders, especially when combined with resistance training..."
      }
    ]
  }
]
Field Type Meaning
keyword string The keyword that was crawled, echoed back exactly as you sent it.
organic array Organic results in rank order.
organic[].rank number Position on the SERP, starting at 1 and running across all pages.
organic[].title string The title as shown on Google.
organic[].url string The real destination URL, not a Google redirect link.
organic[].domain string The bare domain of that URL.
organic[].description string The snippet shown under the title.
aio_text string AI Overview content in markdown, with [[n]] citation markers.
aio_reference array The sources the AI Overview cites, same shape as organic.

The two AI Overview fields can be missing

aio_text and aio_reference only appear when you send include_aio: true and Google actually rendered an AI Overview for that query. Otherwise both keys are absent entirely rather than empty. Your parser must handle the missing keys.

5. Pingback webhook

When a snapshot finishes or fails, SERPUPDATE sends a POST request to the URL you configured. This is SERPUPDATE calling your server, not an endpoint you call.

POST https://your-server.com/webhook/serpupdate
User-Agent: SerpUpdate-Webhook/1.0
X-SerpUpdate-Delivery: dlv_a1b2c3d4e5f6
X-SerpUpdate-Token: the token you set on the API Keys tab
Content-Type: application/json

{
  "snapshot_id": "sd_abc123xyz",
  "status": "ready",
  "keyword": "best protein powder for muscle gain",
  "collect_time_ms": 18450
}

status is ready on success and failed on failure. collect_time_ms is the collection time in milliseconds.

Always verify the token before trusting the payload

Compare the X-SerpUpdate-Token header against the token you set. If it does not match, return 401 and ignore the request. Without this check, anyone who learns your URL can push fake data into your system.

Your server must answer with a 2xx status. If it does not, SERPUPDATE retries up to 3 times, after 30 seconds, 5 minutes and 30 minutes.

Handling the webhook in Node.js

import express from "express";
const app = express();
app.use(express.json());

app.post("/webhook/serpupdate", (req, res) => {
  if (req.get("X-SerpUpdate-Token") !== process.env.SERPUPDATE_PINGBACK_TOKEN) {
    return res.sendStatus(401);
  }
  const { snapshot_id: id, status } = req.body;
  if (status === "ready") {
    console.log("snapshot ready:", id);
  }
  res.sendStatus(200);
});

Configuration requirements

  • The Pingback URL must use https://. Internal addresses are blocked, including localhost, 127.0.0.1 and the 10.x, 192.168.x and 172.16.x to 172.31.x ranges.
  • The Pingback Token must be 16 to 128 characters long.
  • The app has a Test pingback button that sends a trial request and shows the status your server returned.
  • If a delivery fails, the Request logs table has a button to send it again manually.

6. Error codes

Code Meaning
INVALID_REQUEST Invalid request, missing data or malformed JSON.
INVALID_INPUT One of the fields is invalid.
TOO_MANY_INPUTS Only one keyword per run is supported in this phase.
INVALID_IDEMPOTENCY_KEY The Idempotency-Key is too long, 64 characters at most.
UNAUTHORIZED Authentication failed, check your API key.
INSUFFICIENT_BALANCE Not enough balance for this request. Please top up.
FEATURE_DISABLED SERP API is currently disabled for this account.
SNAPSHOT_EXPIRED The snapshot has expired and its data has been deleted.
SNAPSHOT_FAILED The snapshot failed.
COLLECTION_ERROR The request was not accepted.
COLLECTION_TIMEOUT The request timed out.
COLLECTION_UNAVAILABLE The crawling backend is unavailable, try again later.
TRIGGER_TIMEOUT The request could not be started.
INTERNAL_ERROR A system error occurred, try again or contact support.

Rate limiting

When you exceed the rate limit the API returns HTTP 429 with a Retry-After header in seconds. Wait for that interval before calling again instead of retrying in a tight loop.

Failed runs are not refunded automatically

Most of the codes above come with no automatic refund. If you need a run reviewed, contact support with the snapshot_id.

7. Lifecycle and limits

  • Retention: 30 days from the moment a snapshot completes. After that the result endpoint returns 410.
  • Expiry warning: the Request logs table flags a snapshot once fewer than 3 days remain.
  • Snapshot states: running, ready, failed, expired.
  • Snapshots cannot be deleted manually. A background job clears them according to the retention policy.
  • Page range: start_page and end_page both sit between 1 and 10.

Keep the original JSON string if you need to reconcile

If you store the response for auditing or signature hashing, keep the exact raw string you received. Parsing it and writing it back out as new JSON changes key order and whitespace, which breaks any byte comparison.