Skip to main content

zephr

Zero-knowledge one-time secret sharing for AI agents and the humans who orchestrate them. Python SDK for Zephr.

Create a one-time secret link: encrypted on your device and self-destructing after a single retrieval. Pass it between agents, services, and pipelines with no shared infrastructure and no plaintext on the server.

Designed for zero-knowledge secret handoff between independent systems: AI agents, CI/CD pipelines, GitHub Actions, and human operators.

How it works

  1. A 256-bit key is generated locally. It never reaches Zephr's servers.
  2. Your secret is encrypted with AES-GCM on your device
  3. Only the ciphertext is uploaded to Zephr
  4. The link embeds the key in the URL fragment, which browsers never transmit to servers
  5. First retrieval atomically consumes the record. A second request returns 410.

Features

  • No shared infrastructure: a link is the entire transport mechanism between independent processes
  • Zero-knowledge: the server never receives your plaintext or encryption keys
  • Local encryption: AES-GCM-256 on your device before any network call
  • One-time access: record marked consumed atomically on first retrieval
  • Minimal dependencies: only cryptography (audited, widely trusted)
  • API key support for higher limits and longer expiry
  • Webhook callbacks: HMAC-SHA256 signed events on secret consumption (callback_url + callback_secret)
  • Idempotency: auto-generated Idempotency-Key on every create for safe retries

Migrating to 0.5.0

Breaking change: create_secret() and retrieve_secret() now return frozen dataclasses instead of dicts.

# Before (0.4.x)
result = zephr.create_secret("secret")
print(result["full_link"])

# After (0.5.0)
result = zephr.create_secret("secret")
print(result.full_link)  # IDE autocompletion, type safety, immutability

The migration is mechanical: replace result["key"] with result.key for all return value access.

Installation

pip install zephr

Usage

Create a secret

import zephr

result = zephr.create_secret("my-api-key-12345")
print(result.full_link)
# https://zephr.io/secret/abc123#v1.key...

The secret is encrypted exactly as provided. No trimming or normalization. Strings that are empty or contain only whitespace raise ValidationError.

Agent A encrypts and hands off the link. Agent B retrieves it exactly once:

# Agent A: encrypt and dispatch
result = zephr.create_secret("sk-live-abc123", expiry=60, hint="STRIPE_KEY_PROD")
agent_b.dispatch({"credential": result.full_link})

# Agent B: consumed atomically on first read
result = zephr.retrieve_secret(result.full_link)
plaintext = result.plaintext

Retrieve a secret

import zephr

# Full URL string
result = zephr.retrieve_secret("https://zephr.io/secret/abc123#v1.key...")
plaintext = result.plaintext

# Split mode
result = zephr.retrieve_secret({"url": "https://zephr.io/secret/abc123", "key": "v1.key..."})
plaintext = result.plaintext

Retrieval is exactly-once. The server permanently destroys the record on first access.

Options

# Expire in 1 hour
result = zephr.create_secret("secret", expiry=60)

# Expire in 7 days
result = zephr.create_secret("secret", expiry=10080)

# Expire in 30 days (requires an api_key)
result = zephr.create_secret("secret", expiry=43200, api_key="zeph_...")

# Attach a plaintext label for routing and audit logs
result = zephr.create_secret("secret", hint="STRIPE_KEY_PROD")

# Split URL and key for separate transmission
result = zephr.create_secret("secret", split=True)
print(result.url)   # https://zephr.io/secret/abc123
print(result.key)   # v1.key...

Return value

create_secret() returns a frozen SecretLink dataclass.

Standard mode:

result = zephr.create_secret("secret")
result.mode        # "standard"
result.full_link   # "https://zephr.io/secret/abc123#v1.key..."
result.expires_at  # "2026-03-12T12:00:00.000Z"
result.secret_id   # "abc123..." (22-char base64url ID)
result.url         # None (standard mode)
result.key         # None (standard mode)

Split mode:

result = zephr.create_secret("secret", split=True)
result.mode        # "split"
result.url         # "https://zephr.io/secret/abc123"
result.key         # "v1.key..."
result.expires_at  # "2026-03-12T12:00:00.000Z"
result.secret_id   # "abc123..." (22-char base64url ID)
result.full_link   # None (split mode)

retrieve_secret() returns a frozen RetrievalResult dataclass with plaintext (str), hint (str or None), and purge_at (int or None) — a Unix timestamp in seconds.

Webhook callback

Get notified when a secret is consumed — no polling needed:

result = zephr.create_secret("db-password",
    expiry=60,
    hint="DB_PASSWORD_PROD",
    callback_url="https://my-orchestrator.example.com/zephr-events",
    callback_secret="my-hmac-signing-secret",
    api_key=os.environ.get("ZEPHR_API_KEY"),
)

When the secret is retrieved, Zephr POSTs a signed event:

{
  "event":       "secret.consumed",
  "event_id":    "550e8400-e29b-41d4-a716-446655440000",
  "secret_id":   "Ht7kR2mNqP3wXvYz8aB4cD",
  "occurred_at": "2026-03-22T14:32:00.000Z",
  "hint":        "DB_PASSWORD_PROD"
}

Verify the X-Zephr-Signature header — HMAC-SHA256 hex digest of the raw JSON body, signed with your callback_secret. Use timing-safe comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js). See examples/webhook-receiver for runnable Node.js and Python receivers.

Fire-and-forget in v1 — no retries. 5-second timeout. Redirects blocked.

Idempotency

The SDK auto-generates an Idempotency-Key header on every create. If a request times out at the infrastructure level and is replayed, the server returns the cached response without creating a duplicate secret. Cache TTL: 24 hours.

Authentication

The SDK works without an account. No setup required. Higher quotas and expiries beyond 1 hour require a free account. Pass it via the api_key parameter. Anonymous requests are capped at 3/day per IP with a 1 h max expiry.

Tier Create limit Expiry options Max size Authentication
Anonymous 3/day 5m, 15m, 30m, 1h 6 KB None
Free 500/month 5m, 15m, 30m, 1h, 24h, 7d, 30d 20 KB api_key="zeph_..."

Getting an API key: Log in at zephr.io/account, open the API Keys tab, and create a key. The raw key is shown exactly once. Copy it immediately.

Passing the key:

import os
import zephr

# Via parameter
result = zephr.create_secret("secret", api_key="zeph_...")

# Via environment variable: preferred for CI and scripts
# export ZEPHR_API_KEY=zeph_...
result = zephr.create_secret("secret", api_key=os.environ.get("ZEPHR_API_KEY"))

GitHub Actions: Add ZEPHR_API_KEY as a repository secret, then pass it to your script:

steps:
  - run: python share_secret.py
    env:
      ZEPHR_API_KEY: ${{ secrets.ZEPHR_API_KEY }}
# share_secret.py
import os, zephr

result = zephr.create_secret(
    os.environ["MY_SECRET"],
    expiry=60,
    api_key=os.environ.get("ZEPHR_API_KEY"),
)
print(result.full_link)

The key is sent as Authorization: Bearer zeph_... on each request. An invalid or revoked key returns HTTP 401.

Error handling

import zephr

try:
    result = zephr.create_secret("my secret")
except zephr.ValidationError:
    # Invalid input: empty or whitespace-only string, too long, bad expiry
    pass
except zephr.EncryptionError:
    # AES-GCM encryption or decryption failed
    pass
except zephr.ApiError as e:
    # Server returned an error
    print(e.status_code)  # e.g. 429, 401, 403
    print(e.code)         # e.g. "MONTHLY_LIMIT_EXCEEDED"
except zephr.NetworkError:
    # Connection failed or timed out
    pass

Common ApiError codes:

Code Status Meaning
INVALID_API_KEY 401 Key not found or revoked
UPGRADE_REQUIRED 403 Not permitted for the caller's tier (e.g. any expiry other than 60 min without an account, or a webhook callback_url without an account)
ANON_RATE_LIMIT_EXCEEDED 429 Anonymous daily limit reached (3/day per IP)
MONTHLY_LIMIT_EXCEEDED 429 Monthly create limit reached for this API key
PAYLOAD_TOO_LARGE 413 Encrypted blob exceeds the tier blob ceiling
SECRET_NOT_FOUND 404 Secret ID does not exist or has expired
SECRET_ALREADY_CONSUMED 410 Secret was already retrieved
SECRET_EXPIRED 410 Secret has passed its expiry time

Security

  • Encrypts on your device before any network call
  • AES-GCM-256 with authenticated encryption and built-in tamper detection
  • Keys never reach the server. They travel in the URL fragment (RFC 3986 §3.5), which browsers strip before sending requests.
  • Sensitive buffers overwritten after use (best-effort in Python)
  • No plaintext logging. No analytics in the SDK.

Requirements

  • Python 3.10 or higher
  • cryptography >= 43.0

License

MIT

Metadata

Release files for zephr 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zephr 0.5.0
File Size Uploaded
zephr-0.5.0.tar.gz 23.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for zephr 0.5.0
File Interpreter ABI Platform
zephr-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.4 kB

Release files / zephr-0.5.0.tar.gz

Download URL zephr-0.5.0.tar.gz
Size 23.5 kB
Tags Source
SHA-256 checksum
How to use checksums
bfaf1d65a3f2aefaef096acb6d0433daaa341e4d2519bf5ab44894890722ae93
BLAKE2b-256 checksum
How to use checksums
1c63f5ae5666ccc4362890c59766d8491199473c577c212ff64ceb51f01fab69
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.

Transparency log

Release files / zephr-0.5.0-py3-none-any.whl

Download URL zephr-0.5.0-py3-none-any.whl
Size 20.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
99375497caff62041f1455156ad8f4fe4c86b1be610ecd92aa5dd7636d391163
BLAKE2b-256 checksum
How to use checksums
d7f9f16be2af301520e061851736462bb413284bee9ae90dd0da4b93a01f7231
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page