# Read database passwords

```yaml
capability_id: db:read
display_name: Read database passwords
ui_capability_key: read_database_passwords
scope: db:read
endpoint:
  method: POST
  path: /db/get
operation: database_password.get
billable: true
environment: not_applicable
```

## Purpose and authorization

`db:read` authorizes an application to retrieve one named database password
through `POST /db/get`. It is a separate capability from `secrets:read`.

The application must authenticate with Contract v1 credentials and the
authenticated application must have the `db:read` scope. Retrieval is
application-scoped; database host, port, database name, user, charset, and
connection handling remain the application's responsibility.

## Implementation contract

### Request

Send a Contract v1 authenticated `POST` request to `/db/get` with a JSON body
containing exactly one logical name for the lookup:

```http
POST /db/get
Content-Type: application/json
X-GM-App: app_example
X-GM-Ts: 1791748800
X-GM-Sig: <Contract-v1-signature>
X-GM-Credential-Request-ID: 0123456789abcdef0123456789abcdef
```

```json
{
  "name": "primary"
}
```

The name is trimmed before validation and must match
`[a-zA-Z][a-zA-Z0-9_.-]{0,63}`. It is at most 64 characters: the first
character must be an ASCII letter, and remaining characters may be ASCII
letters, digits, `.`, `_`, or `-`. A request must include the signed
single-use `X-GM-Credential-Request-ID` required by this credential route.
The request ID is a fresh lowercase 32-character hexadecimal value for each
attempt. Contract v1 authentication headers and signing rules apply; see the
authentication and contract references below.

Only one name can be requested per call. The endpoint does not list names,
enumerate credentials, or provide bulk retrieval. Additional JSON fields are
ignored by the current route; clients should send only the documented field.

### Successful response: password available

When the requested value is available, the response is HTTP `200`:

```json
{
  "ok": true,
  "name": "primary",
  "password": "example-password"
}
```

### Successful response: password unavailable

An unknown or otherwise unavailable logical name is intentionally omitted,
not disclosed as a separate absence error. The response is HTTP `200`:

```json
{
  "ok": true,
  "name": "primary"
}
```

The `password` property is absent, rather than `null`, an empty string, or a
fabricated value. Applications must distinguish an omitted property from an
actual returned password by checking whether the property exists. Both
successful response forms use `Cache-Control: no-store`.

### Environment independence

Database passwords are environment-independent. `/db/get` has no
`environment` selector, and official clients do not add one. A logical name
refers to the application-scoped password configured under that name across
the application's environments. Applications that need distinct values must
use distinct logical names and keep their environment mapping locally.

### Error outcomes

The omission rule applies only to credential absence. Genuine operational,
authentication, validation, replay, entitlement, concurrency, and accounting
failures remain errors:

| Condition | HTTP response | Contract meaning |
| --- | ---: | --- |
| Authentication failure | `401` | Authentication is missing, invalid, expired, or otherwise rejected. |
| Missing `db:read` scope | `401` | `Scope not allowed`. |
| Invalid JSON, missing name, or invalid name | `400` | `Invalid JSON` or `Invalid request`. |
| Invalid credential request ID | `401` | `Invalid credential request ID`. |
| Reused credential request ID | `409` | `Credential request already used`. |
| Credential retrieval unavailable | `503` | `Credential retrieval unavailable`. |
| Monthly operation allowance exhausted | `403` | `monthly_operation_limit_reached`. |
| Operation accounting unavailable | `503` | `Operation accounting unavailable`. |
| Application concurrency limit reached | `429` | `app_concurrency_limit_exceeded`. |
| Database password retrieval failed | `500` | `Database password unavailable`. |

## Operation and replay obligations

Every accepted lookup consumes one billable `database_password.get`
operation, regardless of whether a password is returned. Authentication,
validation and replay rejections do not consume an operation.

A credential-request identifier is single-use and may be consumed even when
a subsequent failure prevents disclosure. If the outcome is uncertain, the
application **MUST NOT** assume that no operation was charged. A new attempt
requires a fresh identifier and signature and may consume another operation.

The credential request ID is signed and scoped to the application.
An optional idempotency key does not make this credential request reusable or
deduplicate it.

## Implementation outcome

An implementation conforming to this capability:

- authorizes `db:read` and calls `POST /db/get` with one validated logical name;
- treats an HTTP `200` response without `password` as the intentional
  unavailable-credential result;
- never infers absence from a `404` response for this route;
- accounts for Gateman charging one billable `database_password.get` operation
  per accepted lookup, including when no password is returned;
- preserves genuine errors and respects the HTTP `Cache-Control: no-store`
  response policy;
- does not request database connection metadata; and
- keeps returned plaintext in application-controlled memory only as long as
  needed, without logging or exposing it.

## Implementation instructions for agents

- An integration **SHOULD** use an official Gateman client where appropriate.
  A direct HTTP implementation **MUST** conform to Contract v1.
- The integration **MUST** use authenticated `POST /db/get` with the
  application's `db:read` scope and one validated logical name per request.
- The integration **MUST** generate a fresh signed, single-use lowercase
  32-character hexadecimal `X-GM-Credential-Request-ID` for every attempt.
- The integration **SHOULD** omit `environment` from `/db/get` requests.
  Database-password retrieval is environment-independent, and the endpoint
  does not use `environment` to select a password. The integration **MUST
  NOT** enumerate names or request more than one logical name per call.
- The integration **MUST** treat HTTP `200` with `password` as a returned
  value and HTTP `200` without the `password` property as an unavailable
  value. It **MUST** preserve the requested logical name.
- The integration **MUST** preserve genuine authentication, validation,
  replay, accounting, concurrency, and retrieval errors as errors; it **MUST
  NOT** convert them into an unavailable-password result.
- The integration **MUST** account for Gateman charging one billable
  `database_password.get` operation per accepted lookup, whether or not
  `password` is present.
- The integration **SHOULD NOT** automatically retry an uncertain disclosure.
  A deliberate retry policy **MAY** use a fresh request ID and signature and
  **MUST NOT** assume that the ambiguous attempt was unaccepted or unbilled.
- The integration **MUST** protect returned plaintext and **SHOULD** keep it
  in memory only as long as needed; it **MUST NOT** log, commit, or expose it
  unnecessarily.
- Database connection details and environment mapping **MUST** remain the
  application's responsibility; Gateman does not create database connections
  or supply connection parameters.

## Reusable AGENTS.md block

```markdown
<!-- gateman:begin capability=db:read -->
- Read one application-scoped database password with authenticated Contract v1
  `POST /db/get` using `db:read`.
- Send one validated logical `name` per request. Database passwords are
  environment-independent. Omit `environment` from requests; Gateman does
  not use it to select a password. Do not attempt enumeration or bulk
  retrieval.
- Include the signed, single-use lowercase 32-character hexadecimal
  `X-GM-Credential-Request-ID`; use a fresh ID and signature for every retry.
- Names MUST be trimmed before validation and match `[a-zA-Z][a-zA-Z0-9_.-]{0,63}`.
- HTTP `200` with `password` means a value was returned. HTTP `200` without
  the `password` property means the requested name is unknown or unavailable;
  do not expect a separate absence error.
- The integration MUST account for Gateman charging one
  `database_password.get` operation per accepted lookup, whether or not
  `password` is present. Authentication, validation, and replay rejections
  remain errors and do not consume an accepted lookup.
- Do not assume an ambiguous transport outcome was unaccepted or unbilled;
  do not reuse its request ID.
- Treat returned passwords as confidential plaintext: do not log, commit,
  expose, or retain them longer than needed. Database connection metadata and
  environment mapping remain application-owned.
- Full capability reference: https://docs.getgateman.com/capabilities/database-password.read/
<!-- gateman:end capability=db:read -->
```

## Related references

- [Contract v1](https://docs.getgateman.com/contract-v1/)
- [Authentication](https://docs.getgateman.com/authentication/)
- [Environments](https://docs.getgateman.com/environments/)
- [Access stored credentials](https://docs.getgateman.com/capabilities/secrets.read/)
- [Public configuration](https://docs.getgateman.com/public-configuration/)
- [Operation accounting](https://docs.getgateman.com/operation-accounting/)
- [Errors and responses](https://docs.getgateman.com/errors-and-responses/)
- [Idempotency](https://docs.getgateman.com/idempotency/)
- [Rate limits and concurrency](https://docs.getgateman.com/rate-limits-and-concurrency/)
- [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/)
