Certificate API — Integration Guide
This guide is for teams that want to request SSL certificates programmatically through Stanford's certificate provisioning API, rather than through the web UI.
1. Prerequisites
Before you can call the API, you need two things set up:
a. Set up a Keycloak API client
If your team doesn't already have one, request it:
- Open a ServiceNow ticket
- Assignment Group:
UIT Authentication and Collaboration - Request: API access / a Keycloak client for certificate automation
The team will generate a client_credentials-only Keycloak client for you (no interactive login—this is for machine-to-machine automation) and give you a client_id and client_secret.
b. Set up a KEYCLOAK_ID custom field on each NetDB node you need certs for
For every hostname (and every SAN, if requesting a multi-domain certificate) you want to request a certificate for, someone with edit access on that NetDB node needs to:
- Log in to https://netdb.stanford.edu/.
- Search for the node or domain where you want certificate issuance to be API-based and automated (for example,
med4.stanford.edu). - Edit it.
- Add a Custom Field labeled
KEYCLOAK_IDwith the value set to your client'sclient_id(for example,acs_linux_auth1 provided by the UIT Authentication and Collaboration team)
This is how the API authorizes your client to request a certificate for that specific hostname—the API checks the caller's Keycloak client_id against this custom field on the node before allowing the request through. If it's missing or set to a different value, the request will be rejected with a message telling you exactly which node needs it and what value to set.
2. Get a Bearer token
shell
TOKEN=$(curl -s -X POST \
"https://keycloak.svc.stanford.edu/auth/realms/stanford-oauth-dev/protocol/openid-connect/token" \
-d "grant_type=client_credentials" \
-d "client_id=<your_client_id>" \
-d "client_secret=<your_client_secret>" \
| python3 -c "import sys, json; print(json.load(sys.stdin)['access_token'])")Tokens are short-lived—request a fresh one for each session/script run rather than caching it long-term.
3. Build the request payload
At minimum, the payload needs:
shell
jq -Rs '{csr: .}' your-cert.csr > payload.json
Then add the required fields:
Python
import json
with open("payload.json") as f:
payload = json.load(f)
payload["contact_email"] = "you@stanford.edu"
payload["server_type"] = "Apache" # or Tomcat, Microsoft IIS, Java Web Server, RedHat Linux, Other
payload["duration"] = 199 # days
with open("payload.json", "w") as f:
json.dump(payload, f)A note on SANs: You only need to add a sans field if your CSR includes Subject Alternative Names beyond the primary Common Name (i.e., a multi-domain UCC certificate). For a standard single-domain certificate, leave sans out entirely—the API infers the domain from the CSR's CN. If you do include sans, it must exactly match what's actually encoded in the CSR itself, or the request will be rejected with a mismatch error telling you which hostnames are missing or extra.
4. Submit the request [first in UAT env to test your script]
shell
curl -s -X POST "https://certprovision-uat.iam.stanford.edu/api/submit" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @payload.json \
| python3 -m json.tool(Use certprovision-prod.iam.stanford.edu once you're ready to move from UAT to production.)
A successful submission returns something like:
JSON
{
"id": 38204,
"cn": "your-host.stanford.edu",
"cert_type": "normal",
"order_number": null,
"duration": 199,
"server_type": "Apache/ModSSL",
"async": true,
"backend": "acme"
}Certificate issuance happens asynchronously in the background—you'll be notified by email at the contact email address you provided once it's ready.
5. Common error messages
| Message contains | Meaning | Fix |
|---|---|---|
has no KEYCLOAK_ID Custom Field set | The NetDB node isn't linked to your client yet | Add the KEYCLOAK_ID custom field per step 1b above |
has a KEYCLOAK_ID Custom Field set to '...', which does not match | The node is linked to a different client | Correct the KEYCLOAK_ID value, or use the client it's actually set to |
the Subject Alternative Names in your CSR do not match the hostnames you requested | Your sans field and your CSR's actual SAN extension disagree | Make sure sans (if present) exactly matches what's in the CSR, or drop sans if you don't need it |
Not authenticated | Missing or invalid Bearer token | Confirm your token hasn't expired and the Authorization header is set correctly |
Questions
Reach out via the UIT Authentication and Collaboration group or submit a Help ticket.
