Bulk revoke multiple certificates
13 minute read
Bulk revocation is essential when you need to quickly invalidate large numbers of certificates due to events such as key compromise, misissuance, or other security incidents. For example, when a certificate profile is retired, incorrectly configured, or identified as having issued certificates improperly within a defined timeframe, all certificates issued under that profile during that period should be revoked immediately. DigiCert® Trust Lifecycle Manager allows you to search your certificate inventory using various criteria, then revoke each matching certificate programmatically through the API, providing a repeatable and auditable process for bulk revocation events.
What you’ll learn
In this tutorial, you will:
- Search your certificate inventory for all active certificates issued under a specified profile within a defined time window (
valid_fromandvalid_to). - Revoke each certificate using its
serial_numberwhile specifying an appropriaterevocation_reason. - Confirm that the status of each certificate has been updated to
revoked.
Before you begin
An API token with the following permissions:
Certificates: View certificates and certificate details in inventory.Revoke: Revoke certificates.
a1b2c3d4-e5f6-7890-abcd-ef1234567890). You can find this in the Trust Lifecycle Manager UI under Profiles, or from the profile.id field in any certificate detail response.YYYY-MM-DD_YYYY-MM-DD format (for example, 2026-03-01_2026-03-08 for a one-week period).jq installed for JSON formatting. Use jq --version to verify.Endpoint overview
| Purpose | Method | Endpoint |
|---|---|---|
| Search certificates | GET | /mpki/api/v1/certificate-search |
| Revoke certificate | PUT | /mpki/api/v1/certificate/{serial_number}/revoke |
| Verify revocation status | GET | /mpki/api/v1/certificate/{serial_number} |
Revocation reason codes
Every revocation request requires you to specify a reason code that explains why the certificate is being revoked. Choosing the correct reason ensures accurate audit trails and proper compliance with certificate policies. While private certificates support all available reason codes, public and ACME-based certificates are limited to a subset of supported values key_compromise, affiliation_changed, superseded, and cessation_of_operation. Refer to the table below to select the reason that best matches your use case.
| Reason code | When to use | Certificate types |
|---|---|---|
key_compromise | Private key was exposed or stolen | Private, Public, ACME |
affiliation_changed | Certificate holder’s organization changed | Private, Public, ACME |
superseded | Certificate was replaced by a new one | Private, Public, ACME |
cessation_of_operation | Domain or service is being decommissioned | Private, Public, ACME |
privilege_withdrawn | Issuing authority revoked the holder’s entitlement | Private only |
certificate_hold | Temporarily suspend the certificate (adds to CRL until removed or expired) | Private only |
ca_compromise | Certificate Authority was compromised | Private only |
unspecified | No specific reason applies | Private only |
certificate_hold suspends the certificate rather than revoking it permanently. The certificate is temporarily added to the CRL. To permanently revoke a suspended certificate, call the revoke endpoint again with a different reason code.Step 1: Search certificates by profile and issuance date
To revoke certificates, start by identifying the serial numbers of all active certificates issued under the target profile within the required time window. Use the following certificate search endpoint with the profile_id and valid_from parameters to retrieve the relevant certificates.
Request:
curl -X GET "https://demo.one.digicert.com/mpki/api/v1/certificate-search?profile_id=PROFILE_ID&valid_from=2025-03-01_2025-03-08&status=issued&limit=50&offset=0" \
-H "x-api-key: SERVICE_API_TOKEN" \
| jq '.'import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com"
SERVICE_API_TOKEN = "SERVICE_API_TOKEN"
# Query parameters
PROFILE_ID = "PROFILE_ID"
VALID_FROM = "2025-03-01_2025-03-08"
STATUS = "issued"
LIMIT = 50
OFFSET = 0
response = requests.get(
f"{BASE_URL}/mpki/api/v1/certificate-search",
headers={"x-api-key": SERVICE_API_TOKEN},
params={"profile_id": PROFILE_ID, "valid_from": VALID_FROM, "status": STATUS, "limit": LIMIT, "offset": OFFSET}
)
print(f"Status Code: {response.status_code}")
print(response.json())import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ApiExample {
public static void main(String[] args) throws Exception {
// Configuration
String baseUrl = "https://demo.one.digicert.com";
String serviceApiToken = "SERVICE_API_TOKEN";
// Query parameters
String profileId = "PROFILE_ID";
String validFrom = "2025-03-01_2025-03-08";
String status = "issued";
String limit = "50";
String offset = "0";
// Send request
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/mpki/api/v1/certificate-search"
+ "?profile_id=" + profileId
+ "&valid_from=" + validFrom
+ "&status=" + status
+ "&limit=" + limit
+ "&offset=" + offset))
.header("x-api-key", serviceApiToken)
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status Code: " + response.statusCode());
System.out.println(response.body());
}
}using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Configuration
string baseUrl = "https://demo.one.digicert.com";
string serviceApiToken = "SERVICE_API_TOKEN";
// Query parameters
string profileId = "PROFILE_ID";
string validFrom = "2025-03-01_2025-03-08";
string status = "issued";
string limit = "50";
string offset = "0";
// Send request
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", serviceApiToken);
var response = await client.GetAsync(
$"{baseUrl}/mpki/api/v1/certificate-search?profile_id={profileId}&valid_from={validFrom}&status={status}&limit={limit}&offset={offset}");
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Status Code: {(int)response.StatusCode}");
Console.WriteLine(responseBody);
}
}Specify the profile_id as the UUID of the certificate profile you want to target, and set valid_from to the issuance date range using the YYYY-MM-DD_YYYY-MM-DD format (for example, 2025-03-01_2025-03-08 for a one-week window). To exclude certificates that are already revoked or expired, include status=issued in your request. Use the limit and offset parameters to paginate through the results, noting that the default page size is 20 certificates. If no value is provided for the available query parameters, the system will consider default values.
profile_id with other filters such as issuing_ca_id or business_unit_id. You can also filter by valid_to if you need to constrain on the expiry window rather than the issuance window.Successful response (200 OK):
{
"total": 28,
"offset": 0,
"limit": 50,
"items": [
{
"profile": {
"id": "7f606454-e011-4cfb-af90-5a13232bbd8f",
"name": "Example Certificate Profile"
},
"seat": {
"seat_id": "some-seat-id@example.com"
},
"business_unit": {
"id": "12d3c45e-d3e6-46fc-7f48-91030fa5cac6",
"name": "Example-BU1"
},
"account": {
"id": "f3f99122-1234-567a-919b-5f692216ff85"
},
"certificate": "...(Base64-encoded certificate)...",
"ica": {
"id": "444FFF3A03C12BCEF463B5D2EF01985F"
},
"common_name": "host1.example.com",
"status": "issued",
"serial_number": "0F6F55555658A989F0F463A4DA0D807F",
"thumbprint": "FE0EFDC64AA55565556152850D2B84B46EF433444A28970F6A2A7F510A7A8880",
"valid_from": "2025-03-01T00:00:00Z",
"valid_to": "2026-03-08T23:59:59Z",
"notes": "Some example notes about the valid certificate",
"subject": {
"common_name": "host1.example.com",
"organization_name": "Example Org",
"organization_units": "Example Org Unit",
"description": "Some example description",
"serial_number": "0F6F55555658A989F0F463A4DA0D807F",
"pseudonym": "Some example pseudonym",
"unique_identifier": "3a3aeb976040eed5c5d3bfb6143b7814",
"user_identifier": "Some example user identifier",
"domain_component": "Some example domain component",
"dn_qualifier": "Some example DN qualifier",
"unstructured_name": "Some example unstructured name",
"unstructured_address": "Some example unstructured address",
"street_address": "Some example street address",
"postal_code": 89714,
"locality": "Carson city",
"state": "NV",
"country": "US",
"email": "some@mail",
"title": "Some example title",
"given_name": "Some example given name",
"surname": "Some example surname",
"organization_identifier": "1.3.6.1.4.1.311.60.2.1.3"
}
}
]
}
The response returns a paginated result with a total count and an items array. Each item represents a certificate enrolled under the target profile within the specified window. The profile.id in each item will match the profile_id you provided. Each item also includes a subject object with the full distinguished name fields, and additional metadata fields such as account, notes, and attributes depending on how the certificate was enrolled.
Check the total field. If total is greater than your limit, calculate how many additional pages you need and repeat the request with increasing offset values (offset=20, offset=40, and so on) until you have collected all matching serial numbers. Collect the serial_number and thumbprint values from every item and pass the serial number to the revocation endpoint in Step 2. The thumbprint is required if a serial number collision occurs.
Step 2: Revoke each certificate
With the list of serial numbers from Step 1, revoke each certificate individually by calling the revocation endpoint for each one. The API does not support batch revocation in a single request, hence you must submit one revocation call for each certificate you want to revoke.
Request:
curl -X PUT "https://demo.one.digicert.com/mpki/api/v1/certificate/SERIAL_NUMBER/revoke" \
-H "x-api-key: SERVICE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"revocation_reason": "privilege_withdrawn"}' \
| jq '.'import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com"
SERVICE_API_TOKEN = "SERVICE_API_TOKEN"
# Path parameter
SERIAL_NUMBER = "SERIAL_NUMBER"
response = requests.put(
f"{BASE_URL}/mpki/api/v1/certificate/{SERIAL_NUMBER}/revoke",
headers={
"x-api-key": SERVICE_API_TOKEN,
"Content-Type": "application/json"
},
json={"revocation_reason": "privilege_withdrawn"}
)
print(f"Status Code: {response.status_code}")
print(response.json())import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class ApiExample {
public static void main(String[] args) throws Exception {
// Configuration
String baseUrl = "https://demo.one.digicert.com";
String serviceApiToken = "SERVICE_API_TOKEN";
// Path parameter
String serialNumber = "SERIAL_NUMBER";
// Build payload
ObjectMapper mapper = new ObjectMapper();
ObjectNode payload = mapper.createObjectNode();
payload.put("revocation_reason", "privilege_withdrawn");
// Send request
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/mpki/api/v1/certificate/" + serialNumber + "/revoke"))
.header("x-api-key", serviceApiToken)
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(payload)))
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status Code: " + response.statusCode());
System.out.println(response.body());
}
}using System;
using System.Net.Http;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Configuration
string baseUrl = "https://demo.one.digicert.com";
string serviceApiToken = "SERVICE_API_TOKEN";
// Path parameter
string serialNumber = "SERIAL_NUMBER";
// Build payload
var payload = new JsonObject
{
["revocation_reason"] = "privilege_withdrawn"
};
// Send request
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", serviceApiToken);
var content = new StringContent(
payload.ToJsonString(),
Encoding.UTF8,
"application/json"
);
var response = await client.PutAsync(
$"{baseUrl}/mpki/api/v1/certificate/{serialNumber}/revoke", content);
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Status Code: {(int)response.StatusCode}");
Console.WriteLine(responseBody);
}
}Replace {serial_number} in the endpoint path with the serial number of the certificate you are revoking. Set revocation_reason in the request body with the appropriate reason code from the reference table above. Use the same reason code for all certificates in the same incident. This makes your audit trail consistent.
"Collision occurred! Please specify additional parameter: thumbprint_sha256", two or more certificates in your account share the same serial number. Retry the request with the SHA-256 thumbprint appended as a query parameter: ?thumbprint_sha256={thumbprint}. Use the thumbprint value collected in Step 1.Successful response (204 No Content):
A 204 No Content response confirms the revocation was accepted. There is no response body. Record the serial number as successfully revoked and proceed to the next certificate in your list. Repeat this step for each serial number collected in Step 1.
Step 3: Verify revocation status
After revoking each certificate, confirm that its status has updated to revoked in Trust Lifecycle Manager. This is especially important for audit trails and compliance reporting. To do this, call the certificate details endpoint for each revoked certificate using its serial number. If the revocation has propagated successfully, the status field in the response will show revoked, and the revocation object will contain the reason code you specified along with a revocation_date timestamp. If the status still shows issued, the revocation may not have propagated yet. Wait a few seconds and retry the details request until you see the updated status. Repeat this verification for each certificate in your list to confirm the bulk operation is complete.
Request:
curl -X GET "https://demo.one.digicert.com/mpki/api/v1/certificate/SERIAL_NUMBER" \
-H "x-api-key: SERVICE_API_TOKEN" \
| jq '.'import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com"
SERVICE_API_TOKEN = "SERVICE_API_TOKEN"
# Path parameter
SERIAL_NUMBER = "SERIAL_NUMBER"
response = requests.get(
f"{BASE_URL}/mpki/api/v1/certificate/{SERIAL_NUMBER}",
headers={"x-api-key": SERVICE_API_TOKEN}
)
print(f"Status Code: {response.status_code}")
print(response.json())import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ApiExample {
public static void main(String[] args) throws Exception {
// Configuration
String baseUrl = "https://demo.one.digicert.com";
String serviceApiToken = "SERVICE_API_TOKEN";
// Path parameter
String serialNumber = "SERIAL_NUMBER";
// Send request
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/mpki/api/v1/certificate/" + serialNumber))
.header("x-api-key", serviceApiToken)
.GET()
.build();
HttpResponse<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status Code: " + response.statusCode());
System.out.println(response.body());
}
}using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
// Configuration
string baseUrl = "https://demo.one.digicert.com";
string serviceApiToken = "SERVICE_API_TOKEN";
// Path parameter
string serialNumber = "SERIAL_NUMBER";
// Send request
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", serviceApiToken);
var response = await client.GetAsync($"{baseUrl}/mpki/api/v1/certificate/{serialNumber}");
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Status Code: {(int)response.StatusCode}");
Console.WriteLine(responseBody);
}
}Replace {serial_number} in the endpoint path with the serial number of the certificate you revoked in Step 2. If the serial number is not unique, include the SHA-256 thumbprint as a query parameter using ?thumbprint_sha256={thumbprint} to ensure the correct certificate is referenced. To know more, see Serial number collision in the Common errors and solutions section.
Successful response (200 OK):
{
"profile": {
"id": "7f606454-e011-4cfb-af90-5a13232bbd8f",
"name": "Example Certificate Profile"
},
"seat": {
"seat_id": "some-seat-id@example.com"
},
"business_unit": {
"id": "12d3c45e-d3e6-46fc-7f48-91030fa5cac6",
"name": "Example-BU1"
},
"account": {
"id": "f3f99122-1234-567a-919b-5f692216ff85"
},
"certificate": "...(Base64-encoded certificate)...",
"ica": {
"id": "444FFF3A03C12BCEF463B5D2EF01985F"
},
"common_name": "host1.example.com",
"status": "revoked",
"serial_number": "0F6F55555658A989F0F463A4DA0D807F",
"thumbprint": "FE0EFDC64AA55565556152850D2B84B46EF433444A28970F6A2A7F510A7A8880",
"valid_from": "2025-03-01T00:00:00Z",
"valid_to": "2026-03-08T23:59:59Z",
"notes": "Some example notes about the valid certificate",
"revocation": {
"reason": "privilege_withdrawn",
"revocation_date": "2026-03-23T00:00:00Z",
"comments": ""
},
"template_name": "Template Name",
"issuing_ca_name": "Issuing CA Name",
"key_size": 2048,
"signature_algorithm": "RSA",
"subject": {
"common_name": "host1.example.com",
"organization_name": "Example Org",
"organization_units": "Example Org Unit",
"description": "Some example description",
"serial_number": "0F6F55555658A989F0F463A4DA0D807F",
"pseudonym": "Some example pseudonym",
"unique_identifier": "3a3aeb976040eed5c5d3bfb6143b7814",
"user_identifier": "Some example user identifier",
"domain_component": "Some example domain component",
"dn_qualifier": "Some example DN qualifier",
"unstructured_name": "Some example unstructured name",
"unstructured_address": "Some example unstructured address",
"street_address": "Some example street address",
"postal_code": 89714,
"locality": "Carson city",
"state": "NV",
"country": "US",
"email": "some@mail",
"title": "Some example title",
"given_name": "Some example given name",
"surname": "Some example surname",
"organization_identifier": "1.3.6.1.4.1.311.60.2.1.3"
},
"cert_security_rating": "Secure",
"security_rating_info": {
"security_rating": {
"encryption": [
{
"category_name": "Encryption",
"criteria_name": "AES256",
"value": "Strong",
"rating": {
"value": true,
"issue": "None"
},
"data": {
"key_size": 256,
"mode": "CBC"
}
}
]
},
"handshake_protocols": [
{
"cipher_algorithm": "ECDHE-RSA-AES256-GCM-SHA384",
"key_size": 256,
"protocol": "TLSv1.2"
}
],
"security_headers": {
"X-Content-Type-Options": "nosniff"
},
"http_response_headers": {
"Content-Security-Policy": "default-src 'self'"
},
"certificate_chain": {
"cert_chain_details": [
{
"cert_chain_id": 1,
"overall_status": "Valid",
"is_additional_cert_chain": false,
"is_valid_chain": true,
"cert_details": [
{
"status": "Active",
"expiration_date": 1672531199000,
"key_algorithm": "RSA",
"algorithm_type": "SHA256",
"issue_date": 1609459200000,
"issuer_DN": "CN=Example CA, O=Example Inc., C=US",
"subject": "CN=example.com, O=Example Inc., C=US",
"errors": []
}
]
}
]
},
"security_rating_score": "Secure",
"certificate_chain_score": "Secure",
"handshake_protocol_score": [
"Secure"
],
"security_headers_score": "Secure",
"http_response_headers_score": 10,
"protocol_ciphers": [
{
"protocol": "TLSv1.2",
"insecure_ciphers": [
{
"code": 1,
"forward_secrecy": false,
"kex_info": "RSA",
"name": "RC4",
"unsafe": 1
}
],
"weak_ciphers": [],
"safe_ciphers": [
{
"code": 156,
"forward_secrecy": true,
"kex_info": "ECDHE-RSA",
"name": "AES256-GCM-SHA384",
"unsafe": 0
}
],
"compressors": "DEFLATE",
"cipher_pref_order": "client",
"warnings": ""
}
],
"protocol_cipher_score": {
"protocols": [
"TLSv1.2",
"TLSv1.3"
],
"score": "Secure"
}
}
}
The response returns the full certificate record. Confirm that the status field is revoked. The revocation object nested in the response will contain the reason code you specified and the revocation_date timestamp. If the status field still shows issued, the revocation may not have happened yet. In such case, wait for a few seconds and retry. Repeat this verification for each certificate in your list to confirm the bulk operation is complete.
Common errors and solutions
For general API errors (authentication, rate limits), see Trust Lifecycle Manager API error reference.
Serial number collision
{
"errors": [
{
"code": "collision_error",
"message": "Collision occurred! Please specify additional parameter: thumbprint_sha256"
}
]
}
Two or more certificates in your account share the same serial number. Retry the revocation request with the SHA-256 thumbprint appended as a query parameter: PUT /mpki/api/v1/certificate/{serial_number}/revoke?thumbprint_sha256={thumbprint}. The thumbprint value was returned in the thumbprint field of the certificate search response in Step 1.
Certificate already revoked
If you attempt to revoke a certificate that is already in revoked status, the API may return an error indicating the certificate is not in a revocable state. Remove revoked certificates from your working list by filtering for status=issued in Step 1 before starting the revocation loop.
No certificates returned for CA and date range
If Step 1 returns an empty items array with "total": 0, it means no issued certificates match the search criteria you specified. In such case, verify that the issuing_ca_id is correct and the date range is properly formatted as YYYY-MM-DD_YYYY-MM-DD. Also note that the valid_from filter applies to the certificate’s validity start date. The backdated certificates issued within the selected time window may fall outside the specified range and therefore not appear in the results.
What’s next?
Now that you have bulk revoked certificates enrolled under the target profile, you can:
- Monitor certificate inventory: Run periodic searches using
profile_id={profile_id}&valid_from=2026-03-01_2026-03-08&status=issuedto confirm no active certificates remain under the affected profile. See Search certificates in the API reference. - Issue replacement certificates: If this revocation was part of a profile update rather than a security incident, use the certificate issuance endpoint (
POST /mpki/api/v1/certificate) to enroll new certificates under the updated profile. - Automate certificate lifecycle management: Configure automation profiles in Trust Lifecycle Manager to renew or replace certificates before expiry, reducing the risk of future bulk revocations. See Automate certificate lifecycle management for setup instructions.
- Export a revocation audit report: Use the reporting features in Trust Lifecycle Manager to generate an audit trail of the revocation event, including timestamps and reason codes, for compliance and incident documentation purposes.