Keep API secrets out of plaintext config: layered .ranbval* env files, decrypt vault tokens only when used, optional usage telemetryโminimal deps, your own HTTP/SDK stack.
Project description
๐ Ranbval SDK v0.5.1
Your secrets, your rules. Stop committing plaintext API keys. Stop paying for other people's usage. Take back control.
pip install ranbval-sdk
๐ง Why Ranbval Exists
With so many LLM APIs and services in the market today, managing secrets has become a nightmare.
Someone shares an API key โ they share it with someone else โ suddenly the CEO is paying a massive unexpected bill โ and nobody knows which repo, which device, or which person burned the tokens.
That is exactly why we built Ranbval.
| Problem | Ranbval Solution |
|---|---|
| API keys committed to Git | Encrypted .ranbval* files โ plaintext never touches source control |
| Keys copied and shared freely | Repo allowlist โ if a repo is not authorized, the key cannot decrypt |
| No idea who used what, when | Live Monitor โ every usage logged with machine, repo, model, tokens |
| Surprise bills from leaked keys | Plan enforcement โ SDK checks subscription before every decrypt |
load_dotenv() scattered everywhere |
One call: load_ranbval() โ layered, mode-aware, zero side effects on import |
No more paying for other people's usage. Your secrets, your rules.
โก Quick Start
from ranbval_sdk import load_ranbval, safe_decrypt, emit_telemetry
import os
# 1. Load encrypted config from .ranbval files
load_ranbval()
# 2. Decrypt a vault token into a protected SecretString
secret = safe_decrypt(os.environ["MY_API_KEY"], os.environ["RANBVAL_VAULT_SECRET"])
# 3. Use it โ value is NEVER visible in logs or prints
import openai
client = openai.OpenAI(api_key=secret.use()) # โ only access point
# 4. Log usage to Live Monitor
emit_telemetry(vault_token_env="MY_API_KEY", model_used="gpt-4o", background=True)
๐ฆ What's Inside
| Module | What it does |
|---|---|
load_ranbval() |
Merges layered .ranbval* files into os.environ |
safe_decrypt() |
Decrypts vault token โ returns SecretString (never printable) |
SecretString |
Wrapper that blocks all display paths โ value only via .use() |
assert_plan_active() |
Raises BillingError if subscription/trial is not valid |
fetch_billing_status() |
Inspect plan, limits, trial state for a vault session |
plan_limits() |
Get request/secret limits for the active plan |
emit_telemetry() |
POST usage log to Ranbval Live Monitor |
secure_client() |
Wrap a third-party SDK class for auto-decrypt + telemetry |
๐๏ธ Function Reference
load_ranbval()
Loads configuration from .ranbval* files into os.environ. No network calls. No decryption. Zero side effects on import.
from ranbval_sdk import load_ranbval
load_ranbval() # auto-discover from cwd upward
load_ranbval(mode="production") # force a specific mode
load_ranbval(start="/path/to/project") # start search from a custom directory
load_ranbval("/absolute/path/to/file") # single file, skip layer discovery
load_ranbval(override=True) # file values overwrite existing os.environ
How it finds files
Walks from cwd upward until it finds a directory containing .ranbval or any .ranbval.* file. That becomes the config root.
Merge order (later file wins for duplicate keys):
.ranbval โ shared base
.ranbval.{mode} โ e.g. .ranbval.production
.ranbval.local โ machine-only, add to .gitignore
.ranbval.{mode}.local โ highest priority
Mode resolution order:
load_ranbval(mode="...")explicit argumentRANBVAL_ENVenvironment variableENVIRONMENTenvironment variableENVenvironment variable- Default:
development
Returns: True if at least one file was read, False if none found.
Example .ranbval file:
# Plain values
APP_NAME=my-app
DATABASE_URL=postgresql://localhost/mydb
# Encrypted vault token (generated in the Ranbval dashboard)
OPENAI_API_KEY=ranbval.4ii0a022aa.p1GOZ...ahsan
safe_decrypt()
Decrypts a ranbval.* vault token using AES-256-GCM with PBKDF2 key derivation.
Before decrypting it automatically:
- โ Checks repo allowlist โ is this Git repo allowed to use this key?
- โ Checks billing/plan โ does the vault owner have an active subscription?
from ranbval_sdk import load_ranbval, safe_decrypt
import os
load_ranbval()
secret = safe_decrypt(
os.environ["OPENAI_API_KEY"], # the ranbval.* token
os.environ["RANBVAL_VAULT_SECRET"], # your vault password
)
Returns: a SecretString โ the decrypted value is never accessible via print, str, repr, f-strings, or logs.
print(secret) # โ [ranbval:secret]
str(secret) # โ [ranbval:secret]
f"key={secret}" # โ key=[ranbval:secret]
repr(secret) # โ SecretString(***)
len(secret) # โ 164 (safe โ reveals only length)
# โ
Only correct usage:
client = openai.OpenAI(api_key=secret.use())
headers = {"Authorization": f"Bearer {secret.use()}"}
Raises:
BillingErrorโ no active subscription or trial expiredPermissionErrorโ this Git repo is not in the allowed listValueErrorโ wrong vault secret or corrupted token
Bypass flags (dev/CI only):
RANBVAL_SKIP_REPO_CHECK=1 # skip git remote allowlist check
RANBVAL_SKIP_BILLING_CHECK=1 # skip subscription/plan check
SecretString
A string wrapper that makes it impossible to accidentally expose a secret through print, logging, f-strings, or repr.
from ranbval_sdk import SecretString
# Created automatically by safe_decrypt() โ but you can also wrap your own values:
secret = SecretString("sk-proj-super-secret-key", label="openai")
print(secret) # [ranbval:secret]
repr(secret) # SecretString(***)
f"key={secret}" # key=[ranbval:secret]
str(secret) # [ranbval:secret]
len(secret) # 26 โ safe
# โ
Only way to get the real value:
real_value = secret.use()
Why this matters:
# โ Old way โ key leaks in logs/stdout
api_key = os.environ["OPENAI_KEY"] # plain string
print(f"Using key: {api_key}") # ๐ key printed to console/logs
# โ
Ranbval way โ impossible to leak accidentally
secret = safe_decrypt(token, vault_secret)
print(f"Using key: {secret}") # โ Using key: [ranbval:secret]
Properties:
| Method/Property | Description |
|---|---|
.use() |
Returns the raw string โ only access point |
len(secret) |
Length of the secret (safe) |
.label |
Optional name set at creation |
== |
Compares two SecretStrings by value (secure) |
assert_plan_active()
Checks whether the vault owner has an active subscription or valid trial. Raises BillingError if not.
Called automatically inside safe_decrypt() โ but you can also call it manually at app startup to fail fast.
from ranbval_sdk import assert_plan_active, BillingError
salt = "4ii0a022aa" # from ranbval.<salt>.<blob>.<label>
try:
info = assert_plan_active(salt)
print(f"Plan: {info['plan_key']} ({info['plan_name']})")
except BillingError as e:
print(f"Access denied: {e}")
# โ Ranbval: your free trial has ended.
# Subscribe at https://www.ranbval.com to continue.
What triggers a BillingError:
vault_locked = True(trial expired, no active subscription)- No active subscription AND trial not running
- Trial expired
Bypass for local dev:
RANBVAL_SKIP_BILLING_CHECK=1
fetch_billing_status()
Fetch full billing and plan information for a vault session โ no auth token required, only the client_salt.
from ranbval_sdk import fetch_billing_status
info = fetch_billing_status("4ii0a022aa")
print(info["plan_key"]) # "growth"
print(info["plan_name"]) # "Growth"
print(info["subscription_status"]) # "active"
print(info["has_active_subscription"]) # True
print(info["trial_active"]) # False
print(info["trial_expired"]) # False
print(info["vault_locked"]) # False
print(info["request_limit_month"]) # 100000
print(info["secrets_limit"]) # 50
Response fields:
| Field | Type | Description |
|---|---|---|
plan_key |
str|None |
starter / growth / pro / enterprise |
plan_name |
str|None |
Human-readable plan name |
subscription_status |
str|None |
Stripe status: active, trialing, past_due, canceled |
has_active_subscription |
bool |
True if subscription is active |
trial_active |
bool |
True if free trial is currently running |
trial_expired |
bool |
True if trial ended with no subscription |
trial_ends_at |
str|None |
ISO datetime when trial ends |
vault_locked |
bool |
True = no access at all |
request_limit_month |
int|None |
Max API requests per month for this plan |
secrets_limit |
int|None |
Max secrets allowed for this plan |
Raises:
BillingErrorโ session not found (wrong salt or no matching project)OSErrorโ network / TLS failure reaching the API
plan_limits()
Lightweight shortcut โ returns just the plan limits. Never raises (returns {} on any error).
from ranbval_sdk import plan_limits
limits = plan_limits("4ii0a022aa")
# {
# "plan_key": "growth",
# "plan_name": "Growth",
# "request_limit_month": 100000,
# "secrets_limit": 50
# }
Use this when you want to enforce limits in your own code without crashing on billing errors:
limits = plan_limits(salt)
if limits.get("request_limit_month") and usage > limits["request_limit_month"]:
raise Exception("Monthly request limit reached โ upgrade your Ranbval plan.")
emit_telemetry()
Posts a usage event to the Ranbval Live Monitor. Call it after your API request so the dashboard tracks every usage against the correct vault credential.
from ranbval_sdk import emit_telemetry
emit_telemetry(
vault_token_env="OPENAI_API_KEY", # env var holding a ranbval.* token
model_used="gpt-4o", # label shown in the dashboard
prompt_tokens=512,
completion_tokens=128,
event_kind="llm.chat",
background=True, # non-blocking (runs in a daemon thread)
)
Or pass the salt directly:
emit_telemetry(
client_salt="4ii0a022aa",
model_used="my-service.v1",
background=True,
)
Parameters:
| Parameter | Type | Description |
|---|---|---|
vault_token_env |
str |
Env var name holding a ranbval.* token โ salt extracted automatically |
client_salt |
str |
Use instead of vault_token_env if you have the salt directly |
model_used |
str |
Label for the dashboard (e.g. "gpt-4o", "stripe.charge") |
prompt_tokens |
int |
Input tokens (0 if not an LLM call) |
completion_tokens |
int |
Output tokens (0 if not an LLM call) |
event_kind |
str |
Event type (e.g. "llm.chat", "custom.request") |
background |
bool |
True = fire-and-forget (daemon thread). False = blocking |
host_url |
str |
Override RANBVAL_HOST for this call |
Environment variables:
| Variable | Purpose |
|---|---|
RANBVAL_HOST |
Password-manager base URL (default: https://api.ranbval.com) |
RANBVAL_TELEMETRY |
0 / false / off โ disable all telemetry POSTs |
RANBVAL_TELEMETRY_DEBUG |
1 / true โ print telemetry failures to stderr |
If no
client_saltcan be resolved, this is a no-op (silent). Safe to call even with plain (non-ranbval) keys.
secure_client() / build_secure_client()
Wrap a third-party SDK class so it auto-decrypts and auto-logs telemetry on every call.
from ranbval_sdk import load_ranbval, secure_client
import openai
load_ranbval()
# Returns an openai.OpenAI instance with auto-decrypt + telemetry
client = secure_client(
openai.OpenAI,
env_var="OPENAI_API_KEY", # env var with the ranbval.* token
key_kwarg="api_key", # constructor kwarg for the key
method_path_to_patch="chat.completions.create", # method to wrap for telemetry
)
# Use exactly like openai.OpenAI โ telemetry fires automatically
response = client.chat.completions.create(model="gpt-4o", messages=[...])
build_secure_client() returns a subclass instead of an instance โ use when you need to instantiate multiple times:
from ranbval_sdk import build_secure_client
import anthropic
SecureAnthropic = build_secure_client(
anthropic.Anthropic,
env_var="ANTHROPIC_API_KEY",
key_kwarg="api_key",
)
client = SecureAnthropic()
๐ Security Architecture
Your Code
โ
โโโ load_ranbval() Reads .ranbval* files โ os.environ (no network, no decrypt)
โ
โโโ safe_decrypt(token, secret)
โ โ
โ โโโ 1. Repo allowlist check โ GET /api/public/repo-policy?client_salt=...
โ โโโ 2. Billing/plan check โ GET /api/public/billing-status?client_salt=...
โ โโโ 3. AES-256-GCM decrypt โ SecretString (value sealed, never printable)
โ
โโโ secret.use() Only access point โ pass directly to SDK/headers
โ
โโโ emit_telemetry() POST /api/telemetry โ Ranbval Live Monitor
Token format: ranbval.<client_salt>.<aes-gcm-blob>.<label>
client_saltโ 10-char noise, used for session lookup and telemetry attributionaes-gcm-blobโ IV + ciphertext, base64url encodedlabelโ human-readable tag (ahsan,openai, etc.)
๐ Environment Variables Reference
| Variable | Default | Description |
|---|---|---|
RANBVAL_HOST |
https://api.ranbval.com |
Password-manager base URL (no /api suffix) |
RANBVAL_ENV |
development |
Active mode for layered config |
RANBVAL_VAULT_SECRET |
(required) | Vault password for safe_decrypt() |
RANBVAL_SKIP_REPO_CHECK |
0 |
1 = skip Git remote allowlist check |
RANBVAL_SKIP_BILLING_CHECK |
0 |
1 = skip subscription/plan check |
RANBVAL_TELEMETRY |
on |
0 / false = disable telemetry POSTs |
RANBVAL_TELEMETRY_DEBUG |
0 |
1 = print telemetry errors to stderr |
๐งช Test with curl
Check session exists:
curl "https://api.ranbval.com/api/public/repo-policy?client_salt=YOUR_SALT"
Check billing/plan:
curl "https://api.ranbval.com/api/public/billing-status?client_salt=YOUR_SALT"
Send a telemetry event:
curl -X POST "https://api.ranbval.com/api/telemetry" \
-H "Content-Type: application/json" \
-d '{
"client_salt": "YOUR_SALT",
"machine_name": "curl-test",
"repo_path": "/my/project",
"model_used": "curl.test",
"prompt_tokens": 0,
"completion_tokens": 0,
"security": { "event_kind": "custom.request", "transport": "https" }
}'
Test decryption (Python):
RANBVAL_SKIP_REPO_CHECK=1 \
RANBVAL_SKIP_BILLING_CHECK=1 \
RANBVAL_VAULT_SECRET="your_vault_secret" \
python -c "
import os
from ranbval_sdk import safe_decrypt
secret = safe_decrypt(os.environ['RANBVAL_TOKEN'], os.environ['RANBVAL_VAULT_SECRET'])
print(f'OK โ {len(secret)} chars, value hidden: {secret}')
"
๐ n8n โ HTTP Request + Telemetry
No Python needed. Chain two HTTP Request nodes:
Node 1 โ Your API call (OpenAI, Stripe, etc.) via HTTPS.
Node 2 โ Telemetry log to Ranbval:
POST https://api.ranbval.com/api/telemetry
Content-Type: application/json
{
"client_salt": "{{ $json.client_salt }}",
"machine_name": "n8n",
"repo_path": "{{ $workflow.name }}",
"model_used": "openai.chat",
"prompt_tokens": 0,
"completion_tokens": 0,
"security": {
"event_kind": "custom.request",
"transport": "https",
"client_platform": "n8n"
}
}
Extract client_salt from a ranbval.* token in a Code node:
const token = $json.apiKey;
const salt = token.startsWith("ranbval.") ? token.split(".")[1] : null;
return [{ json: { client_salt: salt } }];
๐ Plans
| Plan | Price | Requests/mo | Secrets |
|---|---|---|---|
| Starter | $15/mo | 10,000 | 10 |
| Growth โญ | $49/mo | 100,000 | 50 |
| Pro | $129/mo | 500,000 | Unlimited |
| Enterprise | $499+/mo | Unlimited | Unlimited |
Free trial: 1 day ยท 1 project ยท 5 secrets ยท Full Growth features
๐ File Layout Example
my-project/
โโโ .ranbval โ shared defaults (commit this)
โโโ .ranbval.production โ production overrides (commit this)
โโโ .ranbval.local โ machine secrets (gitignore this!)
โโโ .ranbval.production.local โ prod + local (gitignore this!)
โโโ src/
โโโ main.py
.gitignore:
.ranbval.local
.ranbval.*.local
๐ค Investors & Partnerships
Ranbval is looking for CEOs and investors who want to back the next generation of API secret governance.
If you are interested in backing an idea that solves a real pain for every engineering team using LLMs and third-party APIs โ reach out through ranbval.com.
PyPI: ranbval-sdk ยท Docs: api.ranbval.com/docs ยท Dashboard: ranbval.com
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ranbval_sdk-0.9.0.tar.gz.
File metadata
- Download URL: ranbval_sdk-0.9.0.tar.gz
- Upload date:
- Size: 25.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.12.0 Darwin/25.4.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
291729542d42888e43799899d7708cc5ebecada93d19fd013a5b9b702108a1a4
|
|
| MD5 |
062365009d59972cb1a4f45ddc589042
|
|
| BLAKE2b-256 |
5f37a0d703495b026d896b0c8c047dc380b238a4b7863f0e64ed2dd33939fa4e
|
File details
Details for the file ranbval_sdk-0.9.0-cp312-cp312-macosx_26_0_arm64.whl.
File metadata
- Download URL: ranbval_sdk-0.9.0-cp312-cp312-macosx_26_0_arm64.whl
- Upload date:
- Size: 25.3 kB
- Tags: CPython 3.12, macOS 26.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.3.2 CPython/3.12.0 Darwin/25.4.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c12303ad8abaf09b0e00b875ec77a9d9604365cbe0a073532e44b2bd04956f7
|
|
| MD5 |
718e4ca53e60f4976a0a450de34e6ab3
|
|
| BLAKE2b-256 |
b1b3febf2372407b1730842da1e7727b76d94386c0ddd1b5cef174cd806ded9f
|