# Authentication

> How Gomry Cloud API keys work: the gck_live_ format, scopes per operation, and the two independent checks every request passes.

Source: https://cloud.gomry.com/docs/authentication

Gomry Cloud authenticates machines with API keys, sent as a bearer token. There is no session, no OAuth flow and no browser-facing credential — a Cloud key is a server-side secret.

## Sending a key

```http
POST /v1/scrape HTTP/1.1
Host: api.cloud.gomry.com
Authorization: Bearer gck_live_...
Content-Type: application/json
```

> **Important:** Never put a Cloud key in browser JavaScript, a mobile app, or anything else you ship to a user. The data plane sends no CORS headers, deliberately — a key that a browser can read is a key anyone can spend.

## Key format

A key is the `gck_live_` prefix plus 32 random bytes. There is one kind of key: it does the work and bills for it. Spend is bounded by the project's monthly budget cap, not by the credential.

> **Important:** `gck_test_` keys are retired. They ran the real request against the real upstream and were simply not invoiced, which made "test" a promise of a sandbox that never existed. Existing ones no longer authenticate — mint a replacement in the console.

We store only a SHA-256 hash of the key. The plaintext is returned once, at mint time, and cannot be recovered — if it is lost, revoke it and mint another. The console shows a key by its prefix and last four characters, which is enough to tell two keys apart and useless to anyone who steals the display.

## Scopes

A scope names exactly one operation a key may perform, in the form `service:action`. A key carries the scopes you grant it and nothing else.

- `scrape:run` — Call POST /v1/scrape — render a page and extract structured data from it.
- `scrape:fetch` — Call POST /v1/fetch — render a page and return its HTML and metadata.
- `toolsapi:read` — Search and inspect the tool catalogue — POST /v1/toolsapi/discover, POST /v1/toolsapi/inspect, and polling a run with GET /v1/toolsapi/runs/{runId}. Reads only; it cannot execute anything.
- `toolsapi:run` — Call POST /v1/toolsapi/run — execute a tool and pay its upstream cost. This is the scope that spends: grant it separately from toolsapi:read.
- `tasks:run` — Call POST /v1/superagent — start a research task. This is the scope that spends, and it commits to work that continues after the request returns.
- `tasks:read` — Poll a task with GET /v1/superagent/{taskId} and stop one with DELETE. Neither is billed, so a worker that only collects results needs nothing more than this.

> **Important:** Scopes are flat: there are no wildcards, and `scrape:run` does **not** imply `scrape:fetch`. They are separately priced operations, so they are separately granted — a key minted for a cheap fetch loop must not be able to spend four times the rate on extractions.

## Two independent checks

A request is authorized by two separate facts, and both must hold. They fail with different codes so you can tell them apart without guessing.

| Check | Failure | Fix |
| --- | --- | --- |
| The key carries the scope for this operation | `403 forbidden` | Mint a key with the scope, or grant it. |
| The project has this API enabled | `403 api_not_enabled` | Turn the API on for the project in the console. |

Keeping them separate is what stops each from silently widening the other: enabling an API must not grant new power to keys that already exist, and a broadly scoped key must not reach an API the project never turned on.

## Rotation and revocation

Revocation takes effect on the next request — there is no cache of authorization decisions to wait out. To rotate without downtime, mint the new key, deploy it, then revoke the old one. A key can also be given an expiry at mint time, which is the better default for anything handed to a third party.
