Free tier credentials
One credential type. One scope. One way to mint. Free and Mint Pack customers hold a single shared-secret bearer that authorises every mint they’re entitled to. There’s no permission-scope surface — that ships only on Dev tier.
The one bearer
| Property | Value |
|---|---|
| What it is | An opaque 32-byte random secret, base64url-encoded. |
| Where it lives | Workers KV namespace mint-free-state-prod at key token:<sha256-of-bearer>. The plaintext is shown to you once; we store only the hash. |
| What it authorises | POST mint.stackrunner.dev/<ttl>/v1/free/issue (sign a cert) and POST mint.stackrunner.dev/v1/free/revoke (revoke a cert you previously minted; revoke is TTL-less). |
| Scope | The entire Free Pack / Mint Pack quota for your handle. There is no per-CSR or per-purpose narrowing. |
| Rotation | None today. Email support@ if it leaks; we revoke the hash and reissue. |
| Quota | 25 lifetime certs (Free Pack). Mint Pack lifts the cap; same bearer. |
The bearer is the only credential the mint-free plane reads. It
is also the only credential you, as a Free customer, hold — there is
no birth cert, no scoped credential, no Teller surface, no Firebase
SSO past signup. See “What’s not here on Free” below.
How you get it
You sign up on the dashboard with Google (or GitHub) SSO; the dashboard
calls the bearer-claim-free Cloud Function exactly once for your
handle. That CF generates the bearer, hashes it, writes the hash into
KV under token:<hash>, marks bearer_claimed_at on your Firestore
customer record, and returns the plaintext to the browser only.
The same record can never be claimed twice; a second call returns
409 already_claimed. If you lose the bearer, the only recovery is
operator-initiated rotation.
# After dashboard signup — copy the bearer from the modal.export STACKRUNNER_BEARER='<your-43-char-bearer-from-the-claim-modal>'export STACKRUNNER_HANDLE=your-handleHow you use it
The bearer goes in the Authorization header on every mint. The TTL
is the first path segment on mint.stackrunner.dev/ — one of
{1h, 1d, 7d, 14d, 30d}. The worker rejects URL-vs-body mismatch
with 400 ttl_mismatch. On *.workers.dev (dev/test) the worker
falls back to body.ttl.
openssl ecparam -name prime256v1 -genkey -noout -out leaf.keyopenssl req -new -key leaf.key -out leaf.csr \ -subj "/CN=${STACKRUNNER_HANDLE}.leaf.example"
curl -fsS \ -H "Authorization: Bearer ${STACKRUNNER_BEARER}" \ -H "Content-Type: application/x-pem-file" \ --data-binary @leaf.csr \ https://mint.stackrunner.dev/7d/v1/free/issue \ | jq -r .cert_pem > leaf.crtThat’s the public contract — raw CSR PEM body, no JSON envelope. If
you’d rather send the envelope (for the version lever, or because
your tooling already wraps requests), set Content-Type: application/json and POST {"version":"v1","csr_pem":"..."}
instead; the response is identical.
The worker hashes the bearer, looks the hash up in KV, confirms the
handle binding, enforces the per-minute rate limit (5 certs/min on Free
Pack) and the 25-certs-total lifetime cap (with a per-TTL ceiling of
25 — same as the total, so you can spend the whole pack on a single
TTL bucket), then proxies to the mint-free Cloud Function for signing.
To revoke:
curl -fsS \ -H "Authorization: Bearer ${STACKRUNNER_BEARER}" \ -H "Content-Type: application/json" \ -d '{"version":"v1","serial":"3e:5f:..."}' \ https://mint.stackrunner.dev/v1/free/revoke(/v1/free/revoke is not TTL-scoped, so it doesn’t ride a <ttl>.
path segment. Bare mint.stackrunner.dev/v1/free/revoke works.)
What’s NOT here on Free
The following exist on Dev tier and are absent on Free. They are
not feature-flagged off — the routes literally don’t exist in the
Free-plane worker. Trying to call them returns 404 bad_path.
| Capability | Free Pack | Dev tier |
|---|---|---|
POST /v1/sign-leaf — scoped CSR mint, leaf Subject overridden | ❌ no route | ✅ |
POST /v1/cross-sign — preserve CSR Subject + SANs in the issued leaf | ❌ no route | ✅ |
| Per-credential rotation / revocation without rotating the master bearer | ❌ | ✅ (scoped credentials are independent) |
| Birth cert (your own mTLS leaf for service-to-service mTLS) | ❌ | ✅ |
Teller projection surface (teller.stackrunner.dev/<handle>/issue) | ❌ | ✅ |
EST (RFC 7030) enrollment envelope (est.stackrunner.dev) | ❌ | ✅ (via scoped credential) |
| Per-customer dedicated intermediate CA | ❌ shared intermediate-free | ✅ dev-<handle>-intermediate |
If you need any of the above, you need Dev tier. The bearer claim flow doesn’t gate against tier directly — the routes themselves are the gate.
Rotation, revocation, recovery
- Bearer rotation: not self-serve today. Email
support@— we revoke the existing hash in KV and reissue. Cuts off in-flight callers; coordinate. - Cert revocation: any cert you minted, via the
/v1/free/revokeendpoint above. Revocation is published in your CRL atpki.stackrunner.dev/<handle>/intermediate.crlwithin ~1 minute. - Handle compromise: if you suspect the bearer is in the wrong hands, revoke any valuable certs immediately, then ask for rotation. The bearer can’t mint past your lifetime cert cap, but it can burn your quota.
Operational details (for the curious)
- The
mint-free-state-prodKV namespace is shared across all Free Pack handles; entries are independent (token:<hash>is keyed by the secret, not by handle). - The bearer record stores
{handle, tier?, created_at}.tieris informational; the worker’s enforcement is per-handle from KV + per-handle from D1 rate-limit. - Quota counters live in KV at
quota:<handle>:<yyyy-mm>andquota:<handle>:<yyyy-mm>:ttl-<bucket>. The nightlyquota-reconcileCloud Function reconciles them againstaudit_eventsin Firestore and writes monthly snapshots. - The bearer never appears in audit logs in plaintext — only the hash + the resolved handle.
See also
- Free tier quickstart — end-to-end first cert.
- Dev tier credentials — what tier-up unlocks.