# Authentication for agents

> How an automated caller obtains, uses, rotates and revokes a Gomry Cloud credential, and which parts of the agent-auth specs this platform does and does not implement.

Source: https://cloud.gomry.com/auth

This page is addressed to **software**. It says how a program gets a credential for Gomry Cloud, what that credential may do, and what happens when it is refused. The equivalent written for a person is [Authentication](https://cloud.gomry.com/docs/authentication).

## Discover

The resource server is `https://api.cloud.gomry.com`. Its protected-resource metadata is published under [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) at [`/.well-known/oauth-protected-resource`](https://cloud.gomry.com/.well-known/oauth-protected-resource), on both this host and the API host. It names the resource, every scope this platform defines, and the bearer methods accepted.

An unauthenticated call to any `/v1` endpoint answers `401` with a `WWW-Authenticate: Bearer resource_metadata="…"` header pointing at that document, so one request is enough to learn the requirements. The callable surface itself is described at [`/openapi.json`](https://cloud.gomry.com/openapi.json), and [`/.well-known/api-catalog`](https://cloud.gomry.com/.well-known/api-catalog) is the RFC 9727 linkset that points at both.

> **Important:** **What this platform does not implement, stated plainly so nothing has to guess.** There is no OAuth 2.0 authorization server, so there is no `/.well-known/oauth-authorization-server` document, no `agent_auth` block, no `identity_endpoint`, no `claim_endpoint` and no `events_endpoint`. No `identity_assertion` flow is accepted, `service_auth` is not offered, and an ID-JAG assertion (`urn:ietf:params:oauth:token-type:id-jag`) will be refused like any other unrecognised bearer token. A credential is minted by a human in the console and handed to the agent. If that changes, this section changes with it — and the metadata document above is where a machine should look, not this prose.

## Pick a method

There is one method: a project-scoped API key, sent as a bearer token. It is not a user token — it carries no person's identity, it does not expire with a session, and it cannot be exchanged for one.

| Key prefix | Behaviour | Use it for |
| --- | --- | --- |
| `gck_live_` | The real request, metered and invoiced. | Production. |
| `gck_test_` | The same request path and the same real upstream call, returning real data, metered at zero. | A test environment, a CI run, or an agent exercising the integration before it is trusted with spend. |

> **Note:** A `gck_test_` key is the sandbox. It is not a mock and not a separate host: the URL, the request body and the response shape are identical to production, which is the point — an integration proven with a test key works unchanged when the prefix changes. It still requires an organization with a payment method, because it still spends real upstream cost; what it does not do is bill you for it.

## Register

Registration is self-serve and needs no sales contact. A person signs in at [the console](https://cloud.gomry.com/sign-in) with a Google account, an email code or a phone number, creates or adopts an organization, adds a payment method, and creates a project. Nothing is provisioned for an agent automatically — there is no dynamic client registration endpoint.

1. **Create a project** — The unit of isolation and of spend. Keys, enabled APIs, usage and the monthly budget cap all belong to one project.

2. **Enable the APIs the agent will call** — Enabling is separate from scopes on purpose: a key minted with a broad scope still cannot reach an API the project never enabled.

3. **Set a budget cap** — Checked before every request, from local tables. This is the real ceiling on what an unattended caller can spend, and it is the control to set before handing a key to one.

## Claim

A key is minted in the console against a project, with an explicit list of scopes and an optional expiry. Grant only the scopes the agent will call — scopes are flat, there are no wildcards, and a read scope never implies the matching run scope. Every scope this platform defines is listed at [Authentication](https://cloud.gomry.com/docs/authentication) and in the protected-resource metadata.

> **Important:** The plaintext key is shown **once**, at mint time. Only a SHA-256 hash is stored, so it cannot be recovered — put it in your secret manager in the same step that creates it. An expiry at mint time is the better default for anything handed to a third party or to an unattended process.

## Exchange

There is no exchange step. The key you were given is the credential you send — there is no token endpoint, no refresh token, no assertion to present and nothing to mint at runtime. An agent that has the key is ready to call.

## Use the access token

Send it as a bearer token on every request. There is no cookie, no session and no ambient credential: a browser session cannot authenticate a data-plane call, and a key cannot authenticate a console call.

Request:
```bash
curl -sS https://api.cloud.gomry.com/v1/fetch \
  -H "Authorization: Bearer $GOMRY_CLOUD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'
```

Every response carries `x-request-id`; capture it, because an intermittent failure cannot be traced without it. Billable responses report the quantity they metered in their own body, so what you were charged arrives in the same payload as the result.

## Errors

Failures are JSON with a stable `error` code and a human `message`. Branch on the code; the message is written for a person and may be reworded. The complete table is at [Errors](https://cloud.gomry.com/docs/errors) — these are the ones an authentication layer has to handle.

| Status | Code | What an agent should do |
| --- | --- | --- |
| `401` | `unauthorized` | The key is missing, malformed, unknown, revoked or expired. Do not retry — the credential has to be replaced. The `WWW-Authenticate` header names the metadata document. |
| `403` | `forbidden` | The key is valid but lacks the scope for this operation. Do not retry; a broader key must be minted. |
| `403` | `api_not_enabled` | The project has not enabled this API. Do not retry; a person must turn it on. |
| `402` | `billing_required`, `billing_past_due`, `billing_suspended`, `insufficient_credit` | Spending is blocked until a human adds a card, fixes one, or raises the project budget. **Never retry in a loop** — nothing an agent can do resolves these. |
| `429` | `rate_limited` | Back off until `x-ratelimit-reset`, with jitter. Limits are per key, not per IP. |
| `5xx` | `internal_error`, `upstream_error`, `provider_unavailable` | Retryable with backoff, and not billed. |

## Revocation

Revoke a key in the console. It takes effect on the **next request** — there is no cache of authorization decisions to wait out and no propagation delay to plan around. To rotate without downtime, mint the replacement, deploy it, then revoke the old one.

Revocation does not stop work already in flight that outlives its request: a research task started before the key was revoked continues and is still billed, because the spend was committed when the task was accepted. Stop it explicitly with `DELETE /v1/superagent/{taskId}`. Deleting a project revokes every key it owns.
