A small JSON API for guarding secrets, files, tools and actions with plain-English rules. Your code asks Mellon; Jev adjudicates; your code acts on a verdict it can inspect.
Base URLhttp://localhost:3000
Overview
Everything is JSON over HTTP. A lock holds a payload (a secret, a URL, a pointer to a file) behind a ruleset written in plain English. Anyone can make an attempt by sending a statement. Mellon asks Jev three typed questions about it, applies the lock's thresholds in code, and returns the payload only on a grant.
Call Mellon from your server, not the browser. If a verdict unlocks something in your system, the request to Mellon must come from code you control, so the user can't forge the answer.
Authentication
Mellon accounts are themselves Mellons. Instead of a password, you register identity rules: a plain-English description of how you will prove who you are. To sign in, you send a statement; Jev adjudicates it against your rules with stricter thresholds than a normal lock (confidence ≥ 0.7, compliance ≥ 0.8, manipulation ≤ 0.2). A grant returns a session token.
Credential
How to send it
Grants
None
—
Create anonymous locks, make attempts, read public lock info
Session token mln_…
Authorization: Bearer mln_…
Link new locks to your account, list them, read logs and delete without owner tokens. Valid for 7 days; revoke with DELETE /api/sessions.
Owner token
X-Owner-Token: …
Manage one lock (log, delete). Returned once when the lock is created.
A malformed, expired or revoked bearer token is rejected with 401 rather than silently treated as anonymous, so a lock is never created unlinked by accident.
Quickstart
Create an account, provision a lock linked to it, then make an attempt.
export MELLON=http://localhost:3000
# 1. Create an account. The response includes a session token.
TOKEN=$(curl -s -X POST "$MELLON/api/accounts" -H 'Content-Type: application/json' \
-d '{"username":"dana","rules":"The requester must say the passphrase osprey lantern and name the harbor town of Kettleby."}' \
| jq -r .token)
# Later, sign in again by proving it is you:
# curl -s -X POST "$MELLON/api/sessions" -H 'Content-Type: application/json' \
# -d '{"username":"dana","statement":"Passphrase osprey lantern. I grew up in Kettleby."}'
# 2. Provision a lock linked to your account.
LOCK=$(curl -s -X POST "$MELLON/api/locks" -H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"On-call DB password","rules":"Only the current on-call engineer, during an active incident, with an incident ID.","secret":"hunter2"}' \
| jq -r .lock.id)
# 3. Make an attempt. "secret" is present only when granted.
curl -s -X POST "$MELLON/api/locks/$LOCK/attempt" -H 'Content-Type: application/json' \
-d '{"input":"I am Sam, on call this week, handling incident INC-2291 (primary DB failover)."}' | jq
# 4. Read the attempt log with your session.
curl -s "$MELLON/api/locks/$LOCK/attempts" -H "Authorization: Bearer $TOKEN" | jq
// Node 18+ or any modern runtime with fetch.
const MELLON = process.env.MELLON ?? 'http://localhost:3000';
const post = (path, body, token) => fetch(`${MELLON}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(token && { Authorization: `Bearer ${token}` }) },
body: JSON.stringify(body),
}).then((r) => r.json());
// 1. Create an account (or POST /api/sessions to sign in later).
const { token } = await post('/api/accounts', {
username: 'dana',
rules: 'The requester must say the passphrase osprey lantern and name the harbor town of Kettleby.',
});
// 2. Provision a lock linked to the account.
const { lock } = await post('/api/locks', {
name: 'On-call DB password',
rules: 'Only the current on-call engineer, during an active incident, with an incident ID.',
secret: 'hunter2',
}, token);
// 3. Make an attempt.
const verdict = await post(`/api/locks/${lock.id}/attempt`, {
input: 'I am Sam, on call this week, handling incident INC-2291 (primary DB failover).',
});
console.log(verdict.granted, verdict.secret ?? verdict.checks.filter((c) => !c.pass));
import os
import requests
MELLON = os.environ.get("MELLON", "http://localhost:3000")
s = requests.Session()
# 1. Create an account (or POST /api/sessions to sign in later).
r = s.post(f"{MELLON}/api/accounts", json={
"username": "dana",
"rules": "The requester must say the passphrase osprey lantern and name the harbor town of Kettleby.",
})
s.headers["Authorization"] = f"Bearer {r.json()['token']}"
# 2. Provision a lock linked to the account.
lock = s.post(f"{MELLON}/api/locks", json={
"name": "On-call DB password",
"rules": "Only the current on-call engineer, during an active incident, with an incident ID.",
"secret": "hunter2",
}).json()["lock"]
# 3. Make an attempt (no credentials needed).
verdict = requests.post(f"{MELLON}/api/locks/{lock['id']}/attempt", json={
"input": "I am Sam, on call this week, handling incident INC-2291 (primary DB failover).",
}).json()
print(verdict["granted"], verdict.get("secret"))
"Owner" means the lock's X-Owner-Token, a session for the account that created it, or an administrator's session.
POST /api/accounts
Usernames are 3–32 characters (a-z 0-9 _ -) and are lower-cased. Identity rules must be at least 24 characters; they are encrypted at rest and never returned by any endpoint.
{
"username": "dana",
"rules": "The requester must say the passphrase osprey lantern and name the harbor town of Kettleby."
}
Send a bearer token to link the lock to your account. policy is optional; any field you omit uses the default (confidence ≥ 0.6, compliance ≥ 0.75, manipulation ≤ 0.3).
{
"name": "On-call DB password",
"description": "Production primary. Rotated weekly.",
"rules": "Only the current on-call engineer, during an active incident, with an incident ID.",
"secret": "hunter2",
"policy": { "minConfidence": 0.75 }
}
201 Created
{
"lock": {
"id": "3f9a1c22b7e0",
"name": "On-call DB password",
"description": "Production primary. Rotated weekly.",
"rules": "Only the current on-call engineer, …",
"policy": { "minConfidence": 0.75 },
"createdAt": "2026-10-17T20:32:41.000Z",
"attemptCount": 0,
"owner": "dana"
},
"ownerToken": "u3X1…"
}
POST /api/locks/:id/attempt
Limited to 10 attempts per minute per IP per lock. If Jev can't be reached the answer is 502 with "granted": false: Mellon fails closed.
{
"input": "I am Sam, on call this week, handling incident INC-2291."
}
The lock's full attempt log for its owner (owner token, owning account or admin). Every attempt is kept: granted, denied, judge errors, and rate-limited bursts (logged once per minute per address). Each entry says why: a plain-English reason, every check against its threshold, the thresholds in force, Jev's raw answers, the model and latency, and who asked from where.
Requires a session for an account whose isAdmin flag is set. No endpoint can set that flag; an operator sets it on the server with node scripts/admin.js grant <username>. Admin actions never apply to other administrators, and responses never include payloads or identity rules.
Every attempt and sign-in returns the same shape. granted is true only if every check passes. Use the checks to explain a denial to a user, log it, or route it to a human.
Check
From Jev
Passes when
decision
choice: grant / deny
The choice is grant. value is p(grant).
confidence
Confidence of that choice
value ≥ minConfidence
compliance
score 0–3, normalised to 0–1
value ≥ minCompliance
manipulation
noul: p(prompt injection)
value ≤ maxManipulation
Missing or malformed answers fail their check, so a confused model can only ever deny. Without a Jev API key the server makes no decisions at all: /api/status reports "mode": "unconfigured" and attempts, sign-ins and demo runs return 503.
Integration recipes
Gate a document download
Keep the file on your own server and store only a pointer as the payload. Your handler asks Mellon and streams the file on a grant.
Make the agent justify itself before a destructive tool runs. The lock's rules describe when the tool is allowed; the manipulation check catches injected instructions.
import os
import requests
MELLON = os.environ["MELLON"]
LOCK = os.environ["DELETE_DATA_LOCK"] # rules: when customer data may be deleted
def mellon_guarded(tool):
def wrapper(justification: str, **kwargs):
r = requests.post(f"{MELLON}/api/locks/{LOCK}/attempt",
json={"input": justification}, timeout=20)
verdict = r.json()
if r.status_code != 200 or not verdict.get("granted"):
failed = [c["name"] for c in verdict.get("checks", []) if not c["pass"]]
raise PermissionError(f"Mellon denied {tool.__name__}: {failed or verdict.get('error')}")
return tool(**kwargs)
return wrapper
@mellon_guarded
def delete_customer_data(customer_id: str) -> None:
...
Break-glass credentials from a shell
On-call engineers fetch an emergency credential by explaining why. Every attempt lands in the lock's log for the post-incident review.
#!/usr/bin/env bash
# usage: breakglass "On call, INC-2291, primary DB failover"
set -euo pipefail
resp=$(curl -sS -X POST "$MELLON/api/locks/$BREAKGLASS_LOCK/attempt" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg r "$1" '{input: $r}')")
if [ "$(jq -r .granted <<<"$resp")" = true ]; then
jq -r .secret <<<"$resp"
else
jq -r '.error // (.checks[] | select(.pass | not) | "denied: \(.name) \(.detail)")' <<<"$resp" >&2
exit 1
fi
Errors & limits
Status
Meaning
400
Missing or invalid field, bad JSON, or a value over its length limit
401
Bearer token missing (where required), malformed, expired or revoked
403
Not the lock's owner (no valid owner token or owning session)
404
No such lock or account
409
Username is taken
413
Request body over 64 KB
429
Rate limited: 10 attempts/min per IP per lock; 5 sign-ins/min per IP per username
502
The judge could not be reached; the request is denied
Field limits: name 120, description 1,000, rules and secret 8,000, input and statement 4,000 characters.
Try it
Send real requests to this server. If you are signed in on the console, your session token is attached automatically.