Canonical page: https://docs.singlebase.io/sdk-php

# PHP SDK

A small client you can copy into your backend. This is a documented implementation, not a separately published package. It sends the same API envelope for every service through one `call` method.

PHP 8.0+ with HTTPS stream wrappers enabled (`allow_url_fopen`). No third-party package is required.

## Response contract

| Field | Meaning |
|---|---|
| `data` | Operation result; absent on API errors |
| `error` | API error object, or null/nil on success; `message` is the stable code |
| `meta` | Response metadata, including pagination when returned |
| `exec_time` | Gateway execution time in seconds, when returned |

API errors return in `Result.error`. Network, invalid JSON, and transport failures raise an exception or return a Go error. These small clients do not manage sessions, auto-refresh tokens, or retry requests. Pass a current user bearer token to the constructor/client only when acting as that user; omit it for trusted server/agent calls. Keep keys on the server.

## Client implementation

Save as `singlebase.php`. [Download the source](/examples/singlebase.php).

```php
<?php
// PHP 8.0+, with HTTPS support enabled for stream wrappers.
class Result {
    public mixed $data;
    public ?array $error;
    public array $meta;
    public ?float $exec_time;
    public function __construct(array $body) {
        $this->data = $body['data'] ?? null;
        $this->error = $body['error'] ?? null;
        $this->meta = $body['meta'] ?? [];
        $this->exec_time = isset($body['exec_time']) ? (float)$body['exec_time'] : null;
    }
}
class SinglebaseClient {
    public function __construct(private string $api_key, private string $api_url, private ?string $bearer = null) {}
    public function call(string $operation, array $payload, ?array $options = null): Result {
        $headers = ['Content-Type: application/json', 'X-API-KEY: ' . $this->api_key];
        if ($this->bearer !== null) $headers[] = 'Authorization: Bearer ' . $this->bearer;
        $body = ['operation' => $operation, 'payload' => (object)$payload];
        if ($options !== null) $body['options'] = (object)$options;
        $context = stream_context_create(['http' => [
            'method' => 'POST', 'header' => implode("\r\n", $headers),
            'content' => json_encode($body, JSON_THROW_ON_ERROR),
            'timeout' => 30, 'ignore_errors' => true, 'follow_location' => 0
        ]]);
        $raw = file_get_contents($this->api_url, false, $context);
        if ($raw === false) throw new RuntimeException('HTTP transport failed');
        $decoded = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
        if (!is_array($decoded)) throw new RuntimeException('Expected a JSON API envelope');
        $status = 0;
        foreach ($http_response_header ?? [] as $line) {
            if (preg_match('/^HTTP\/\S+\s+(\d{3})/', $line, $m)) $status = (int)$m[1];
        }
        if ($status >= 400 && !isset($decoded['error'])) throw new RuntimeException('HTTP ' . $status . ' without an API error envelope');
        return new Result($decoded);
    }
}
```

## Example

Set `SINGLEBASE_API_URL` to the complete console endpoint and `SINGLEBASE_API_KEY` to your server or appropriately scoped agent key. The `notes` collection must exist.

```php
<?php
require 'singlebase.php';
$sbc = new SinglebaseClient(getenv('SINGLEBASE_API_KEY'), getenv('SINGLEBASE_API_URL'));
$resp = $sbc->call('data.query', ['collection' => 'notes', 'limit' => 5], []);
if ($resp->error !== null) throw new RuntimeException($resp->error['message']);
var_dump($resp->data, $resp->meta, $resp->exec_time);
```

Use the same method with `llm.summarize` for generation or `chat.ask` for an answer, with the operation's verified payload. The `options` argument goes in the request envelope, separate from `payload`. An empty options object is valid; omit it when unused. See [API overview](/api.md) for request access and errors, and [API reference](/api-reference.md) for the operation groups.
