Python SDK

Read secrets from any Python application using Ed25519 machine authentication.

Installation

pip install sikkerkey

Requires Python 3.10+. Single dependency: cryptography (for Ed25519 signing).

Quick Start

from sikkerkey import SikkerKey

sk = SikkerKey("vault_abc123")
secret = sk.get_secret("sk_a1b2c3d4e5")

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("vault_abc123")

# Direct path to identity file
sk = SikkerKey("/etc/sikkerkey/vaults/vault_abc123/identity.json")

# Auto-detect from SIKKERKEY_IDENTITY env or single vault on disk
sk = SikkerKey()

Raises ConfigurationError if the identity is missing, the key can't be loaded, or multiple vaults exist without a specified vault ID.

Reading Secrets

Single Value

api_key = sk.get_secret("sk_stripe_prod")

Structured (Multiple Fields)

db = sk.get_fields("sk_db_prod")
host = db["host"]       # "db.example.com"
user = db["username"]   # "admin"
password = db["password"]  # "hunter2"

Raises SecretStructureError if the secret value is not a JSON object.

Single Field

password = sk.get_field("sk_db_prod", "password")

Raises FieldNotFoundError if the field doesn't exist. The error message includes available field names.

Listing Secrets

# All secrets this machine can access
secrets = sk.list_secrets()
for s in secrets:
    print(f"{s.id}: {s.name}")

# Secrets in a specific project
project_secrets = sk.list_secrets_by_project("proj_abc123")

Returns list[SecretListItem] with attributes id, name, field_names (optional), and project_id (optional).

Export

# All secrets as a flat dict
env = sk.export()
# {"API_KEY": "sk-live-...", "DB_CREDS_HOST": "db.example.com", "DB_CREDS_PASSWORD": "s3cret"}

# Scoped to a project
env = sk.export(project_id="proj_abc123")

# Inject into environment
import os
os.environ.update(sk.export())

Structured secrets are flattened: SECRET_NAME_FIELD_NAME.

Watching for Changes

Watch secrets for real-time updates. When a secret is rotated, updated, or deleted, the callback fires with the new value.

from sikkerkey import WatchStatus

def on_db_change(event):
    if event.status == WatchStatus.CHANGED:
        # event.value has the new value
        # event.fields has parsed key-value pairs for structured secrets
        db.configure_credentials(
            username=event.fields["username"],
            password=event.fields["password"],
        )
    elif event.status == WatchStatus.DELETED:
        print("Secret deleted")
    elif event.status == WatchStatus.ACCESS_DENIED:
        print("Access revoked")
    elif event.status == WatchStatus.ERROR:
        print(f"Error: {event.error}")

sk.watch("sk_db_credentials", on_db_change)

Polling starts automatically on the first watch() call and runs on a background daemon thread. Default interval is 15 seconds (server enforces a 10-second minimum).

sk.set_poll_interval(30)  # seconds
sk.unwatch("sk_db_credentials")  # stop watching one secret
sk.close()  # stop all watches

SikkerKey can be used as a context manager for automatic cleanup.

Multi-Vault

prod = SikkerKey("vault_a1b2c3d4e5f6g7h8")
staging = SikkerKey("vault_x9y8z7w6v5u4t3s2")

prod_db = prod.get_secret("sk_db_prod")
staging_db = staging.get_secret("sk_db_staging")

List Registered Vaults

vaults = SikkerKey.list_vaults()
# ["vault_a1b2c3d4e5f6g7h8", "vault_x9y8z7w6v5u4t3s2"]

Static method on the SikkerKey class.

Identity Resolution

  1. Explicit path - starts with / or contains identity.json
  2. Vault ID - looks up ~/.sikkerkey/vaults/{vaultId}/identity.json
  3. SIKKERKEY_IDENTITY env - path to identity file
  4. Auto-detect - single vault on disk

The vault_ prefix is added automatically if not present. Override base directory with SIKKERKEY_HOME.

Error Handling

from sikkerkey import SikkerKey, NotFoundError, AccessDeniedError, AuthenticationError

try:
    secret = sk.get_secret("sk_nonexistent")
except NotFoundError:
    # 404 - secret doesn't exist
except AccessDeniedError:
    # 403 - machine not approved or no grant
except AuthenticationError:
    # 401 - invalid signature or unknown machine
except RateLimitedError:
    # 429 - too many requests (retried automatically)
except ServerSealedError:
    # 503 - server needs unseal (retried automatically)
except ConfigurationError:
    # identity file missing, key not found
except ApiError as e:
    # any other HTTP error
    print(e.http_status)

Exception Hierarchy

SikkerKeyError
├── ConfigurationError      - identity/key issues
├── SecretStructureError    - secret is not a JSON object (get_fields)
├── FieldNotFoundError      - field not in structured secret (get_field)
└── ApiError                - HTTP error (has http_status: int)
    ├── AuthenticationError - 401
    ├── AccessDeniedError   - 403
    ├── NotFoundError       - 404
    ├── ConflictError       - 409
    ├── RateLimitedError    - 429
    └── ServerSealedError   - 503

Properties

PropertyTypeDescription
machine_idstrMachine UUID
machine_namestrHostname from bootstrap
vault_idstrVault this identity belongs to
api_urlstrSikkerKey API URL

Read-only @property decorators.

Method Reference

MethodReturnsDescription
SikkerKey(vault_or_path?)SikkerKeyCreate client
SikkerKey.list_vaults()list[str]List registered vault IDs (static)
get_secret(secret_id)strRead a secret value
get_fields(secret_id)dict[str, str]Read structured secret
get_field(secret_id, field)strRead single field
list_secrets()list[SecretListItem]List all accessible secrets
list_secrets_by_project(project_id)list[SecretListItem]List secrets in a project
export(project_id?)dict[str, str]Export as env map
watch(secret_id, callback)NoneWatch a secret for changes
unwatch(secret_id)NoneStop watching a secret
set_poll_interval(seconds)NoneSet poll interval (min 10s)
close()NoneStop all watches, shut down polling

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.

Environment Variables

VariableDescription
SIKKERKEY_IDENTITYPath to identity.json - overrides vault lookup
SIKKERKEY_HOMEBase config directory (default: ~/.sikkerkey)

Dependencies

DependencyVersionPurpose
cryptography>=41.0Ed25519 key loading and signing

All other functionality uses Python stdlib: urllib.request, json, hashlib, secrets. Requires Python 3.10+. All HTTP requests have a 15-second timeout.