Skip to main content

Keep API secrets out of plaintext config: layered .ranbval* env files, decrypt vault tokens only when used, optional plan/subscription enforcement, 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

PyPI Python License: MIT


๐Ÿง  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:

  1. load_ranbval(mode="...") explicit argument
  2. RANBVAL_ENV environment variable
  3. ENVIRONMENT environment variable
  4. ENV environment variable
  5. 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:

  1. โœ… Checks repo allowlist โ€” is this Git repo allowed to use this key?
  2. โœ… 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 expired
  • PermissionError โ€” this Git repo is not in the allowed list
  • ValueError โ€” 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_salt can 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 attribution
  • aes-gcm-blob โ€” IV + ciphertext, base64url encoded
  • label โ€” 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

โ†’ Subscribe at ranbval.com


๐Ÿ“ 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ranbval_sdk-0.7.0.tar.gz (27.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ranbval_sdk-0.7.0-cp312-cp312-macosx_26_0_arm64.whl (27.7 kB view details)

Uploaded CPython 3.12macOS 26.0+ ARM64

File details

Details for the file ranbval_sdk-0.7.0.tar.gz.

File metadata

  • Download URL: ranbval_sdk-0.7.0.tar.gz
  • Upload date:
  • Size: 27.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.12.0 Darwin/25.4.0

File hashes

Hashes for ranbval_sdk-0.7.0.tar.gz
Algorithm Hash digest
SHA256 3517df015dbf7bc6fe37ad02bdb2a15d6a268a58e764fb530a9f71f16e0b7684
MD5 db7761d802e3e3d7ec88bd7a7f1113a0
BLAKE2b-256 1391c08409315197743f24cb44ca1802074a45f1fa3444fb27e3834efd648c6e

See more details on using hashes here.

File details

Details for the file ranbval_sdk-0.7.0-cp312-cp312-macosx_26_0_arm64.whl.

File metadata

File hashes

Hashes for ranbval_sdk-0.7.0-cp312-cp312-macosx_26_0_arm64.whl
Algorithm Hash digest
SHA256 c2dc9df3a7d6dba4771f567a0d14a51da65614f69f5bb9a6c3f7d33d305bb4af
MD5 fcf9ec4b885fe449bc86fc6891cab84d
BLAKE2b-256 8685b37bf2f1a6b4bb126fdfd35e89f3a112d0f4e515443c39a99cf6dcb5f0bc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page