Source: http://www.poma-ai.com/docs/grill/getting-started/authentication

# Grill Authentication

Every `/grill/*` endpoint is **project-scoped**. The Bearer token you send must be a **project API key** belonging to a project whose `product` is set to `grill`. Account-level API keys cannot call `/grill/*` directly.

This is intentional. A project namespace is the unit of isolation for ingested documents and their vectors, so the credential that talks to Grill must be tied to one.

## Token types at a glance

| Token | Prefix | Where it comes from | Use it for | Use it on `/grill/*`? |
| --- | --- | --- | --- | --- |
| Session JWT | — (JWT) | Console login (or `POST /verifyEmail`) | Browser session inside the Console | ❌ |
| Account API key | `poma_acc_…` | Console → **Account** → **Generate API key** | `/primeCut/ingest`, automation that needs account-wide access | ❌ |
| **Project API key** | `poma_prod_gr_…` | Console → **Grill** → **Context Engine** → create a project | **Every `/grill/*` call** | ✅ |

The prefix is your fastest sanity check: if the token in your `Authorization` header does **not** start with `poma_prod_gr_`, it cannot call `/grill/*`. The official Python SDK validates this prefix locally and raises `InvalidGrillApiKeyError` before the request ever leaves your machine.

The project API key is shown **exactly once** at project creation. POMA does not store it in plaintext — if you lose it you must rotate the project credential (see below).

## Get a Grill project API key — the Console workflow

The whole provisioning flow happens inside [console.poma-ai.com](https://console.poma-ai.com/):

1. **Sign in.** First visit creates a free account (1,000 trial pages, no card).
2. **(Optional) Create an organisation** under **Organisations** if you want the project to be team-owned. Skip this step for a personal project.
3. Go to **Grill → Context Engine**.
4. Under **Grill Projects**, type a project name, optionally pick an organisation, and click **Create**.
5. Copy the project API key the dialog hands back. **It's shown once.** Paste it into your secret manager immediately.

![POMA Console — Grill Context Engine view, showing the Grill Projects list and the New project form](/console/grill-context-engine.png)

The same page is also where you'll later drag in files for ad-hoc ingestion, rotate the project, or delete it. Everything Grill-related is in this view.

> Need to script project creation programmatically — e.g. provisioning one project per customer in a multi-tenant SaaS? The raw `POST /projects` flow lives in the [POMA API v3 reference](https://api.poma-ai.com/v3/docs#tag/projects). These docs intentionally don't duplicate it; the Console is the supported path for human-operated setup.

## Using the project API key

The Python SDK reads it from `POMA_GRILL_API_KEY` and validates the prefix at construction time. Direct HTTP callers just put the same token in a Bearer header.

::: code-group

```python [python (SDK)]
# pip install poma
import os
os.environ["POMA_GRILL_API_KEY"] = "poma_prod_gr_…"  # or load from .env

from poma import Grill

g = Grill()  # raises InvalidGrillApiKeyError if the prefix is wrong
ctx = g.search("What changed in the Q3 financial outlook?")
print(ctx.context)
```

```bash [curl]
export POMA_GRILL_API_KEY="poma_prod_gr_…"

curl -s https://api.poma-ai.com/v3/grill/docs \
  -H "authorization: Bearer $POMA_GRILL_API_KEY"
```

```python [python (requests)]
import os, requests

GRILL = "https://api.poma-ai.com/v3"
H = {"Authorization": f"Bearer {os.environ['POMA_GRILL_API_KEY']}"}

r = requests.post(
    f"{GRILL}/grill/search",
    headers={**H, "content-type": "application/json"},
    json={"query": "What changed in the Q3 financial outlook?"},
    timeout=30,
)
print(r.json()["context"])
```

```javascript [node]
const res = await fetch("https://api.poma-ai.com/v3/grill/search", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POMA_GRILL_API_KEY}`,
    "content-type": "application/json",
  },
  body: JSON.stringify({
    query: "What changed in the Q3 financial outlook?",
  }),
});
const { context } = await res.json();
```

:::

## Storing the key

- Treat the project API key like a database password — it grants full read/write/delete access to the project's document namespace.
- Inject via environment variable (`POMA_GRILL_API_KEY` — the name the SDK reads automatically) or your secret manager. Do **not** commit it.
- One key per project. If you run multiple Grill projects in the same process, instantiate `Grill(api_key=...)` explicitly per project rather than juggling the env var.
- Never use a project API key in browser code. It is a server-side credential. For browser flows, proxy through your backend.

## Rotation and recovery

If the key is leaked or lost, the recovery flow is again Console-driven:

1. Open the Console → **Grill → Context Engine**.
2. Find the project in the **Grill Projects** list and **delete** it. (Deletion invalidates the existing API key immediately.)
3. **Create** a new project under the same name in the same view. The dialog hands back a fresh `poma_prod_gr_…` key.
4. Re-ingest documents into the new project namespace.

A non-destructive rotate-without-losing-data flow is on the roadmap — until then, plan on keeping the project API key in a secret manager from day one. The equivalent raw API calls (`DELETE /projects/{id}` + `POST /projects`) are documented in the [API reference](https://api.poma-ai.com/v3/docs#tag/projects) if you need to script rotation.

## Common errors

| Where | Symptom | Cause | Fix |
| --- | --- | --- | --- |
| SDK (local) | `InvalidGrillApiKeyError` at `Grill()` construction | Token is missing, or has the wrong prefix (e.g. `poma_acc_…` instead of `poma_prod_gr_…`) | Generate the key from **Grill → Context Engine** in the Console, not from **Account**. |
| Server | `401` | Missing or invalid `Authorization` header | Check the header name and that the token is present, unquoted, and trimmed. |
| Server | `403` | Token belongs to a project whose `product` is `primecut`, not `grill` | Create a new project under **Grill → Context Engine** in the Console and use that project's key. |
| Server | `404` from `/grill/docs/{docId}` | Document is not in **this project's** namespace | Confirm the doc was ingested into the same project (same key) you're now querying with. |

## Next

- [Create a Grill project](/grill/getting-started/projects) — what a project owns, organisation-scoped projects, and what deletion really means.
- [Quickstart](/grill/getting-started/quickstart) — first ingest + search round-trip with the project key.