--- title: "Bulk revoke multiple certificates" source_url: https://dev.digicert.com/trust-lifecycle-api/tutorials/bulk-certificate-revocation.html --- 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_from` and `valid_to`). * Revoke each certificate using its `serial_number` while specifying an appropriate `revocation_reason`. * Confirm that the status of each certificate has been updated to `revoked`. ## Before you begin - A DigiCert ONE account with DigiCert® Trust Lifecycle Manager access. - An API token with the following permissions: - `Certificates`: View certificates and certificate details in inventory. - `Revoke`: Revoke certificates. - The UUID of the certificate profile whose certificates you want to revoke (for example, `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. - The start and end dates of the issuance window you want to target, in `YYYY-MM-DD_YYYY-MM-DD` format (for example, `2026-03-01_2026-03-08` for a one-week period). - The serial number of each certificate is not required in advance. You will retrieve it in [Step 1](#step-1-search-certificates-by-profile-and-issuance-date). - `jq` installed for JSON formatting. Use `jq --version` to verify. > **Info** > > Trust Lifecycle Manager can only revoke certificates from CAs it has access to. For certificates issued by external CAs, ensure that an active CA connector is configured before proceeding. See [Connectors](https://dev.digicert.com/trust-lifecycle-api/api-reference.html) for setup instructions. ## 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 | > **Important** > > Using `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** ```bash 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 '.' ``` **python** ```python 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()) ``` **Java** ```java 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 response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println("Status Code: " + response.statusCode()); System.out.println(response.body()); } } ``` **C#** ```csharp 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. > **Tip** > > To narrow the results further, you can combine `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):** ```json { "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](#step-2-revoke-each-certificate). The thumbprint is required if a serial number collision occurs. ## Step 2: Revoke each certificate With the list of serial numbers from [Step 1](#step-1-search-certificates-by-profile-and-issuance-date), 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** ```bash 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 '.' ``` **python** ```python 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()) ``` **Java** ```java 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 response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println("Status Code: " + response.statusCode()); System.out.println(response.body()); } } ``` **C#** ```csharp 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](#revocation-reason-codes) from the reference table above. Use the same reason code for all certificates in the same incident. This makes your audit trail consistent. > **Important** > > If you receive a response with the message `"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](#step-1-search-certificates-by-profile-and-issuance-date). **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-1-search-certificates-by-profile-and-issuance-date). ## 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** ```bash curl -X GET "https://demo.one.digicert.com/mpki/api/v1/certificate/SERIAL_NUMBER" \ -H "x-api-key: SERVICE_API_TOKEN" \ | jq '.' ``` **python** ```python 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()) ``` **Java** ```java 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 response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println("Status Code: " + response.statusCode()); System.out.println(response.body()); } } ``` **C#** ```csharp 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](#serial-number-collision) in the Common errors and solutions section. **Successful response (200 OK):** ```json { "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](https://dev.digicert.com/md/get-started/error-handling-rate-limits.md). ### Serial number collision ```json { "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](#step-1-search-certificates-by-profile-and-issuance-date). ### 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](#step-1-search-certificates-by-profile-and-issuance-date) before starting the revocation loop. ### No certificates returned for CA and date range If [Step 1](#step-1-search-certificates-by-profile-and-issuance-date) 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=issued` to confirm no active certificates remain under the affected profile. See [Search certificates]( https://dev.digicert.com/trust-lifecycle-api/api-reference.html#/paths/mpki-api-v1-certificate-search/get) 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](https://docs.digicert.com/en/trust-lifecycle-manager/automate-management-of-certificates/managed-automation-solution.html) 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.