Dev tier quickstart
Your own dedicated CA, KMS-backed, batch minting, 90-day max-TTL discipline. From sign-in to first cert: 60 seconds.
What you get
- Dedicated per-customer intermediate CA (
dev-{handle}-intermediate), HSM-protected, signed by a per-customer root that’s offline after the one-time ceremony. - 5,000 leaf certs/month included. Rate cap: 500 certs/min.
- Batch minting, up to 100 CSRs per request, all-or-nothing — either every cert in the batch comes back or none do.
- Programmable TTLs anywhere from 1 hour to 90 days.
- Customer-scoped PKI URLs:
<handle>.pki.stackrunner.dev/ca/root.pem,intermediate.pem,chain.pem, plus a per-customer CRL endpoint and serial-lookup API. - Birth cert (1-year TTL) for authenticating your own services to each other — issued by your intermediate, downloads as PKCS#12.
- mTLS upgrade path: bearer auth at launch; mTLS to stackrunner via the birth cert ships as Phase 1.5 when Cloudflare API Shield costs pencil out.
Prerequisites
opensslandcurl- A Google account on the pre-launch SSO allowlist (or invited via
support@) - During build-out: your
apexrunner/oreo-boyboy/ etc. mock-paid bearer (operator issues — won’t be self-serve until Stripe wires up in Step 7.8)
1. Sign up
(Self-serve flow is Step 7.6 — Stripe-checkout-then-provisioning.
For now, an operator runs scripts/promote_mock_paid.py <handle> and
hands you the bearer.)
export STACKRUNNER_BEARER=<paid-bearer>export STACKRUNNER_HANDLE=apexrunnerOn provisioning your dashboard polls “provisioning your CA…” → “your CA is ready” within ~30 seconds. Behind the scenes:
provision-customer-caCloud Function createsdev-{handle}-root(10-year, HSM) anddev-{handle}-intermediate(2-year, HSM, rotated annually) in Google KMS.- The root self-signs; the root signs the intermediate; both PEMs are persisted to your Firestore customer record.
mint-paid-runtimeis grantedroles/cloudkms.signeron your intermediate (per-key scope, dynamic — your key is the only one this SA can sign with on your behalf).- Trust artifacts publish to
<handle>.pki.stackrunner.dev/ca/.
2. Make keypairs + CSRs
Single cert:
openssl ecparam -name prime256v1 -genkey -noout -out leaf.keyopenssl req -new -key leaf.key -out leaf.csr \ -subj "/CN=app-1.${STACKRUNNER_HANDLE}.internal" \ -addext "subjectAltName = DNS:app-1.${STACKRUNNER_HANDLE}.internal"Batch of 10 — same shape, just loop:
mkdir -p batchfor i in $(seq 1 10); do openssl ecparam -name prime256v1 -genkey -noout -out batch/leaf-${i}.key openssl req -new -key batch/leaf-${i}.key -out batch/leaf-${i}.csr \ -subj "/CN=app-${i}.${STACKRUNNER_HANDLE}.internal" \ -addext "subjectAltName = DNS:app-${i}.${STACKRUNNER_HANDLE}.internal"done3. Mint
Single cert
curl -sS -X POST \ -H "Authorization: Bearer ${STACKRUNNER_BEARER}" \ -H "Content-Type: application/json" \ -d @- \ https://mint.stackrunner.dev/${STACKRUNNER_HANDLE}/mint <<EOF{ "version": "v1", "csr_pems": [$(jq -Rs . < leaf.csr)], "ttl": "30d"}EOFBatch (count > 1)
CSRS=$(for f in batch/leaf-*.csr; do jq -Rs . < "$f"; done | paste -sd,)curl -sS -X POST \ -H "Authorization: Bearer ${STACKRUNNER_BEARER}" \ -H "Content-Type: application/json" \ -d "{\"version\":\"v1\",\"csr_pems\":[${CSRS}],\"ttl\":\"7d\"}" \ https://mint.stackrunner.dev/${STACKRUNNER_HANDLE}/mintResponse shape on success:
{ "version": "v1", "certs": [ { "cert_pem": "-----BEGIN CERTIFICATE-----\n…", "serial": "ec1b7cc69f…", "fingerprint": "b38f2fce3427…", "issued_at": "2026-05-19T00:02:03Z", "expires_at": "2026-05-26T00:03:03Z" } ], "chain": "-----BEGIN CERTIFICATE-----\n… (intermediate)\n…\n-----BEGIN CERTIFICATE-----\n… (root)\n…", "batch_id": "apexrunner-#42"}batch_id is present when csr_pems.length > 1; it increments
per-customer via a Firestore transaction so it’s unique and ordered.
TTL accepts:
- Day-bucket shorthand:
1d,7d,14d,30d,90d - Any Go duration string in
[1h, 2160h](= 90 days) - Anything outside that range is
400 bad_ttl
4. Verify the chain
curl -sS https://${STACKRUNNER_HANDLE}.pki.stackrunner.dev/ca/root.pem > root.pemcurl -sS https://${STACKRUNNER_HANDLE}.pki.stackrunner.dev/ca/intermediate.pem > intermediate.pemopenssl verify -CAfile root.pem -untrusted intermediate.pem leaf.cert.pem# expected: leaf.cert.pem: OKThe chain field in the response is the intermediate + root concatenated
— you can write it to disk and skip the curls if you’d rather not
round-trip.
5. Check cert validity
Public per-customer status API (anyone can call — handle scoped):
curl -sS https://${STACKRUNNER_HANDLE}.pki.stackrunner.dev/cert/<serial>Returns {"status":"active"|"revoked"|"expired", "revoked_at":"...", "revoked_reason":"..."}.
CRL:
curl -sS http://pki.stackrunner.dev/${STACKRUNNER_HANDLE}/intermediate.crl > crl.deropenssl crl -in crl.der -inform DER -text -noout | head -20(CRL is HTTP per RFC 5280 — TLS for the CRL itself would create a chicken-and-egg.)
6. Birth cert (for your own mTLS, not for talking to us)
The birth cert is a 1-year leaf signed by your intermediate that your own services can use as the trust anchor for mTLS between microservices. It is not authentication to stackrunner — at launch, that’s the bearer above.
Dashboard generates a P-256 keypair in your browser via WebCrypto, sends only the CSR, and returns the signed birth cert + chain PKCS#12-bundled for you to download. Your private key never touches our servers.
Limits + rate caps
| Dev tier | |
|---|---|
| Included certs/mo | 5,000 |
| Rate cap | 500 certs/min |
| Batch size | 1–100 |
| TTL range | 1h–90d |
| Intermediate validity | 2y (rotated annually) |
| Root validity | 10y (offline after ceremony) |
Common errors
| HTTP | code | what it means |
|---|---|---|
401 | (Cloud Run rejects) | bearer not recognized; check STACKRUNNER_BEARER |
404 | customer_not_found | handle in URL doesn’t match any active paid customer |
400 | bad_count | csr_pems empty or > 100 |
400 | bad_ttl | TTL outside [1h, 90d] |
400 | handle_mismatch | body’s handle differs from URL handle |
400 | issue_failed | one of the CSRs failed to parse or sign — whole batch rejected |
429 | rate_limited | >500 certs/min on this handle |
503 | customer_not_active | account suspended or being provisioned |
Help
- Email:
[email protected] - Status: https://status.stackrunner.dev
- Founder badge for first 100 Dev subscribers — discount on BYOR add-on once that ships