# Authentication

> Every SerpKite API call is authenticated with a secret API key, sent as a Bearer token in the Authorization header, or on GET requests as a query parameter.

## API keys

SerpKite keys look like `skt_live_` followed by a random secret. You create them in the dashboard at [app.serpkite.com](https://app.serpkite.com) under **API keys**. The full secret is shown exactly once, when the key is created or rotated. We store only a SHA-256 hash of it, so nobody at SerpKite can read it back to you. If you lose a key, [rotate it](https://serpkite.com/docs/api-keys#rotate-a-key).

The `skt_` prefix is deliberate: it doesn't collide with Stripe's `sk_` pattern, so secret scanners (GitHub push protection and similar) can tell a leaked SerpKite key apart from a Stripe one.

An account can have several keys, each with its own name and optional monthly credit limit. See [API keys](https://serpkite.com/docs/api-keys).

## Sending the key

Send the key as a Bearer token in the `Authorization` header. This works on every request, including the [MCP server](https://serpkite.com/docs/mcp):

| Method | Example | Works on |
| --- | --- | --- |
| Bearer token (recommended) | `Authorization: Bearer skt_live_…` | All requests |
| Query parameter | `?api_key=skt_live_…` | `GET` requests only |
| Google's `key` parameter | `?key=skt_live_…` | [`GET /customsearch/v1`](https://serpkite.com/docs/endpoints/customsearch) only |

Other headers are not read. A request without a valid `Authorization: Bearer` header (or `api_key` on a `GET`) gets `401 unauthorized`.

### Bearer token

The official SDKs send the header for you and read the key from `SERPKITE_API_KEY` unless you pass one explicitly (`new SerpKite({ apiKey })`, `SerpKite(api_key=...)`, `serpkite.WithAPIKey(...)`). With curl or your own HTTP client, set the header yourself:

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","language":"en"}'
```

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", language: "en" });
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", language="en")
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", Language: "en"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(res.Results[0].Title, res.Meta.CreditsUsed)
}
```

### Query parameter (GET only)

For `GET` requests you can put the key in the URL as `api_key`. This exists for tools that can only paste a URL (spreadsheets, no-code HTTP nodes). The [Custom Search–compatible endpoint](https://serpkite.com/docs/endpoints/customsearch) also accepts Google's `key=`, so existing CSE clients keep working.

```bash
curl "https://api.serpkite.com/v1/search?q=best+espresso+machine+2026&country=us&api_key=$SERPKITE_API_KEY"
```

> **Prefer headers**
> URLs end up in proxy logs, browser history and analytics tools. Use a header whenever your client allows it, and never put a key in a URL that a browser or a third party will see.

## Keep keys secret

- Load the key from an environment variable or a secrets manager. All examples in these docs read `SERPKITE_API_KEY`.
- Don't ship keys in mobile apps or public frontend bundles. The API sends `Access-Control-Allow-Origin: *` so that a browser playground with the user's own key works, but anyone who can read your JavaScript can read a key embedded in it. Call SerpKite from your backend instead.
- Use one key per environment or service (`production`, `staging`, `n8n`) and give each a [monthly credit limit](https://serpkite.com/docs/api-keys#per-key-monthly-limit). A leaked key then has a bounded cost, and you can revoke it without touching the others.
- If a key leaks, [rotate or revoke it](https://serpkite.com/docs/api-keys) in the dashboard. Revocation is effective within about a minute.

## Checking a key

`GET /v1/account` is free and returns the balance, rate limit and this month's usage for the account behind the key. It is a cheap way to validate a key at startup:

cURL:

```bash
curl "https://api.serpkite.com/v1/account" \
  -H "Authorization: Bearer $SERPKITE_API_KEY"
```

TypeScript:

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

const sk = new SerpKite();
const account = await sk.account();
console.log(account.balance, account.month.credits);
```

Python:

```python
from serpkite import SerpKite

sk = SerpKite()
account = sk.account()
print(account.balance, account.month.credits)
```

Go:

```go
c := serpkite.NewClient()
account, err := c.Account(ctx)
if err != nil {
	log.Fatal(err)
}
fmt.Println(account.Balance, account.Month.Credits)
```

A missing, malformed or revoked key gets `401 unauthorized`:

```json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key",
    "request_id": "req_01J8ZK9P3WQ5N"
  }
}
```

The SDKs raise this as an error you can catch: `SerpKiteError` in TypeScript and Python (with `status`, `code`, `message` and the request ID), `*serpkite.Error` in Go. Check for `code === "unauthorized"` rather than parsing the message.

The dashboard itself (app.serpkite.com) uses a separate session cookie, not API keys. API keys only work against `api.serpkite.com`.

## Related

- [API keys](https://serpkite.com/docs/api-keys): Create, limit, rotate and revoke keys.
- [Errors](https://serpkite.com/docs/errors): Every error code and what to do about it.
- [Rate limits](https://serpkite.com/docs/rate-limits): Requests per second per key, and how to back off.
- [MCP server](https://serpkite.com/docs/mcp): Use your key as a Bearer token for the remote MCP server.