# Public configuration

```yaml
display_name: Public configuration
scope: client:read
endpoint:
  method: POST
  path: /config/get
billable: false
environment: required
```

There is no separate `capability_id`, `ui_capability_key`, or operation
identifier for this route. `client:read` is a baseline application scope;
Public Configuration is not a separately selectable console capability.

## Purpose and authorization

Public Configuration returns a fixed bundle of intentionally non-secret
provider configuration for the authenticated application. The values are
appropriate for client-facing use, but the Gateman endpoint is not anonymous.

The application MUST authenticate with [Contract v1](https://docs.getgateman.com/contract-v1/)
and MUST have the `client:read` scope. Gateman resolves configuration using the
authenticated immutable `app_id`; an application cannot select another
application's values.

This route does not require `secrets:read`, a credential-request identifier, or
any provider **Allow app retrieval** setting. Those controls belong to
`POST /secrets/get`. Provider operation scopes such as `stripe:use` and
`anti_bot.verify` are also independent. Retrieving a publishable or site key
does not authorize a provider operation.

Public Configuration returns only eligible public configuration values. Private
provider credentials are not available through this endpoint.

## Implementation contract

### Request

```http
POST /config/get
Content-Type: application/json
X-GM-App: app_example
X-GM-Ts: 1710000000
X-GM-Sig: <Contract-v1-signature>
```

The request MUST use ordinary signed Contract v1 authentication. The
`X-GM-Credential-Request-ID` header is not required for this route. If supplied,
it is accepted but not validated or used by the `/config/get` authentication or
route path; it does not affect signature verification, request processing, or
the response. Clients SHOULD ordinarily omit it. An `X-GM-Idempotency-Key` MAY
be supplied and is covered by the Contract v1 signature, but this route has no
endpoint-specific idempotency ledger.

The JSON body MUST contain exactly one field:

```json
{
  "environment": "staging"
}
```

Accepted values are `production`, `staging`, and `development`. The value is
required, is normalized by trimming and lowercasing, and is signed as part of
the request body. Additional fields are rejected. The caller MUST NOT submit
configuration names: this route returns a predefined bundle and is not a
list-all or caller-selected credential operation.

The four official Gateman clients can inject the configured environment for
environment-aware routes. A direct implementation MUST inject the same field
and MUST sign the exact transmitted JSON bytes.

### Eligible values

| Provider | Stored name | Response field |
| --- | --- | --- |
| Stripe production | `STRIPE_PUBLISHABLE_KEY` | `config.stripe.publishable_key` |
| Stripe test | `STRIPE_PUBLISHABLE_KEY_TEST` | `config.stripe.publishable_key` |
| Cloudflare Turnstile | `TURNSTILE_SITE_KEY` | `config.turnstile.site_key` |
| Google reCAPTCHA | `RECAPTCHA_SITE_KEY` | `config.recaptcha.site_key` |

Stripe selection is environment-aware:

| Request environment | Selected Stripe value |
| --- | --- |
| `production` | `STRIPE_PUBLISHABLE_KEY` |
| `staging` | `STRIPE_PUBLISHABLE_KEY_TEST` |
| `development` | `STRIPE_PUBLISHABLE_KEY_TEST` |

There is no fallback between Stripe production and test values. Turnstile and
reCAPTCHA site keys use their shared configured values across environments;
there are no additional environment-specific site-key names.

The route MUST NOT disclose Stripe secret keys, Stripe webhook secrets,
Turnstile secret keys, reCAPTCHA secret keys, private provider credentials,
arbitrary custom credentials, or provider configuration metadata.

### Successful response

```http
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-store
```

```json
{
  "ok": true,
  "config": {
    "stripe": {
      "publishable_key": "pk_test_example"
    },
    "turnstile": {
      "site_key": "0x_example"
    },
    "recaptcha": {
      "site_key": "6Lc_example"
    }
  }
}
```

The values above are fictitious examples. Values that are not configured or
otherwise unavailable are omitted. Empty provider groups are omitted, and an
entirely empty configuration is valid:

```json
{
  "ok": true,
  "config": {}
}
```

### Errors

Gateman returns the shared JSON error shape for errors generated by this route:

```json
{
  "ok": false,
  "error": "Invalid JSON"
}
```

The applicable outcomes are:

| Condition | Status and response error |
| --- | --- |
| Missing or invalid authentication, stale timestamp, inactive application, or source-IP rejection | HTTP `401`; the authentication error, such as `Missing required auth headers`, `Invalid signature`, `Unknown app`, or `Authentication failed`. |
| Missing `client:read` | HTTP `401`, `Scope not allowed`. |
| Application concurrency limit | HTTP `429`, `app_concurrency_limit_exceeded`, with `Retry-After: 1`. |
| Application concurrency guard unavailable | HTTP `503`, `app_concurrency_guard_unavailable`. |
| Invalid JSON | HTTP `400`, `Invalid JSON`. |
| Missing environment | HTTP `400`, `Application environment is required`. |
| Unsupported environment | HTTP `400`, `Unsupported application environment`. |
| Any field other than `environment` | HTTP `400`, `This endpoint accepts only environment`. |
| Public configuration rate-limit dependency unavailable | HTTP `503`, `Public configuration rate limit unavailable`. |
| Dedicated request-frequency limit exceeded | HTTP `429`, `public_config_rate_limit_exceeded`, with `Retry-After`. |
| Public configuration retrieval failure | HTTP `500`, `Public configuration unavailable`. |

Unavailable individual values do not produce an error; they are omitted from
an otherwise successful response. There are no name-selection or excessive-name
errors because callers cannot submit names.

## Rate limits, accounting, and runtime reuse

Public Configuration is non-billable. It does not create a billable operation
reservation or completion record, and it does not consume the monthly billable
operation allowance.

The endpoint has a separate fixed-window limit of **10 requests per application
per UTC hour**. The 11th request in the current UTC-hour window returns:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds until the UTC-hour window ends>
```

```json
{
  "ok": false,
  "error": "public_config_rate_limit_exceeded"
}
```

Authenticated, valid requests reaching this limiter consume its capacity,
including requests that return an empty configuration. Rate-limit rejections
also increment the limiter's attempt counter under the current implementation.
Authentication and request-validation failures that occur before the limiter
do not consume its capacity. The shared application concurrency guard also
applies.

HTTP response caching and application-managed runtime reuse are different:

- Gateman returns `Cache-Control: no-store`, which governs HTTP response
  caching. Applications MAY retain extracted public configuration values in
  application-managed runtime state and reuse them as appropriate.
- An application SHOULD retrieve configuration at initialization or deliberate
  refresh points rather than repeatedly during ordinary user interactions.
- An application MAY deliberately persist the values according to its own
  security and freshness decisions.
- The application SHOULD reuse the values for later application operations and
  MUST NOT request them for every page load, user interaction, payment
  operation, or anti-bot render.
- A deliberate refresh MUST respect the 10-request-per-UTC-hour limit.
- A Gateman configuration change is visible on the next uncached retrieval; it
  does not automatically update values already retained by the application.

Gateman does not provide a universal runtime TTL, freshness guarantee, storage
mechanism, or invalidation protocol. The WordPress integration's 12-hour
success cache and 15-minute error cache for Stripe publishable-key status are
integration-specific choices, not a universal Gateman contract.

Official clients do not automatically retry. A caller implementing retries
MUST account for the dedicated request-frequency limit and SHOULD avoid
unnecessary repeated retrievals.

## Implementation outcome

A correct integration produces these independently assessable outcomes:

- It authenticates and retrieves the fixed public configuration bundle for its
  own application.
- It sends the required environment and receives the correct Stripe
  production/test publishable key for that environment.
- It handles missing values and omitted provider groups without assuming that
  every provider is configured.
- It keeps private provider credentials out of client-facing configuration.
- It reuses public configuration in application-managed runtime state instead
  of requesting it for every user interaction; this is distinct from HTTP
  response caching.
- It refreshes deliberately while respecting the request-frequency limit and
  the HTTP `no-store` response policy.
- It handles authentication, concurrency, rate-limit, storage, and validation
  errors according to their actual HTTP status and error string.
- It does not treat Public Configuration retrieval as permission to perform a
  Stripe, Turnstile, reCAPTCHA, or other provider operation.

## Implementation instructions for agents

- **SHOULD** use an official Gateman client where appropriate. A direct HTTP
  implementation **MUST** conform to Contract v1.
- The integration **MUST** use authenticated `POST /config/get` with the
  `client:read` scope and a required `environment` of `production`, `staging`,
  or `development`.
- It **SHOULD** ordinarily omit `X-GM-Credential-Request-ID` for this route. It
  is not required and, if supplied, is not validated or used by the
  `/config/get` authentication or route path.
- It **MUST NOT** submit configuration names or add a caller-controlled field
  selector; the response bundle is fixed.
- It **MUST** use the Stripe production value only for `production`, and the
  Stripe test value for `staging` and `development`.
- It **MUST** treat Turnstile and reCAPTCHA site keys as shared configured
  values and **MUST NOT** invent environment-specific site-key names.
- It **MUST** handle omitted values and provider groups explicitly.
- It **MUST** keep private provider credentials out of this configuration path
  and **MUST NOT** grant `secrets:read` merely to retrieve public values.
- It **MUST NOT** require provider **Allow app retrieval** settings for this
  route.
- It **SHOULD** retrieve configuration at initialization or deliberate refresh
  points and **MAY** retain extracted values in application-managed runtime
  state for reuse. This is distinct from HTTP response caching.
- It **MUST** respect the 10-request-per-UTC-hour limit and handle
  `Retry-After` on `429` responses.
- It **SHOULD NOT** add automatic retries that poll the endpoint repeatedly.
- It **MUST NOT** treat a returned public key as provider API authorization.
- It **MUST NOT** convert the endpoint into an anonymous configuration service,
  general credential store, or CDN.

## Reusable AGENTS.md block

```markdown
<!-- gateman:begin capability=public-configuration -->
- Retrieve public provider configuration only through authenticated
  `POST /config/get` using the application's baseline `client:read` scope.
- Supply exactly one required JSON field: `environment`, with value
  `production`, `staging`, or `development`.
- The response is a fixed bundle. Handle omitted Stripe, Turnstile, and
  reCAPTCHA values; do not request names or expect private credentials.
- Stripe uses the production publishable key for `production` and the test
  publishable key for `staging` and `development`. Turnstile and reCAPTCHA
  site keys are shared across environments.
- Gateman remains authenticated even though returned values are public. This
  route does not require `secrets:read` or provider **Allow app retrieval**.
- `X-GM-Credential-Request-ID` is not required for this route. Applications
  SHOULD ordinarily omit it; if supplied, it is not validated or used by the
  `/config/get` authentication or route path.
- Retrieve at initialization or deliberate refresh points rather than
  repeatedly during ordinary user interactions. Applications MAY retain
  extracted values in application-managed runtime state and reuse them.
- Gateman returns `Cache-Control: no-store`, which governs HTTP response
  caching; it does not prohibit application-managed runtime reuse. Do not
  assume a universal local TTL or automatic invalidation guarantee.
- The route is limited to 10 requests per application per UTC hour. Respect
  `Retry-After` on `429` responses and do not add unnecessary automatic
  retries.
- Public Configuration does not authorize Stripe, Turnstile, reCAPTCHA, or
  other provider operations. Keep private provider credentials separate.
- Full reference: https://docs.getgateman.com/public-configuration/
<!-- gateman:end capability=public-configuration -->
```

## Related references

- [Contract v1](https://docs.getgateman.com/contract-v1/)
- [Authentication and signing](https://docs.getgateman.com/authentication/)
- [Environments](https://docs.getgateman.com/environments/)
- [Access stored credentials](https://docs.getgateman.com/capabilities/secrets.read/)
- [Errors and responses](https://docs.getgateman.com/errors-and-responses/)
- [Rate limits and concurrency](https://docs.getgateman.com/rate-limits-and-concurrency/)
- [Operation Accounting](https://docs.getgateman.com/operation-accounting/)
- [Data retention](https://docs.getgateman.com/data-retention/)
- [Official PHP client](https://docs.getgateman.com/canonical-client-php/)
- [Official Node.js client](https://docs.getgateman.com/canonical-client-node/)
- [Official Python client](https://docs.getgateman.com/canonical-client-python/)
- [Official Go client](https://docs.getgateman.com/canonical-client-go/)
