bella-baxter / sdk
Official PHP SDK for the Bella Baxter secret management platform
Requires
Requires (Dev)
- phpunit/phpunit: ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v0.1.1-beta.126
- v0.1.1-beta.124
- v0.1.1-beta.123
- v0.1.1-beta.122
- v0.1.1-beta.121
- v0.1.1-beta.119
- v0.1.1-beta.118
- v0.1.1-beta.117
- v0.1.1-beta.116
- v0.1.1-beta.115
- v0.1.1-beta.114
- v0.1.1-beta.113
- v0.1.1-beta.112
- v0.1.1-beta.111
- v0.1.1-beta.110
- v0.1.1-beta.109
- v0.1.1-beta.108
- v0.1.1-beta.107
- v0.1.1-beta.106
- v0.1.1-beta.105
- v0.1.1-beta.104
- v0.1.1-beta.103
- v0.1.1-beta.102
- v0.1.1-beta.101
- v0.1.1-beta.100
- v0.1.1-beta.99
- v0.1.1-beta.98
- v0.1.1-beta.97
- v0.1.1-beta.96
- v0.1.1-beta.95
- v0.1.1-beta.94
- v0.1.1-beta.93
- v0.1.1-beta.92
- v0.1.1-beta.91
- v0.1.1-beta.90
- v0.1.1-beta.89
- v0.1.1-beta.88
- v0.1.1-beta.87
- v0.1.1-beta.86
- v0.1.1-beta.85
- v0.1.1-beta.84
- v0.1.1-beta.83
- v0.1.1-beta.82
- v0.1.1-beta.81
- v0.1.1-beta.80
- v0.1.1-beta.79
- v0.1.1-beta.78
- v0.1.1-beta.77
- v0.1.1-beta.76
- v0.1.1-beta.75
- v0.1.1-beta.74
- v0.1.1-beta.73
- v0.1.1-beta.72
- v0.1.1-beta.71
- v0.1.1-beta.70
- v0.1.1-beta.69
- v0.1.1-beta.68
- v0.1.1-beta.67
- v0.1.1-beta.66
- v0.1.1-beta.65
- v0.1.1-beta.64
- v0.1.1-beta.63
- v0.1.1-beta.62
- v0.1.1-beta.61
- v0.1.1-beta.60
- v0.1.1-beta.59
- v0.1.1-beta.58
- v0.1.1-beta.57
- v0.1.1-beta.56
- v0.1.1-beta.55
- v0.1.1-beta.54
- v0.1.1-beta.53
- v0.1.1-beta.52
- v0.1.1-beta.51
- v0.1.1-beta.50
- v0.1.1-beta.49
- v0.1.1-beta.48
- v0.1.1-beta.47
- v0.1.1-beta.46
- v0.1.1-beta.45
- v0.1.1-beta.44
- v0.1.1-beta.43
- v0.1.1-beta.42
- v0.1.1-beta.41
- v0.1.1-beta.40
- v0.1.1-beta.39
- v0.1.1-beta.38
- v0.1.1-beta.37
- v0.1.1-beta.36
- v0.1.1-beta.35
- v0.1.1-beta.34
- v0.1.1-beta.33
- v0.1.1-beta.32
- v0.1.1-beta.31
- v0.1.1-beta.30
- v0.1.1-beta.29
- v0.1.1-beta.28
- v0.1.1-beta.27
- v0.1.1-beta.26
- v0.1.1-beta.23
- v0.1.1-beta.22
This package is auto-updated.
Last update: 2026-10-05 13:20:32 UTC
README
Official PHP SDK for the Bella Baxter secret management platform.
Requirements
- PHP 8.1+
- Extensions:
ext-curl,ext-json,ext-openssl(all bundled by default)
Installation
composer require bella-baxter/sdk
Quick Start
use BellaBaxter\BaxterClient; use BellaBaxter\BaxterClientOptions; $client = new BaxterClient(new BaxterClientOptions( baxterUrl: 'https://baxter.example.com', apiKey: getenv('BELLA_BAXTER_API_KEY'), // bax-<keyId>-<secret>, from: bella apikeys create )); // Project and environment come from the API key itself (GET /api/v1/keys/me). $secrets = $client->getAllSecrets(); echo $secrets['DATABASE_URL'];
End-to-End Encryption (E2EE)
E2EE is always on — there is no option to enable or disable it. Every client installs
E2EGuzzleMiddleware, which:
- Uses your device key (
privateKey, orBELLA_BAXTER_PRIVATE_KEY, both frombella auth setup) or, when none is configured, a fresh ephemeral P-256 key pair - Sends its public key as the
X-E2E-Public-Keyheader on every read that carries secret values — the seven envelope-required reads of the SDK contract (all secrets, both exports, the provider list, one secret, one secret version, the global list), whether issued bygetAllSecrets()or the Kiota client — and on nothing else - The server encrypts the response using ECDH-P256 + HKDF-SHA256 + AES-256-GCM
- The SDK decrypts the response transparently and hands on the server's own JSON for that read
Secret values are never visible in plaintext — not in server logs, proxies, or network captures.
Fail closed (#1050). Once the key has been presented on a read that carries secret values, an answer
that is not a decryptable envelope is refused with BellaBaxter\E2EEResponseException, never returned:
getErrorCode() is e2ee-plaintext-response for plain secrets (a header-stripping proxy or a server
regression) and e2ee-decryption-failed for an envelope that is malformed, tampered or encrypted to
another key. There is no plaintext fallback.
use BellaBaxter\BaxterClient; use BellaBaxter\BaxterClientOptions; use BellaBaxter\E2EEResponseException; // ZKE: present a registered device key instead of an ephemeral one. $client = new BaxterClient(new BaxterClientOptions( apiKey: getenv('BELLA_BAXTER_API_KEY'), privateKey: getenv('BELLA_BAXTER_PRIVATE_KEY') ?: null, // PKCS#8 PEM; this env var is also read automatically onWrappedDekReceived: function (string $wrappedDek, ?string $leaseExpires): void { // ZKE key wrapping: persist the wrapped DEK for offline use }, )); try { $secrets = $client->getAllSecrets(); } catch (E2EEResponseException $e) { error_log('refused: ' . $e->getErrorCode()); // never the body or any key throw $e; }
API
getAllSecrets(): array<string,string>
Fetches all secrets for the project + environment the API key is scoped to.
$secrets = $client->getAllSecrets(); // ['DATABASE_URL' => 'postgres://...', 'API_KEY' => '...']
getSecretsVersion(): array
Lightweight change check — returns environmentSlug, version and lastModified, no values.
$version = $client->getSecretsVersion(); if ($version['version'] !== $lastSeenVersion) { $secrets = $client->getAllSecrets(); }
getKeyContext(): array
The project and environment the API key is scoped to (GET /api/v1/keys/me).
$ctx = $client->getKeyContext(); echo $ctx['projectSlug'] . '/' . $ctx['environmentSlug'];
getClient() and getRequestAdapter() — the full API
getClient() is the Kiota-generated client for every other endpoint; its requests go through the same
signing and E2EE middleware. Five of the seven value-carrying reads are declared as E2EEncryptedPayload in
the OpenAPI document, which is not the shape they decrypt to, so read those through getRequestAdapter()
as a raw body:
use Psr\Http\Message\StreamInterface; $info = $client->getClient()->api()->v1()->projects()->byId('my-app') ->environments()->byEnvSlug('production')->providers()->byProviderSlug('vault') ->secrets()->byKey('DATABASE_URL')->toGetRequestInformation(); $item = json_decode( (string) $client->getRequestAdapter()->sendPrimitiveAsync($info, StreamInterface::class)->wait(), true, ); echo $item['value'];
Configuration
BaxterClientOptions constructor arguments (use named arguments):
| Option | Type | Default | Description |
|---|---|---|---|
baxterUrl |
string |
https://api.bella-baxter.io |
Base URL of the Baxter API |
apiKey |
string |
— (required) | Bella Baxter API key, bax-<keyId>-<secret> |
timeoutSeconds |
int |
10 |
HTTP request timeout |
privateKey |
?string |
null |
PKCS#8 PEM device key (ZKE); falls back to BELLA_BAXTER_PRIVATE_KEY |
onWrappedDekReceived |
?callable |
null |
(string $wrappedDek, ?string $leaseExpires): void, called on X-Bella-Wrapped-Dek |
Laravel Integration
// config/services.php return [ 'bella' => [ 'url' => env('BELLA_BAXTER_URL', 'https://api.bella-baxter.io'), 'api_key' => env('BELLA_BAXTER_API_KEY'), ], ];
// AppServiceProvider::register() $this->app->singleton(BaxterClient::class, function () { return new BaxterClient(new BaxterClientOptions( baxterUrl: config('services.bella.url'), apiKey: config('services.bella.api_key'), )); });
Symfony Integration
# config/services.yaml BellaBaxter\BaxterClientOptions: arguments: $baxterUrl: '%env(BELLA_BAXTER_URL)%' $apiKey: '%env(BELLA_BAXTER_API_KEY)%' BellaBaxter\BaxterClient: arguments: $options: '@BellaBaxter\BaxterClientOptions'
Typed Secret Code Generation
bella secrets generate php fetches the secrets manifest (key names + type hints, no values) from the Bella API and generates a typed AppSecrets class. Each method calls getenv() at runtime — no secret values are ever embedded in the generated file.
bella secrets generate php \ --project my-app \ --environment production \ --output AppSecrets.php
Generated AppSecrets.php:
<?php // Auto-generated by bella secrets generate php — do not edit manually. class AppSecrets { public function getDatabaseUrl(): string { $v = getenv('DATABASE_URL'); if ($v === false) throw new \RuntimeException("Secret 'DATABASE_URL' is not set."); return $v; } public function getPort(): int { $v = getenv('PORT'); if ($v === false) throw new \RuntimeException("Secret 'PORT' is not set."); return (int) $v; } public function isEnableFeatureX(): bool { $v = getenv('ENABLE_FEATURE_X'); if ($v === false) throw new \RuntimeException("Secret 'ENABLE_FEATURE_X' is not set."); return filter_var($v, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) ?? false; } }
Usage alongside the SDK
// Secrets must be in the environment before accessing. // Use bella run, the SDK (BaxterClient), or a .env file loaded at bootstrap. $secrets = new AppSecrets(); $dbUrl = $secrets->getDatabaseUrl(); // string — throws if missing $port = $secrets->getPort(); // int — parsed automatically
Because each method reads getenv() on every call, values updated between requests (or via bella watch) are always current.
Options
| Option | Default | Description |
|---|---|---|
-p, --project <slug> |
.bella context |
Project slug |
-e, --environment <slug> |
.bella context |
Environment slug |
--provider <slug> |
default |
Provider slug |
-o, --output <path> |
AppSecrets.php |
Output file path |
--class-name <name> |
AppSecrets |
Class name |
--dry-run |
— | Print to stdout without writing |