# Authentication

> Every Grabber API request needs an API key in the Authorization header. How to create a key, send it, keep it safe and read a 401 response.

An API key from https://dashboard.grabber.sh/keys. It starts with `gsh_`.
The header is the only place it is accepted.

Send it as a Bearer token on every request:

```http
Authorization: Bearer gsh_your_api_key
```

## Send the key

Each example reads the key from the GRABBER_API_KEY environment variable.

### cURL

```bash
curl -G https://api.grabber.sh/fetch \
  -H "Authorization: Bearer $GRABBER_API_KEY" \
  --data-urlencode "url=https://example.com/" \
  -d "formats=markdown" \
  -d "unwrap=true"
```

### Python

```python
import os
import requests

response = requests.get(
    "https://api.grabber.sh/fetch",
    params={"url": "https://example.com/", "formats": "markdown", "unwrap": "true"},
    headers={"Authorization": f"Bearer {os.environ['GRABBER_API_KEY']}"},
    timeout=130,
)
response.raise_for_status()
print(response.text)
```

### Node.js

```javascript
const params = new URLSearchParams({ url: 'https://example.com/', formats: 'markdown', unwrap: 'true' })

const response = await fetch(`https://api.grabber.sh/fetch?${params}`, {
  headers: { Authorization: `Bearer ${process.env.GRABBER_API_KEY}` },
  signal: AbortSignal.timeout(130_000),
})
if (!response.ok) throw new Error(`Grabber answered ${response.status}`)
console.log(await response.text())
```

### Go

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"time"
)

func main() {
	query := url.Values{"url": {"https://example.com/"}, "formats": {"markdown"}, "unwrap": {"true"}}
	req, _ := http.NewRequest("GET", "https://api.grabber.sh/fetch?"+query.Encode(), nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("GRABBER_API_KEY"))

	client := &http.Client{Timeout: 130 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.Status)
	fmt.Println(string(body))
}
```

### PHP

```php
<?php
$query = http_build_query(['url' => 'https://example.com/', 'formats' => 'markdown', 'unwrap' => 'true']);

$ch = curl_init("https://api.grabber.sh/fetch?$query");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('GRABBER_API_KEY')],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 130,
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body;
```

## Only in the header

A key in the address, like `?key=gsh_…`, is refused like any other unknown parameter: the answer is `400 invalid_query` and the key is not used. Keys in URLs end up in logs and browser history, so Grabber never reads them there.

## When the key is wrong

The key is missing, malformed, unknown, or deleted. The answer is `401` with `WWW-Authenticate: Bearer`, and nothing is charged.

```json
{
  "status": 401,
  "message": "Unauthorized",
  "detail": "invalid_api_key"
}
```

## Keep your key safe

- Keep the key on a server or in your agent's environment, never in code that runs in a browser or in a public repository.
- Create one key per app or agent. The dashboard shows usage and cost per key, and you can delete one without touching the others.
- If a key leaks, delete it in the [dashboard](https://dashboard.grabber.sh/keys). It stops working at once, and the next request with it gets a `401`.
