Tutorial: your first renewal
This walks the whole path once, end to end, so the pieces connect. Roughly fifteen minutes. Where the API cannot take you further, it says so rather than pretending.
You need a NextPKI tenant, and someone with the approver role - possibly you.
1. A token with the right scopes
Section titled “1. A token with the right scopes”Console, Settings → API tokens. Create one with both certs:read and
certs:renew. Copy it now; it is stored as a hash and cannot be shown again.
export NEXTPKI_TOKEN='npapi.…'export NEXTPKI_BASE='https://api.nextpki.com' # api.nextpki.de also serves EUConfirm it works and see what it carries:
curl -sS "$NEXTPKI_BASE/v1/auth/validate" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" | jq{ "valid": true, "tenant_id": "11111111-1111-1111-1111-111111111111", "name": "tutorial", "scopes": ["certs:read", "certs:renew"]}If scopes lacks certs:renew, stop here and make a new token. Scopes are fixed at
creation - there is no way to add one later.
2. Find something to renew
Section titled “2. Find something to renew”curl -sS "$NEXTPKI_BASE/v1/certificates?limit=200" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" \| jq -r '.items[] | select(.is_revoked == false) | [.id, .common_name, .not_after, .issuer_dn] | @tsv' \| sort -t$'\t' -k3 | headSorted by expiry, soonest first. Pick one you are willing to experiment with:
export CERT_ID='…'Look at it properly before renewing:
curl -sS "$NEXTPKI_BASE/v1/certificates/$CERT_ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" \| jq '{common_name, subject_dn, san_dns, not_after, trust_status, last_seen_at}'last_seen_at is the field worth a second look. If it is weeks old, nothing is
serving this certificate and renewing it is busywork - pick another. That
distinction is the whole reason observations exist.
3. Key and CSR, on the right host
Section titled “3. Key and CSR, on the right host”Generate these where the certificate will serve. In a tutorial that is your laptop; in production it is the web server.
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ -nodes -keyout tutorial.key -out tutorial.csr \ -subj "/CN=www.example.com" \ -addext "subjectAltName=DNS:www.example.com,DNS:example.com"chmod 600 tutorial.keyTwo things worth noticing. The SANs matter and the Common Name does not - clients
have checked SAN for years, and a certificate with only a CN is rejected. And
-nodes means the key is unencrypted, which is what a web server needs at boot;
protect it with file permissions, not a passphrase you would have to type after
every reboot.
Check what you built:
openssl req -in tutorial.csr -noout -text | grep -A2 'Subject Alternative Name'4. Which connector
Section titled “4. Which connector”Renewal orders go through a connector - your account at a CA. Connectors are configured in the Console under Settings → Connectors, and there is no connector API in v1, so take the ID from there:
export CONNECTOR_ID='…'5. Open the renewal
Section titled “5. Open the renewal”curl -sS -X POST "$NEXTPKI_BASE/v1/certificates/$CERT_ID/renew" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg csr "$(cat tutorial.csr)" --arg c "$CONNECTOR_ID" \ '{connector_id: $c, csr: $csr}')" | jq{ "id": "88880000-0000-0000-0000-000000000001", "state": "pending_approval" }export RENEWAL_ID='88880000-0000-0000-0000-000000000001'Note what did not happen: no CA was contacted and no money was spent. The renewal is sitting in a queue.
6. Approval - by a person
Section titled “6. Approval - by a person”curl -sS "$NEXTPKI_BASE/v1/requests/$RENEWAL_ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" | jq '{state, state_reason}'It will stay pending_approval until someone with the approver role approves it in
the Console, under Renewals.
There is no API call for this and no scope that grants it. That is deliberate: it means a leaked automation token cannot spend your money. See roles and permissions.
7. Watch it move
Section titled “7. Watch it move”while :; do read -r state reason < <(curl -sS "$NEXTPKI_BASE/v1/requests/$RENEWAL_ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" \ | jq -r '[.state, (.state_reason // "-")] | @tsv') printf '%-18s %s\n' "$state" "$reason" case "$state" in deployed|failed|cancelled) break ;; issued) echo "issued - see step 8"; break ;; esac sleep 15doneYou should see approved, csr_generated, submitted, issued. Break on all
terminal states, not just the happy one, or a failed renewal loops forever.
If it fails, state_reason says why -
failure modes covers the ones you will
actually hit.
8. Where the API stops
Section titled “8. Where the API stops”curl -sS "$NEXTPKI_BASE/v1/requests/$RENEWAL_ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" | jq '{state, new_cert_id}'new_cert_id identifies the certificate that now exists in your inventory. You can
read its metadata:
NEW_ID=$(curl -sS "$NEXTPKI_BASE/v1/requests/$RENEWAL_ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" | jq -r .new_cert_id)
curl -sS "$NEXTPKI_BASE/v1/certificates/$NEW_ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" \| jq '{common_name, not_before, not_after, fingerprint_sha256}'And that is as far as v1 goes. There is no endpoint that returns the certificate bytes, so you cannot install it from here. Download it from the Console, or fetch it from your CA. Deploy a renewed certificate covers both, and the ordering that avoids an outage.
What you just learned
Section titled “What you just learned”- Scopes are fixed at creation, so get them right the first time.
last_seen_atdecides whether a certificate is worth renewing at all.- The private key never leaves the host, because only the CSR is ever sent.
- Approval is a human step by design, not an oversight.
- Terminal states are terminal; a retry is a new renewal.
- The loop does not close in v1 - the certificate has to come from somewhere else.
- Automate renewals - the same flow, unattended
- Monitor expiring certificates - find the work
- Client libraries - stop writing curl