# Gateway HTTP

Gateway HTTP is the HTTP surface of an Agent Gateway environment: the MCP endpoint, the REST mirror, the documents it publishes and the sign-in endpoints. Use this page when you call a gateway from your own code or need to read an error.

## URLs

Every URL of an environment starts with the same base. `<workspace>` is your workspace's slug and `<gateway>` is the gateway's slug.

```text
https://gateway.faustine.ai/<workspace>/<gateway>                  the default environment
https://gateway.faustine.ai/<workspace>/<gateway>/<environment>    any other environment
```

With a [custom domain](https://docs.faustine.ai/agent-gateway/custom-domain/), your domain replaces `gateway.faustine.ai/<workspace>`: `https://<your-domain>/<gateway>[/<environment>]`. Both addresses keep working.

| Path after the base | Methods | What it serves |
| --- | --- | --- |
| `/mcp` | `POST`, `GET`, `DELETE` | The MCP endpoint, over Streamable HTTP. `POST` carries JSON-RPC. The server keeps no state between requests. |
| `/api/<namespace>/<operation>` | `POST`, `GET` | The REST mirror. One URL per operation. |
| `/openapi.json` | `GET` | The OpenAPI document of the environment. |
| `/docs` | `GET` | The docs page. |
| `/skill.md` | `GET` | A skill file that describes the gateway to a coding agent. |
| `/.well-known/oauth-protected-resource` | `GET` | OAuth protected resource metadata. |
| `/.well-known/oauth-authorization-server` | `GET` | OAuth authorization server metadata. |
| `/oauth/register`, `/oauth/authorize`, `/oauth/token`, `/oauth/revoke` | | The OAuth endpoints gateway users sign in through. |

`/openapi.json`, `/docs` and `/skill.md` are public by default. When an environment's docs are not public, they require a credential and show only the operations that credential can call. See [Customize the gateway docs page](https://docs.faustine.ai/agent-gateway/docs-page/).

In the REST path, `<namespace>` is the API's namespace and `<operation>` is the rest of the operation key. The operation `crm.contacts.create` is served at `/api/crm/contacts.create`.

## Authentication

Every request to `/mcp` and `/api` carries a bearer token.

```http
Authorization: Bearer fik_…
```

| Credential | Token | How the client gets it |
| --- | --- | --- |
| API key | Starts with `fik_` | You create the key in the portal and give it to the client. See [Create an API key](https://docs.faustine.ai/agent-gateway/api-keys/). |
| Gateway user | Starts with `fot_` | The client obtains it from the gateway's OAuth server after the person signs in. See [Create a gateway user](https://docs.faustine.ai/agent-gateway/gateway-users/). |

A credential works on the environment it belongs to. On any other environment or gateway, the response is the same as for an unknown credential.

### Sign-in with OAuth

The gateway is its own OAuth 2.0 authorization server, one per environment. A request without a valid token gets `401` with a challenge that names the metadata document:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://gateway.faustine.ai/<workspace>/<gateway>/.well-known/oauth-protected-resource"
```

| Property | Value |
| --- | --- |
| Client registration | Dynamic, at `/oauth/register`. Public clients only (`token_endpoint_auth_method: none`). |
| Grant types | `authorization_code`, `refresh_token` |
| PKCE | Required, with `S256`. |
| Access token lifetime | 3600 seconds |
| Refresh token lifetime | 30 days |

A session is valid only on the host it was obtained through. A token issued on `gateway.faustine.ai` does not work on a custom domain, and the reverse.

### Failed authentication

A `401` body is JSON with a `message` field.

| Message | Cause |
| --- | --- |
| `Invalid API key` | The key is unknown, or belongs to another environment or gateway. |
| `Invalid session token` | The session token is unknown, or belongs to another environment, gateway or host. |
| `API key revoked`, `Gateway user revoked` | The credential was revoked. |
| `API key rotated and its grace period has ended` | The key was rotated out. |
| `Denied (expired): API key expired` | The credential's **Expires** date has passed. |
| `Denied (not_yet_valid): …` | The credential's **Not before** date has not been reached. |
| `Access token expired`, `Session revoked` | The session has to be refreshed, or the person has to sign in again. |

After 30 failed authentications from one IP address in a minute, every request from that address gets `429` with the code `auth_throttled` and a `Retry-After` header, until the minute ends.

## Request headers

| Header | Required | Description |
| --- | --- | --- |
| `Authorization` | Yes | `Bearer` and the key or the session token. |
| `Content-Type` | For `POST` with a body | `application/json` |
| `X-Faustine-Connection` | No | The name of the connection to call, when the API's routing lets the caller choose. Same as the `$connection` input field. See [Routing: fixed, dynamic and fan-out](https://docs.faustine.ai/agent-gateway/routing/). |
| `X-Upstream-Authorization` | No | The caller's own credentials for your API, when the API passes credentials through. See [Pass the caller's credentials through](https://docs.faustine.ai/apis/pass-through-credentials/). |

## Response headers

| Header | Description |
| --- | --- |
| `X-Faustine-Request-Id`, `x-request-id` | The ID of the request, starting with `req_`. Search the gateway's **Logs** tab by it. |
| `X-Faustine-Manifest-Version` | The versions of the APIs the environment serves. It changes when the served operations change. |
| `Retry-After` | Seconds to wait, on `429` responses. |
| `Allow` | The accepted methods, on `405` responses. |

## REST requests

A REST call runs one operation. The request body is the operation's input.

| Method | Input | Accepted for |
| --- | --- | --- |
| `POST` | A JSON object in the body. An empty body is an empty input. | Every operation. |
| `GET` | The query string. | Read operations only. A `GET` to a write operation returns `405`. |

With `GET`, a query value is read as a boolean, a number, `null` or JSON when it looks like one, unless the operation's schema declares the field as a string. A repeated key becomes an array.

`<GATEWAY_KEY>` is an API key of the environment.

```bash
curl -X POST https://gateway.faustine.ai/<workspace>/<gateway>/api/crm/contacts.create \
  -H "Authorization: Bearer <GATEWAY_KEY>" \
  -H "Content-Type: application/json" \
  -d '{"first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.com"}'
```

## REST responses

A successful call returns `200` with the status and the body your API answered.

```json
{
  "status": 201,
  "body": { "id": 42, "first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.com" }
}
```

Under fan-out routing, `body` is `{ "byConnection": [ … ] }` with one entry per connection.

## Errors

A failed call returns an error envelope.

```json
{
  "error": {
    "code": "scope",
    "message": "Denied (scope): This key is not scoped for \"crm.contacts.delete\"",
    "requestId": "req_01JCD9T3W8Q2M4X6Z0V5B7N1KP"
  },
  "denyReason": "scope"
}
```

| Field | Description |
| --- | --- |
| `error.code` | A stable code from the tables on this page. |
| `error.message` | A sentence for a person or a model. A refusal starts with `Denied (<code>):`. |
| `error.requestId` | The request's ID in the gateway's **Logs** tab. |
| `denyReason` | Present when the gateway refused the call. Same value as the code. |
| `status`, `body` | Present on `upstream_error`. The status and the body your API answered. |

Over MCP, a failed operation is a tool result marked as an error, with the same message as its text. A request refused before any tool runs (a paused gateway, a channel or an IP restriction) is an HTTP error with the envelope.

### Refused by the credential or the environment

| Code | Status | Cause |
| --- | --- | --- |
| `scope` | 403 | The operation is not in the credential's scope. |
| `read_only` | 403 | The credential is read-only and the operation writes. |
| `requires_confirmation` | 403 | The operation requires confirmation and was not confirmed: the credential is set to **Refuse**, or the client cannot ask the person. See [Confirmation](https://docs.faustine.ai/agent-gateway/confirmation/). |
| `channel` | 403 | The credential or the environment does not allow this channel. |
| `ip_not_allowed` | 403 | The caller's address is not on the allowlist. |
| `ip_blocked` | 403 | The caller's address is on a blocklist or belongs to a blocked country. |
| `write_not_available` | 403 | The API uses fan-out routing and the write is disabled or names no connection. |
| `gateway_paused` | 503 | The gateway is paused. |

### Wrong request

| Code | Status | Cause |
| --- | --- | --- |
| `unknown_operation` | 404 | No operation with that key is exposed in this environment. |
| `method_not_allowed` | 405 | `GET` on a write operation, or a method other than `GET` and `POST`. |
| `invalid_input` | 400 | The input is not a JSON object. |
| `no_connection` | 400 | The API has several connections and the request does not select one, or names one that is not available. |
| `body_too_large` | 413 | The body exceeds the credential's limit. |

### Limits

| Code | Status | Cause |
| --- | --- | --- |
| `rate_limit` | 429 | The credential's rate limit or the environment-wide rate limit was exceeded. |
| `quota` | 429 | The hourly or daily quota is used up. |
| `concurrency` | 429 | The credential has too many calls in flight. |
| `runner_busy` | 429 | A recipe could not start because the code runner is at capacity. |
| `auth_throttled` | 429 | Too many failed authentications from the caller's address. |

Every `429` carries `Retry-After` in seconds. See [Limits and quotas](https://docs.faustine.ai/agent-gateway/limits/) for the defaults.

### Repeated writes

These codes exist when the environment deduplicates repeated writes. A repeat of a finished write is not an error: it returns `200` with the stored response of the first call.

| Code | Status | Cause |
| --- | --- | --- |
| `idempotency_in_flight` | 409 | The same write was sent before and has not finished, or its outcome is unknown. |
| `idempotency_conflict` | 409 | A write with the same identifying fields and different values ran inside the window. |
| `idempotency_duplicate` | 409 | The same write already ran, and its response was larger than 64 KB and was not kept. |

See [Repeated writes](https://docs.faustine.ai/agent-gateway/repeated-writes/).

### Your API and recipes

| Code | Status | Cause |
| --- | --- | --- |
| `upstream_error` | 502 | Your API answered with an error status or could not be reached. |
| `auth_failed` | 502 | The gateway could not sign in to your API with the connection's credentials. |
| `no_credential` | 502 | The connection has no credentials. |
| `no_base_url` | 502 | The connection has no base URL. |
| `response_too_large` | 502 | Your API's response is larger than the gateway reads. |
| `recipe_error` | 422 | The code of a recipe threw an error. |
| `sandbox_limit` | 422 | A recipe exceeded its time, memory or call limit. |
| `recipe_http_denied` | 400 | A recipe tried to reach a URL it is not allowed to call. |
| `runner_unavailable` | 503 | The code runner is not available. |
| `internal` | 500 | An error inside the gateway. |

## Next steps

- [Call a gateway over REST and OpenAPI](https://docs.faustine.ai/technologies/rest/): generate a client from the OpenAPI document.
- [Connect clients](https://docs.faustine.ai/agent-gateway/connect-clients/): connect an MCP client instead of calling HTTP yourself.
- [Logs](https://docs.faustine.ai/agent-gateway/logs/): find a request by its ID.
