Skip to main content

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:

  1. Log in to https://netdb.stanford.edu/.
  2. Search for the node or domain where you want certificate issuance to be API-based and automated (for example, med4.stanford.edu).
  3. Edit it.
  4. Add a Custom Field labeled KEYCLOAK_ID with the value set to your client's client_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 containsMeaningFix
has no KEYCLOAK_ID Custom Field setThe NetDB node isn't linked to your client yetAdd the KEYCLOAK_ID custom field per step 1b above
has a KEYCLOAK_ID Custom Field set to '...', which does not matchThe node is linked to a different clientCorrect 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 requestedYour sans field and your CSR's actual SAN extension disagreeMake sure sans (if present) exactly matches what's in the CSR, or drop sans if you don't need it
Not authenticatedMissing or invalid Bearer tokenConfirm 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.

Last modified