PHP SDK
Read secrets from any PHP application using Ed25519 machine authentication, on persistent hosts or serverless and ephemeral environments.
Installation
composer require sikkerkey/sdk
Requires PHP 8.1+ with the sodium and curl extensions (both bundled with modern PHP). No external Composer dependencies.
Quick Start
use SikkerKey\SikkerKey;
$sk = SikkerKey::create('vault_abc123');
$apiKey = $sk->getSecret('sk_stripe_key');
The SDK loads the machine identity from ~/.sikkerkey/vaults/{vaultId}/identity.json, signs every request with the machine's Ed25519 private key, and returns the decrypted value.
Client Creation
// Explicit vault ID
$sk = SikkerKey::create('vault_abc123');
// Direct path to identity file
$sk = SikkerKey::create('/etc/sikkerkey/vaults/vault_abc123/identity.json');
// Auto-detect from SIKKERKEY_IDENTITY env or single vault on disk
$sk = SikkerKey::create();
Throws ConfigurationError if the identity is missing, the key can't be loaded, or multiple vaults exist without a specified vault ID.
Serverless (Memory-Only Bootstrap)
On a long-lived host the SDK loads a persistent identity from disk. Serverless and other ephemeral or read-only-filesystem environments (AWS Lambda, Google Cloud Run, Fly.io, and similar) have no identity to persist. SikkerKey::bootstrapInMemory() handles that case: it generates an Ed25519 keypair in memory, enrolls a short-lived ephemeral machine with an enrollment token, and returns a ready client, without writing anything to disk.
use SikkerKey\SikkerKey;
$sk = SikkerKey::bootstrapInMemory(
getenv('SIKKERKEY_VAULT_ID'),
getenv('SIKKERKEY_ENROLLMENT_TOKEN'),
);
$dbUrl = $sk->getSecret('sk_db_prod');
Create an enrollment token in the dashboard and supply its plaintext plus your vault ID. The token only registers an ephemeral machine scoped to the policy you set (projects, secrets, lifetime); it cannot read secrets on its own.
How It Works
bootstrapInMemory()enrolls once, when you call it: it generates a keypair in memory, registers an ephemeral machine, and returns a ready client.- The returned client signs every read with the in-memory private key, exactly like a disk-based client.
- Nothing is written to disk. The private key lives only in process memory and is gone when the process exits.
- The ephemeral machine lives for the lifetime set on the enrollment token. Reading after it expires throws
AuthenticationError, so set the token's machine lifetime to suit your workload. The common path is to read secrets at startup and hold the values.
Options
$sk = SikkerKey::bootstrapInMemory(
$vaultId,
$token,
hostname: 'worker-1', // defaults to $HOSTNAME, then "serverless"
name: 'batch-runner', // overridden if the token defines a name pattern
);
Provisioning the Token for Serverless
When you create the enrollment token for a serverless deployment:
- Set a short machine lifetime (minutes). Each cold start mints a fresh ephemeral machine, and short-lived ones free their slot quickly as they expire.
- Set max-uses high enough for your cold-start and concurrency volume.
- Leave the source-CIDR restriction unset, since serverless egress IPs are dynamic.
- If the vault has an IP allowlist, make sure it permits the platform's egress or leave it off. Enrollment enforces the allowlist.
- Set a name pattern on the token (for example
serverless-{uuid8}) so each cold-start machine gets a unique name. A name pattern takes precedence overname.{uuidN}inserts N random characters (4 to 32, default 8);{uuid}inserts 8.
Each live ephemeral machine counts against your plan's machine limit until it expires and is cleaned up. Requires outbound HTTPS.
Reading Secrets
Single Value
$apiKey = $sk->getSecret('sk_stripe_prod');
Structured (Multiple Fields)
$fields = $sk->getFields('sk_db_prod');
$host = $fields['host']; // "db.example.com"
$password = $fields['password']; // "hunter2"
Throws SecretStructureError if the secret value is not a JSON object.
Single Field
$password = $sk->getField('sk_db_prod', 'password');
Throws FieldNotFoundError if the field doesn't exist. The error message includes available field names.
Listing Secrets
// All secrets this machine can access
foreach ($sk->listSecrets() as $s) {
echo "{$s->id}: {$s->name}\n";
}
// Secrets in a specific project
$projectSecrets = $sk->listSecretsByProject('proj_production');
Returns SecretListItem[] with id, name, fieldNames (nullable), and projectId (nullable).
Export
// All secrets as a flat associative array
$env = $sk->export();
// ["API_KEY" => "sk-live-...", "DB_CREDS_HOST" => "db.example.com", "DB_CREDS_PASSWORD" => "s3cret"]
// Scoped to a project
$env = $sk->export('proj_production');
// Inject into the environment
foreach ($sk->export() as $key => $value) {
putenv("{$key}={$value}");
}
Structured secrets are flattened: SECRET_NAME_FIELD_NAME.
Multi-Vault
$prod = SikkerKey::create('vault_a1b2c3');
$staging = SikkerKey::create('vault_x9y8z7');
$prodKey = $prod->getSecret('sk_api_key');
$stagingKey = $staging->getSecret('sk_api_key');
List Registered Vaults
$vaults = SikkerKey::listVaults();
// ["vault_a1b2c3", "vault_x9y8z7"]
Static method.
Machine Info
$sk->machineId; // "550e8400-e29b-41d4-a716-446655440000"
$sk->machineName; // "api-server-1"
$sk->vaultId; // "vault_abc123"
$sk->apiUrl; // "https://api.sikkerkey.com"
Read-only properties (also available as machineId(), machineName(), vaultId(), apiUrl() methods).
Error Handling
use SikkerKey\NotFoundError;
use SikkerKey\AccessDeniedError;
use SikkerKey\AuthenticationError;
use SikkerKey\ApiError;
try {
$secret = $sk->getSecret('sk_nonexistent');
} catch (NotFoundError) {
// 404 - secret doesn't exist
} catch (AccessDeniedError) {
// 403 - machine not approved or no grant
} catch (AuthenticationError) {
// 401 - invalid signature or unknown machine
} catch (ApiError $e) {
// any other HTTP error
echo $e->httpStatus;
}
Exception Hierarchy
SikkerKeyError (extends Exception)
├── ConfigurationError - identity/key issues
├── SecretStructureError - secret is not a JSON object (getFields)
├── FieldNotFoundError - field not in structured secret (getField)
└── ApiError - HTTP error (has $httpStatus property)
├── AuthenticationError - 401
├── AccessDeniedError - 403
├── NotFoundError - 404
├── ConflictError - 409
├── RateLimitedError - 429
└── ServerSealedError - 503
Identity Resolution
- Explicit path - starts with
/or containsidentity.json - Vault ID - looks up
~/.sikkerkey/vaults/{vaultId}/identity.json SIKKERKEY_IDENTITYenv - path to identity file- Auto-detect - single vault on disk
The vault_ prefix is added automatically if not present. Override the base directory with SIKKERKEY_HOME.
Environment Variables
| Variable | Description |
|---|---|
SIKKERKEY_IDENTITY | Path to identity.json - overrides vault lookup |
SIKKERKEY_HOME | Base config directory (default: ~/.sikkerkey) |
Method Reference
| Method | Returns | Description |
|---|---|---|
SikkerKey::create(vaultOrPath?) | SikkerKey | Create client from disk identity (static) |
SikkerKey::bootstrapInMemory(vaultId, token, hostname?, name?) | SikkerKey | Memory-only serverless bootstrap (static) |
SikkerKey::listVaults() | string[] | List registered vault IDs (static) |
getSecret(secretId) | string | Read a secret value |
getFields(secretId) | array<string,string> | Read structured secret |
getField(secretId, field) | string | Read single field |
listSecrets() | SecretListItem[] | List all accessible secrets |
listSecretsByProject(projectId) | SecretListItem[] | List secrets in a project |
export(projectId?) | array<string,string> | Export as env map |
Retry Behavior
429 and 503 responses are retried up to 3 times with exponential backoff (1s, 2s, 4s). Each retry uses a fresh timestamp and nonce. Network errors are also retried.
Dependencies
None at the Composer level. Uses the bundled ext-sodium (Ed25519 key generation and signing) and ext-curl (HTTP). Requires PHP 8.1+. All HTTP requests have a 15-second timeout; HTTPS is enforced for non-localhost connections.