# Official Gateman PHP client

The official Gateman PHP client is a standalone implementation of the
authenticated transport and authentication boundary defined by [Contract v1](https://docs.getgateman.com/contract-v1/).
PHP, Node.js, Python, and Go are equally official Gateman clients.

Client version: 1.0.0.

## Requirements and acquisition

The client requires PHP 8.3 or newer, cURL, and JSON support. Sodium is
required for Ed25519 signing. There is no Composer dependency.

Gateman distributes the standalone source file from:

```text
GET https://getgateman.com/client/php
```

Acquire it during setup or deployment:

```bash
curl --fail --silent --show-error https://getgateman.com/client/php -o gateman.php
```

Then include it from server-side PHP:

```php
require_once __DIR__ . '/gateman.php';
```

This is public PHP artifact acquisition. It is distinct from authenticated
POST /client/get, which returns application integration context.

## Configuration

The client reads process environment variables. It does not load .env files.

| Variable | Required | Meaning |
| --- | --- | --- |
| GM_BASE_URL | yes | Gateman URL using http or https |
| GM_APP | yes | Application identifier |
| GM_APP_SECRET | HMAC | Application secret |
| GM_PRIVATE_KEY | Ed25519 | Canonical Base64 raw 32-byte Ed25519 seed |
| APP_ENVIRONMENT | selected routes | production, staging, or development |

GM_PRIVATE_KEY takes precedence over GM_APP_SECRET. Invalid key material is a
local configuration error and never silently falls back to HMAC. The client
does not choose an application environment.

## Authenticated request

gm_request() sends one authenticated JSON POST and never retries. This example
uses POST /client/get, which requires the application scope client:read:

```php
require_once __DIR__ . '/gateman.php';
$result = gm_request('/client/get', []);
if ($result['error'] !== null) {
    throw new RuntimeException($result['error']['message']);
}
$status = $result['meta']['http_status'];
if (!is_int($status) || $status < 200 || $status >= 300) {
    throw new RuntimeException('Gateman returned HTTP ' . (string)$status);
}
$integrationContext = $result['body'];
```

The values above are application code, not a fabricated response. Capability
payloads remain defined by their individual Gateman contracts.

## Public API

gm_request(string $path, array $payload = [], ?string $idempotencyKey = null,
?string $credentialRequestId = null): array

The path must begin with /. Queries, fragments, controls, whitespace, Unicode,
backslashes, dot segments, and transport-normalizing paths are rejected.
Already percent-encoded transport-equivalent paths are accepted.

The payload is a PHP array. The optional idempotency key is trimmed around
spaces and tabs, omitted when empty, limited to 255 bytes, and signed. It does
not itself provide route-level idempotency.

For /secrets/get and /db/get, a fresh lowercase 32-hex-character
credential-request ID is generated when omitted. A supplied ID must have that
exact format.

gm_health(): array

Performs unauthenticated GET /health with a three-second timeout. Health does
not prove application authorization or provider configuration.

gm_client_get(): array

Performs unauthenticated GET /client/php and returns the public PHP source
artifact in body on a successful text/plain response. It does not call
authenticated POST /client/get.

## Environment handling

The client reads the application environment from `APP_ENVIRONMENT`. It accepts
`production`, `staging`, and `development`, and trims and lowercases the value
before use. Requests to routes that require an environment fail locally when
the variable is missing or invalid; the normalized value is injected into the
request payload automatically. See [Contract v1](https://docs.getgateman.com/contract-v1/)
for the authoritative route classification.

If the payload already contains `environment`, its trimmed, lowercased value
must match `APP_ENVIRONMENT`; a conflict is a local validation error. The
caller’s original PHP array is not intentionally mutated. Environment-
independent routes receive no automatic environment, and the client does not
select an implicit default.

## Result structure

Every received HTTP response, including non-2xx responses, is returned as:

```php
[
    'body' => mixed,
    'raw_body' => ?string,
    'headers' => array<string, list<string>>,
    'meta' => [
        'http_status' => ?int,
        'gateman_request_id' => ?string,
        'retry_after' => ?int,
    ],
    'error' => ?array{kind: string, message: string},
]
```

Headers use lowercase names and preserve duplicate values as lists. Valid JSON
is decoded into body. Non-JSON bodies remain in raw_body and set error.kind to
decode. Transport, configuration, validation, encoding, and decoding failures
are local errors; transport failures have no HTTP status.

Request IDs are exposed only when exactly 16 lowercase hexadecimal characters.
Valid integer Retry-After values appear in meta.

## Operational behavior

Authenticated requests use a one-second cURL connect timeout and ten-second
total timeout. Health uses a one-second connect timeout and a three-second
total timeout. Requests are synchronous and never retried automatically.

The shared signing and authentication rules are defined by [Contract v1](https://docs.getgateman.com/contract-v1/).
