# Output formats

> Get Google results as full JSON, token-lean compact JSON or LLM-ready Markdown, and cut them down further with fields projection. X-Tokens-Estimate tells you the size.

Every search endpoint can answer in three formats. They contain the same search, just packaged for different consumers, and they cost the same credits.

| `format` | Content type | Best for |
| --- | --- | --- |
| `json` (default) | `application/json` | Apps and pipelines that parse specific fields. The full `request` / `results` / `meta` envelope. |
| `compact` | `application/json` | Agents that want structure but few tokens. The same `results`, trimmed: short snippets, no thumbnails, positions or tracking data. |
| `markdown` (alias `md`) | `text/markdown` | Putting results straight into an LLM prompt or a tool result. |

> **JSON-only endpoints**
> `/v1/autocomplete` and `/v1/lens` always return JSON; their responses are already small. `format: "compact"` still works there.

## JSON

The default. Every endpoint returns the same envelope, with snake_case keys throughout:

- `request`: the normalised request that ran, with defaults filled in (`endpoint`, `engine`, `q`, `country`, `language`, `num`, `page`, `device`…).
- `results`: the endpoint's main list. Organic results on /v1/search, articles on /v1/news, places on /v1/maps, reviews on /v1/reviews, suggestions on /v1/autocomplete, and so on. [/v1/webpage](https://serpkite.com/docs/endpoints/webpage) is the only endpoint without `results`; it returns `url`, `markdown` and `metadata`.
- Endpoint extras. On /v1/search: `related_searches` (always present), and `answer_box`, `knowledge_graph`, `people_also_ask`, `top_stories`, `places` and `ads` when Google shows them.
- `meta`: `request_id`, `credits_used`, `cached`, `engine` (the [provider](https://serpkite.com/docs/providers) that answered), `route`, `latency_ms`, `parse_quality` and `resolved_urls`.

Every `link` is the resolved destination URL and every result carries a canonical `domain`.

format=json · illustrative:

```json
{
  "request": {
    "endpoint": "search",
    "engine": "google",
    "q": "best espresso machine 2026",
    "country": "us",
    "language": "en",
    "num": 10,
    "page": 1,
    "device": "desktop",
    "autocorrect": true
  },
  "results": [
    {
      "position": 1,
      "title": "The Best Espresso Machines of 2026, Tested and Reviewed",
      "link": "https://www.example.com/best-espresso-machines",
      "domain": "example.com",
      "displayed_link": "https://www.example.com › best-espresso-machines",
      "snippet": "We pulled more than 1,200 shots on 42 machines to find the best espresso makers for every budget, from beginner-friendly to prosumer.",
      "date": "Sep 12, 2026",
      "sitelinks": [
        {
          "title": "Best budget pick",
          "link": "https://www.example.com/best-espresso-machines#budget"
        },
        {
          "title": "Best dual boiler",
          "link": "https://www.example.com/best-espresso-machines#dual-boiler"
        }
      ]
    },
    {
      "position": 2,
      "title": "Espresso Machine Buying Guide (2026)",
      "link": "https://coffee.example.org/guides/espresso",
      "domain": "coffee.example.org",
      "displayed_link": "https://coffee.example.org › guides › espresso",
      "snippet": "Single boiler, heat exchanger or dual boiler? What the specs mean and which features are worth paying for."
    },
    {
      "position": 3,
      "title": "r/espresso: What machine would you buy in 2026?",
      "link": "https://www.reddit.com/r/espresso/comments/abc123/",
      "domain": "reddit.com",
      "displayed_link": "https://www.reddit.com › r › espresso",
      "snippet": "Discussion thread with 480 comments comparing entry-level and prosumer machines."
    }
  ],
  "people_also_ask": [
    {
      "question": "What is the #1 rated espresso machine?",
      "snippet": "Reviewers most often rank dual-boiler machines with PID control at the top…",
      "link": "https://www.example.com/best-espresso-machines"
    },
    {
      "question": "Is a $500 espresso machine worth it?",
      "snippet": "For daily drinkers, a mid-range machine usually pays for itself within a year…",
      "link": "https://coffee.example.org/guides/espresso"
    }
  ],
  "related_searches": [
    {
      "query": "best espresso machine under $500"
    },
    {
      "query": "best espresso machine for beginners"
    },
    {
      "query": "dual boiler vs heat exchanger"
    }
  ],
  "meta": {
    "request_id": "req_01J8ZK4M6Q2V7",
    "credits_used": 1,
    "cached": false,
    "engine": "google",
    "latency_ms": 942,
    "parse_quality": "ok",
    "resolved_urls": true
  }
}
```

The fields of each endpoint are documented on its page, for example [Search](https://serpkite.com/docs/endpoints/search#response).

## Compact

`format: "compact"` returns a token-lean JSON object with only what a model needs to reason about the results. It keeps the same `results` key and snake_case names, shortens snippets, drops empty values, and removes positions, thumbnails, sitelinks and tracking data. `meta` stays, and `fields` works on compact output too.

cURL:

```bash
curl https://api.serpkite.com/v1/search \
  -H "Authorization: Bearer $SERPKITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"best espresso machine 2026","country":"us","format":"compact"}'
```

TypeScript:

```ts
import { SerpKite } from "serpkite";

const sk = new SerpKite(); // reads SERPKITE_API_KEY
const res = await sk.search({ q: "best espresso machine 2026", country: "us", format: "compact" });
console.log(res.results[0].title, res.meta.credits_used);
```

Python:

```python
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY
res = sk.search("best espresso machine 2026", country="us", format="compact")
print(res.results[0].title, res.meta.credits_used)
```

Go:

```go
package main

import (
	"context"
	"fmt"
	"log"

	serpkite "github.com/serpkite/serpkite-go"
)

func main() {
	ctx := context.Background()
	c := serpkite.NewClient() // reads SERPKITE_API_KEY
	res, err := c.Search(ctx, serpkite.SearchParams{Q: "best espresso machine 2026", Country: "us", Format: "compact"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Results[0].Title, res.Meta.CreditsUsed)
}
```

format=compact · illustrative:

```json
{
  "results": [
    {
      "title": "The Best Espresso Machines of 2026, Tested and Reviewed",
      "link": "https://www.example.com/best-espresso-machines",
      "snippet": "We pulled more than 1,200 shots on 42 machines to find the best espresso makers for every budget, from beginner-friendly to prosumer.",
      "date": "Sep 12, 2026"
    },
    {
      "title": "Espresso Machine Buying Guide (2026)",
      "link": "https://coffee.example.org/guides/espresso",
      "snippet": "Single boiler, heat exchanger or dual boiler? What the specs mean and which features are worth paying for."
    },
    {
      "title": "r/espresso: What machine would you buy in 2026?",
      "link": "https://www.reddit.com/r/espresso/comments/abc123/",
      "snippet": "Discussion thread with 480 comments comparing entry-level and prosumer machines."
    }
  ],
  "people_also_ask": [
    "What is the #1 rated espresso machine?",
    "Is a $500 espresso machine worth it?"
  ],
  "meta": {
    "request_id": "req_01J8ZK4M6Q2V7",
    "credits_used": 1,
    "cached": false,
    "engine": "google",
    "latency_ms": 942,
    "parse_quality": "ok",
    "resolved_urls": true
  }
}
```

Compact keys per endpoint:

| Endpoint | Keys |
| --- | --- |
| `/v1/search`, `/v1/scholar`, `/v1/patents`, `/v1/lens` | `results[]` (`title`, `link`, `snippet`, `date`, `content`, `cited_by`, `year`), plus on /v1/search `answer`, `knowledge_graph` (`title`, `type`, `description`, `website`), `people_also_ask[]` (question strings), `top_stories[]` |
| `/v1/news` | `results[]` (`title`, `link`, `source`, `date`, `snippet`) |
| `/v1/images` | `results[]` (`title`, `image_url`, `link`) |
| `/v1/videos` | `results[]` (`title`, `link`, `channel`, `duration`, `date`) |
| `/v1/maps`, `/v1/places` | `results[]` (`title`, `address`, `rating`, `rating_count`, `phone`, `website`, `type`) |
| `/v1/reviews` | `results[]` (`rating`, `date`, `text`) |
| `/v1/shopping` | `results[]` (`title`, `price`, `source`, `link`, `rating`) |
| `/v1/autocomplete` | `results[]` (suggestion strings) |
| `/v1/webpage` | `url`, `title`, `markdown` |

Keys other than `results` only appear when Google returned something for them, so check for presence rather than `null`.

## Markdown

`format: "markdown"` renders the results page as a Markdown document with `Content-Type: text/markdown`. Headings separate the sections (results, People also ask, related searches) and links are kept. It is usually the cheapest way to give a model the whole page.

cURL:

```bash
curl https://api.serpkite.com/v1/search \
  -H "Authorization: Bearer $SERPKITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"best espresso machine 2026","country":"us","format":"markdown"}'
```

TypeScript:

```ts
import { SerpKite } from "serpkite";

const sk = new SerpKite(); // reads SERPKITE_API_KEY
const res = await sk.search({ q: "best espresso machine 2026", country: "us", format: "markdown" });
console.log(res);
```

Python:

```python
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY
res = sk.search("best espresso machine 2026", country="us", format="markdown")
print(res)
```

Go:

```go
package main

import (
	"context"
	"fmt"
	"log"

	serpkite "github.com/serpkite/serpkite-go"
)

func main() {
	ctx := context.Background()
	c := serpkite.NewClient() // reads SERPKITE_API_KEY
	res, err := c.SearchMarkdown(ctx, serpkite.SearchParams{Q: "best espresso machine 2026", Country: "us"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res)
}
```

format=markdown · illustrative:

```markdown
# best espresso machine 2026

## Results
1. **The Best Espresso Machines of 2026, Tested and Reviewed** — example.com
   https://www.example.com/best-espresso-machines
   We pulled more than 1,200 shots on 42 machines to find the best espresso makers for every budget.
2. **Espresso Machine Buying Guide (2026)** — coffee.example.org
   https://coffee.example.org/guides/espresso
   Single boiler, heat exchanger or dual boiler? What the specs mean.
3. **r/espresso: What machine would you buy in 2026?** — reddit.com
   https://www.reddit.com/r/espresso/comments/abc123/

## People also ask
- **What is the #1 rated espresso machine?** Reviewers most often rank dual-boiler machines with PID control at the top…
- **Is a $500 espresso machine worth it?** For daily drinkers, a mid-range machine usually pays for itself within a year…

## Related searches
best espresso machine under $500 · best espresso machine for beginners · dual boiler vs heat exchanger
```

The body is plain text. The SDKs return it as a string (`SearchMarkdown` in Go); with a raw HTTP client read it with `res.text()` (Node) or `res.text` (Python), not as JSON. Errors are still JSON with the usual [error shape](https://serpkite.com/docs/errors), so check the status code first.

## Fields projection

`fields` keeps only the parts of the response you ask for. It takes a comma-separated list of dot paths. Arrays are traversed element by element, so `results.title` keeps the title of every result.

cURL:

```bash
curl https://api.serpkite.com/v1/search \
  -H "Authorization: Bearer $SERPKITE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q":"best espresso machine 2026","fields":"results.title,results.link,knowledge_graph.title"}'
```

TypeScript:

```ts
import { SerpKite } from "serpkite";

const sk = new SerpKite(); // reads SERPKITE_API_KEY
const res = await sk.search({ q: "best espresso machine 2026", fields: "results.title,results.link,knowledge_graph.title" });
console.log(res.results[0].title, res.meta.credits_used);
```

Python:

```python
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY
res = sk.search("best espresso machine 2026", fields="results.title,results.link,knowledge_graph.title")
print(res.results[0].title, res.meta.credits_used)
```

Go:

```go
package main

import (
	"context"
	"fmt"
	"log"

	serpkite "github.com/serpkite/serpkite-go"
)

func main() {
	ctx := context.Background()
	c := serpkite.NewClient() // reads SERPKITE_API_KEY
	res, err := c.Search(ctx, serpkite.SearchParams{Q: "best espresso machine 2026", Fields: "results.title,results.link,knowledge_graph.title"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Results[0].Title, res.Meta.CreditsUsed)
}
```

```json
{
  "request": { "endpoint": "search", "engine": "google", "q": "best espresso machine 2026", "country": "us", "language": "en", "num": 10, "page": 1 },
  "results": [
    { "title": "The Best Espresso Machines of 2026, Tested and Reviewed", "link": "https://www.example.com/best-espresso-machines" },
    { "title": "Espresso Machine Buying Guide (2026)", "link": "https://coffee.example.org/guides/espresso" }
  ],
  "knowledge_graph": { "title": "Espresso machine" },
  "meta": { "request_id": "req_01J8ZK4M6Q2V7", "credits_used": 1, "cached": false, "engine": "google" }
}
```

Rules:

- Up to 50 paths per request, each at most 4 levels deep (`results.sitelinks.title` is fine).
- `request` and `meta` are always kept, so you can still read the request ID and cost.
- Paths that don't exist in the response are ignored.
- With `format: "markdown"`, top-level names in `fields` select which sections are rendered, e.g. `fields: "answer_box,results"` drops People also ask and related searches.
- Invalid paths (empty segments, too deep, too many) return `400 invalid_request`.

## Measuring size: X-Tokens-Estimate

Every response carries `X-Tokens-Estimate`, an approximation of the LLM tokens in the body (characters divided by four). Use it to compare formats for your queries, or to decide how many results fit in a context window before you read the body.

```bash
curl -s -o /dev/null -D - https://api.serpkite.com/v1/search \
  -H "Authorization: Bearer $SERPKITE_API_KEY" -H "Content-Type: application/json" \
  -d '{"q":"best espresso machine 2026","format":"markdown"}' | grep -i x-tokens-estimate
```

It is an estimate, not your model's tokenizer; real counts vary by model and language. For a side-by-side comparison on your own query, try the [SERP token counter](https://serpkite.com/tools/serp-token-counter).

## Choosing a format

- Building an app or storing results: `json`, with `fields` to drop what you don't read.
- Agent tool results where the model reasons over the page: `markdown`.
- Agent tool results where your code post-processes before the model sees them: `compact`.
- Feeding full pages, not just snippets: add [`include_content`](https://serpkite.com/docs/include-content).

Related: [Common parameters](https://serpkite.com/docs/parameters), [Tool calling for agents](https://serpkite.com/docs/guides/agents-tool-calling), [Response headers](https://serpkite.com/docs/response-headers).