# Migrate from Serper

This guide maps Serper's Google Search and Places endpoints to SerpWiz. For pricing, see [Serper vs SerpWiz](/vs/serper).

## Requests

| | Serper | SerpWiz |
|---|---|---|
| Search | `POST https://google.serper.dev/search` | `GET https://api.serpwiz.com/v1/google/search` |
| Places / Maps | `POST https://google.serper.dev/places` | `GET https://api.serpwiz.com/v1/google/maps` |
| API key | `X-API-KEY` header | `X-API-Key` header (header names aren't case-sensitive, so the same header works) |
| Parameters | JSON body or query string | Query string |

### Parameters

| Serper | SerpWiz | Notes |
|---|---|---|
| `q` | `q` | Same. |
| `gl` | `gl` | Same two-letter country code. |
| `hl` | `hl` | Same language code. |
| `page` | `page` | Same: page numbers starting at 1. |
| `tbs` | `tbs` | Supported: `qdr:h/d/w/m/y`, `li:1` and custom date ranges. |
| `num` | — | Search returns 10 results per page. For Maps, use `num` (1–20). |
| `location` | `location` (Maps only) | On Search, use `gl` for the country. |

## Responses

| Serper | SerpWiz |
|---|---|
| `organic[]` | `results[]` |
| `organic[].position` | `results[].position` |
| `organic[].title` | `results[].title` |
| `organic[].link` | `results[].url` |
| `organic[].snippet` | `results[].description` |
| `organic[].date` | `results[].date` |
| `peopleAlsoAsk[]` | `related_keywords.keywords[]`, as question text only |
| `relatedSearches[]` | `related_keywords.keywords[]` (`keyword`) |
| `knowledgeGraph` | `knowledge_panel` |
| `places[]` | `results[]` from `/v1/google/maps` |

See [Google Search](/docs/google-search) and [Google Maps](/docs/google-maps) for every field.

## Code: before and after

```javascript
// Before: Serper
const res = await fetch("https://google.serper.dev/search", {
  method: "POST",
  headers: { "X-API-KEY": process.env.SERPER_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ q: "best crm for startups", gl: "us", page: 2 }),
});
const { organic } = await res.json();
organic.forEach(r => console.log(r.position, r.title, r.link));
```

```javascript
// After: SerpWiz
const params = new URLSearchParams({ q: "best crm for startups", gl: "us", page: 2 });
const res = await fetch(`https://api.serpwiz.com/v1/google/search?${params}`, {
  headers: { "X-API-Key": process.env.SERPWIZ_API_KEY },
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { results } = await res.json();
results.forEach(r => console.log(r.position, r.title, r.url));
```

## Errors

- **SerpWiz errors** come back as `{"error": {"status", "code", "message", "request_id"}}` with a matching HTTP status.
- **Retries:** retry `429` and `503` after the `Retry-After` header. See [Errors](/docs/errors).
- **Charging:** failed requests are never charged. The `X-Credits-Charged` header on every response shows what was billed.
