Skip to content

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

  • openssl and curl
  • 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.)

Terminal window
export STACKRUNNER_BEARER=<paid-bearer>
export STACKRUNNER_HANDLE=apexrunner

On provisioning your dashboard polls “provisioning your CA…” → “your CA is ready” within ~30 seconds. Behind the scenes:

  1. provision-customer-ca Cloud Function creates dev-{handle}-root (10-year, HSM) and dev-{handle}-intermediate (2-year, HSM, rotated annually) in Google KMS.
  2. The root self-signs; the root signs the intermediate; both PEMs are persisted to your Firestore customer record.
  3. mint-paid-runtime is granted roles/cloudkms.signer on your intermediate (per-key scope, dynamic — your key is the only one this SA can sign with on your behalf).
  4. Trust artifacts publish to <handle>.pki.stackrunner.dev/ca/.

2. Make keypairs + CSRs

Single cert:

Terminal window
openssl ecparam -name prime256v1 -genkey -noout -out leaf.key
openssl 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:

Terminal window
mkdir -p batch
for 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"
done

3. Mint

Single cert

Terminal window
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"
}
EOF

Batch (count > 1)

Terminal window
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}/mint

Response 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

Terminal window
curl -sS https://${STACKRUNNER_HANDLE}.pki.stackrunner.dev/ca/root.pem > root.pem
curl -sS https://${STACKRUNNER_HANDLE}.pki.stackrunner.dev/ca/intermediate.pem > intermediate.pem
openssl verify -CAfile root.pem -untrusted intermediate.pem leaf.cert.pem
# expected: leaf.cert.pem: OK

The 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):

Terminal window
curl -sS https://${STACKRUNNER_HANDLE}.pki.stackrunner.dev/cert/<serial>

Returns {"status":"active"|"revoked"|"expired", "revoked_at":"...", "revoked_reason":"..."}.

CRL:

Terminal window
curl -sS http://pki.stackrunner.dev/${STACKRUNNER_HANDLE}/intermediate.crl > crl.der
openssl 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/mo5,000
Rate cap500 certs/min
Batch size1–100
TTL range1h–90d
Intermediate validity2y (rotated annually)
Root validity10y (offline after ceremony)

Common errors

HTTPcodewhat it means
401(Cloud Run rejects)bearer not recognized; check STACKRUNNER_BEARER
404customer_not_foundhandle in URL doesn’t match any active paid customer
400bad_countcsr_pems empty or > 100
400bad_ttlTTL outside [1h, 90d]
400handle_mismatchbody’s handle differs from URL handle
400issue_failedone of the CSRs failed to parse or sign — whole batch rejected
429rate_limited>500 certs/min on this handle
503customer_not_activeaccount suspended or being provisioned

Help

  • Email: support@stackrunner.dev
  • Status: https://status.stackrunner.dev
  • Founder badge for first 100 Dev subscribers — discount on BYOR add-on once that ships