The renewal state machine
A renewal is a long-running process, not a request/response call. It has eight states, and the transitions between them are enforced by a database trigger - an invalid jump is rejected outright rather than silently applied. That means the state you read is a state the system actually reached.
The graph
Section titled “The graph”pending_approval ──► approved ──► csr_generated ──► submitted ──► issued ──► deployed │ │ │ │ │ └──► cancelled ├──► failed ├──► failed ├──► failed ├──► failed └──► cancelled └──► cancelled └──► cancelled└──► cancelleddeployed, failed and cancelled are terminal. Nothing leaves them - not
even a retry. A retry is a new renewal row, which means your integration should
never expect an ID to come back to life.
Two consequences worth designing around:
pending_approvalcan only becomeapprovedorcancelled. It cannot fail, because nothing has been attempted yet.- Every non-terminal state after approval can fail or be cancelled, because from
approvedonwards real work with a real CA is happening.
What each state means
Section titled “What each state means”| State | What has happened | What moves it on |
|---|---|---|
pending_approval |
The request exists. Nothing has been sent anywhere | A human with the approver role approves it |
approved |
Spending is authorised | The worker picks it up and prepares the CSR |
csr_generated |
A CSR is ready - either the one you supplied or an ephemeral one from the scheduler | The worker submits the order to the CA |
submitted |
The order is with the CA. For ACME this is where the challenge happens | The CA issues |
issued |
The certificate exists and is stored in your inventory | Deployment confirms |
deployed |
The certificate is in service | - terminal |
failed |
Something went wrong; state_reason says what |
- terminal, open a new renewal |
cancelled |
A person stopped it | - terminal |
Why approval is mandatory
Section titled “Why approval is mandatory”Ordering a certificate from a commercial CA costs money. NextPKI therefore never
lets an API token, an automation or an agent cause issuance on its own: the
certs:renew scope can create a renewal, and only a person with the approver
role can approve one.
This is a deliberate split. The worst a leaked automation token can do is fill your approval queue with noise.
Failure modes
Section titled “Failure modes”When state is failed, state_reason carries the cause. These are the ones
you will actually see:
state_reason |
Meaning | What to do |
|---|---|---|
worker_timeout |
The renewal sat in a non-terminal state for over an hour and was swept up | Usually a stuck CA order. Open a new renewal |
| Circuit breaker | The connector saw 3 failures in 10 minutes and paused for 30 | Wait, or check whether the CA is down |
| Spending cap | The connector’s monthly cap would be exceeded | Raise the cap in the Console, or use a different connector |
| Issuer allowlist | The issued certificate’s issuer DN was not on the connector’s allowlist | Either the CA changed its issuing CA, or something is wrong. Investigate before widening the list |
| Anomaly detected | Renewal rate exceeded five times the connector baseline | Deliberate bulk operation? Approve again. Otherwise investigate |
| CA-specific errors | Validation failure, rate limit, bad CSR | Read the message; most are actionable |
The circuit breaker, spending caps and anomaly detection are protections against a runaway loop spending your money. If you are doing a legitimate bulk migration, raise the caps first rather than fighting the breaker.
Auto-renewal
Section titled “Auto-renewal”A certificate flagged for auto-renewal does not need you to create anything. A scheduler opens a renewal itself once the certificate enters a 30-day window before expiry, using an ephemeral server-side CSR.
Approval is still required. Auto-renewal removes the “remember to do it” step, not the authorisation step.
If you want to supply your own CSR - because the private key must be generated on the serving host - do not rely on the scheduler. Create the renewal yourself through the API with your CSR.
Polling it correctly
Section titled “Polling it correctly”while :; do state=$(curl -sS "https://api.nextpki.com/v1/requests/$ID" \ -H "Authorization: Bearer $NEXTPKI_TOKEN" | jq -r .state) echo "$state" case "$state" in deployed|failed|cancelled) break ;; esac sleep 15doneBreak on the terminal states, not on the one you hope for - otherwise a failed
renewal loops forever. Fifteen seconds is reasonable while you are actively
waiting; do not poll every renewal in your estate on that interval, and see
rate limits.