--- title: "Create and activate a custom certificate template" description: "Verify, create, and activate a custom certificate template through the API, then issue a certificate that conforms to it.\n" source_url: https://dev.digicert.com/digicert-private-ca-api/tutorials/create-activate-custom-certificate-template.html --- Certificate templates govern how your organization uses its private CA resources. They define the key types, signature algorithms, subject attributes, validity, and extensions that issued certificates must conform to. Managing templates through the API lets you version template definitions in source control, verify them before rollout, and promote consistent certificate profiles across your development, QA, staging, and production environments. In this tutorial, you will: - Retrieve the certificate template validation schema. - Verify a candidate template definition before creating it. - Create a custom certificate template. - Activate the template so it can be used for issuance. - Confirm which accounts can use the template. - Issue a certificate using the new template. ## Before you begin Before you begin, make sure you have: - A DigiCert® ONE account with DigiCert Private CA access. - API token with the following permissions: - `VIEW_CM_CA` - View the issuing CA and its account assignments - `VIEW_CM_CUSTOM_TEMPLATE` - View certificate templates and validation schemas - `MANAGE_CM_CUSTOM_TEMPLATE` - Create and activate custom certificate templates - `MANAGE_CM_END_ENTITY_CERT` - Issue end-entity certificates (required only for the final step) - An existing online intermediate CA in active status. Note its CA ID. To find one, call `GET /ca` and select an item with `cert_type` set to `intermediate`, `hosted_type` set to `online`, and `status` set to `active`. - The account ID that will use the template and CA. To find the accounts assigned to a CA, call `GET /ca/{id}` and inspect the `account_assignments` array. The `id` in each assignment is a DigiCert ONE account UUID. - jq installed for JSON formatting. Use `jq --version` to verify. - OpenSSL installed for CSR generation. Use `openssl version` to verify. The language examples use Python 3 with the `requests` package, Java 11 or later with Jackson Databind, or .NET 6 or later. You need only the tools for the example you choose. > **Note** > > Verifying a template with `POST /template/verify` requires no special permission. It only validates the template definition and does not create a resource. Creating and activating a template require `MANAGE_CM_CUSTOM_TEMPLATE`, and issuing a certificate requires `MANAGE_CM_END_ENTITY_CERT`. ## Endpoint overview | Method | Path | Description | |--------|------|-------------| | GET | `/template/schema` | Get template validation schema | | POST | `/template/verify` | Verify a template definition | | POST | `/template` | Create a custom template | | PUT | `/template/{id}/activate` | Activate the template | | GET | `/template/{id}` | Get the template and its account assignments | | POST | `/certificate` | Issue a certificate using the template | All paths in this tutorial are relative to the DigiCert Private CA API base URL: `https://demo.one.digicert.com/certificate-authority/api/v1`. ## Certificate types and issue types A template is scoped to a single certificate type. Set the `cert_type` field when you create the template: | `cert_type` | Description | |-------------|-------------| | `root` | Template for root CA certificates | | `intermediate` | Template for intermediate (issuing) CA certificates | | `end_entity` | Template for end-entity (leaf) certificates, such as TLS server or client authentication certificates | Each certificate type needs its own template. When you create an intermediate CA template in the user interface, you also choose an **Online** or **Unmanaged** category. The API contract exposes `category` as a string without documenting its accepted values, so use the current validation schema when creating intermediate CA templates through the API. This tutorial uses an `end_entity` template and does not send `category`. The `issue_types` field declares how certificates issued from the template are used, which in turn governs the allowed `key_usage` extensions: | `issue_types` value | Purpose | |---------------------|---------| | `server_authentication` | TLS server certificates | | `client_authentication` | Client authentication certificates | | `all` | No issue-type restriction | ## Step 1: Get the template validation schema Before you author a template, retrieve the validation schema. The schema describes supported fields and structural constraints for template definitions, helping you build the `template_json` body before submitting it to the verification endpoint. **Request:** **curl** ```bash curl -X GET "https://demo.one.digicert.com/certificate-authority/api/v1/template/schema" \ -H "x-api-key: " | jq '.schema | fromjson' ``` **python** ```python import json import requests # Configuration BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1" SERVICE_API_TOKEN = "" response = requests.get( f"{BASE_URL}/template/schema", headers={"x-api-key": SERVICE_API_TOKEN} ) print(f"Status Code: {response.status_code}") schema_json = response.json()["schema"] schema = json.loads(schema_json) print(json.dumps(schema, indent=2)) ``` **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.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; public class ApiExample { public static void main(String[] args) throws Exception { // Configuration String baseUrl = "https://demo.one.digicert.com/certificate-authority/api/v1"; String serviceApiToken = ""; // Send request HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/template/schema")) .header("x-api-key", serviceApiToken) .GET() .build(); HttpResponse response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println("Status Code: " + response.statusCode()); ObjectMapper mapper = new ObjectMapper(); JsonNode responseBody = mapper.readTree(response.body()); JsonNode schema = mapper.readTree(responseBody.get("schema").asText()); System.out.println( mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema) ); } } ``` **C#** ```csharp using System; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { // Configuration string baseUrl = "https://demo.one.digicert.com/certificate-authority/api/v1"; string serviceApiToken = ""; // Send request using var client = new HttpClient(); client.DefaultRequestHeaders.Add("x-api-key", serviceApiToken); var response = await client.GetAsync($"{baseUrl}/template/schema"); string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {(int)response.StatusCode}"); using JsonDocument wrapper = JsonDocument.Parse(responseBody); string schemaJson = wrapper.RootElement.GetProperty("schema").GetString() ?? throw new InvalidOperationException("Response does not contain a schema"); using JsonDocument schema = JsonDocument.Parse(schemaJson); Console.WriteLine(JsonSerializer.Serialize( schema.RootElement, new JsonSerializerOptions { WriteIndented = true } )); } } ``` **Successful response: `200 OK`** The response contains a `schema` field whose value is a serialized JSON Schema string. Each example deserializes that string and prints the resulting JSON Schema object. Use the schema to identify supported fields and constraints for `key_gen`, `signature_algorithm`, `subject`, `validity`, and `extensions`. The API can also apply request context and server-side defaults that are not represented by the standalone schema, so use `POST /template/verify` in Step 2 as the final compatibility check. You do not need to save a value from this response. The following is a minimal `end_entity` template body you can use as a starting point. It allows RSA and ECDSA keys, permits the subject attributes used in this tutorial, sets a validity range, and includes key usage, extended key usage, and SAN requirements for TLS server authentication. Use it for the `template` field in Step 2 and the `template_json` field in Step 3, then expand it using the schema. ```json { "key_gen": { "enabled": true, "key_type": { "allowed_types": ["rsa", "ecdsa"], "default_key_type": "rsa" }, "rsa_key_size": { "min_bits": 2048, "max_bits": 4096, "default_bits": 2048 }, "ecdsa_curve": { "allowed_curves": ["P-256", "P-384"], "default_curve": "P-256" } }, "issue_types": ["server_authentication"], "signature_algorithm": { "allowed_algorithms": ["sha256WithRSA", "sha384WithRSA", "sha256WithECDSA", "match_issuer"], "default_algorithm": "match_issuer" }, "subject": { "attributes": [ { "type": "common_name", "include": "optional", "encoding": "auto", "allowed_source": ["csr", "user_supplied"] }, { "type": "organization_name", "include": "optional", "encoding": "auto", "allowed_source": ["csr", "user_supplied"] }, { "type": "country", "include": "optional", "encoding": "auto", "allowed_source": ["csr", "user_supplied"] } ] }, "validity": { "min_duration": { "value": 1, "unit": "days" }, "max_duration": { "value": 1, "unit": "years" }, "default_duration": { "value": 90, "unit": "days" } }, "extensions": { "key_usage": { "critical": true, "required_usages": { "rsa": ["digital_signature", "key_encipherment"], "ecdsa": ["digital_signature", "key_agreement"] } }, "extended_key_usage": { "critical": false, "include": "optional", "required_usages": [ { "oid": "", "name": "server_authentication" } ] }, "san": { "critical": false, "dns_name": { "include": "yes", "auto_include_cn": "top", "allowed_source": ["csr", "user_supplied"] } } } } ``` Save this object as `template.json`. The `issue_types` block also appears as a top-level `issue_types` field on the create request in Step 3. Keep the two fields consistent. ## Step 2: Verify the template definition Verify your candidate template before creating it. This is the key advantage of managing templates through the API: you can validate a template definition in a pipeline (as a pull-request check, for example) and catch structural or policy errors before the template ever reaches your CA. A successful verification returns `204 No Content` and creates nothing. **Request:** **curl** ```bash TEMPLATE_JSON=$(jq -c '.' template.json) jq -n --arg template "$TEMPLATE_JSON" '{template: $template}' | \ curl --fail-with-body --include \ -X POST "https://demo.one.digicert.com/certificate-authority/api/v1/template/verify" \ -H "x-api-key: " \ -H "Content-Type: application/json" \ --data-binary @- ``` **python** ```python import json from pathlib import Path import requests # Configuration BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1" SERVICE_API_TOKEN = "" # Request details template_body = json.loads(Path("template.json").read_text(encoding="utf-8")) template_json = json.dumps(template_body, separators=(",", ":")) payload = { "template": template_json, } response = requests.post( f"{BASE_URL}/template/verify", headers={ "x-api-key": SERVICE_API_TOKEN, "Content-Type": "application/json" }, json=payload ) print(f"Status Code: {response.status_code}") ``` **Java** ```java import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; 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/certificate-authority/api/v1"; String serviceApiToken = ""; // Request details String templateJson = Files.readString(Path.of("template.json")); // Build payload ObjectMapper mapper = new ObjectMapper(); ObjectNode payload = mapper.createObjectNode(); payload.put("template", templateJson); // Send request HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/template/verify")) .header("x-api-key", serviceApiToken) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(payload))) .build(); HttpResponse response = client.send( request, HttpResponse.BodyHandlers.ofString() ); System.out.println("Status Code: " + response.statusCode()); } } ``` **C#** ```csharp using System; using System.IO; 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/certificate-authority/api/v1"; string serviceApiToken = ""; // Request details string templateJson = await File.ReadAllTextAsync("template.json"); // Build payload var payload = new JsonObject { ["template"] = templateJson }; // 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.PostAsync($"{baseUrl}/template/verify", content); Console.WriteLine($"Status Code: {(int)response.StatusCode}"); } } ``` The API defines `template` as a string. Each example reads `template.json` and serializes that JSON object as the string value of `template`. Use the same serialized value for `template_json` in Step 3. For fields not used in this tutorial, use the validation schema returned in Step 1. The curl example includes response headers so you can see the `204` status even though the response has no body. It also uses `--fail-with-body`, which returns a nonzero exit code for HTTP errors while preserving the API error response. This behavior makes the command suitable for a CI check. **Successful response (204 No Content):** A `204` status with no body indicates the template definition is valid. If the definition is invalid, the API returns an error describing the problem. See [Common errors and solutions](#common-errors-and-solutions). Fix any reported issues and re-run this step until it succeeds before moving on. ## Step 3: Create the custom template Create the template with the definition you just verified. Include the `accounts` array to assign the template to one or more accounts that will be allowed to use it for issuance. This step returns the template `id` that you will reference in every subsequent step. **Request:** **curl** ```bash TEMPLATE_JSON=$(jq -c '.' template.json) jq -n \ --arg template_json "$TEMPLATE_JSON" \ --arg account_id "" \ '{ name: "TLS Server Authentication", cert_type: "end_entity", issue_types: ["server_authentication"], template_json: $template_json, accounts: [{id: $account_id}], active: false }' | \ curl -X POST "https://demo.one.digicert.com/certificate-authority/api/v1/template" \ -H "x-api-key: " \ -H "Content-Type: application/json" \ --data-binary @- | jq '.' ``` **python** ```python import json from pathlib import Path import requests # Configuration BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1" SERVICE_API_TOKEN = "" # Request details ACCOUNT_ID = "" template_body = json.loads(Path("template.json").read_text(encoding="utf-8")) template_json = json.dumps(template_body, separators=(",", ":")) payload = { "name": "TLS Server Authentication", "cert_type": "end_entity", "issue_types": ["server_authentication"], "template_json": template_json, "accounts": [{"id": ACCOUNT_ID}], "active": False, } response = requests.post( f"{BASE_URL}/template", headers={ "x-api-key": SERVICE_API_TOKEN, "Content-Type": "application/json" }, json=payload ) 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 java.nio.file.Files; import java.nio.file.Path; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; 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/certificate-authority/api/v1"; String serviceApiToken = ""; // Request details String templateJson = Files.readString(Path.of("template.json")); String accountId = ""; // Build payload ObjectMapper mapper = new ObjectMapper(); ObjectNode payload = mapper.createObjectNode(); payload.put("name", "TLS Server Authentication"); payload.put("cert_type", "end_entity"); ArrayNode issueTypesArray = mapper.createArrayNode(); issueTypesArray.add("server_authentication"); payload.set("issue_types", issueTypesArray); payload.put("template_json", templateJson); ArrayNode accountsArray = mapper.createArrayNode(); ObjectNode account = mapper.createObjectNode(); account.put("id", accountId); accountsArray.add(account); payload.set("accounts", accountsArray); payload.put("active", false); // Send request HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/template")) .header("x-api-key", serviceApiToken) .header("Content-Type", "application/json") .POST(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.IO; 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/certificate-authority/api/v1"; string serviceApiToken = ""; // Request details string templateJson = await File.ReadAllTextAsync("template.json"); string accountId = ""; // Build payload var payload = new JsonObject { ["name"] = "TLS Server Authentication", ["cert_type"] = "end_entity", ["issue_types"] = new JsonArray { "server_authentication" }, ["template_json"] = templateJson, ["accounts"] = new JsonArray { new JsonObject { ["id"] = accountId } }, ["active"] = false }; // 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.PostAsync($"{baseUrl}/template", content); string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {(int)response.StatusCode}"); Console.WriteLine(responseBody); } } ``` Set `active` to `false` so you can review the template before enabling it, then activate it explicitly in the next step. Set `accounts` to the account IDs that should be able to issue with this template. Each entry is an object with an `id` field. **Example successful response (201 Created, abridged):** ```json { "id": "01234567-89ab-cdef-0123-456789abcdef", "name": "TLS Server Authentication", "cert_type": "end_entity", "issue_types": ["server_authentication"], "active": false, "accounts": [ { "id": "0bc4edb9-fbf6-4571-8cf6-33773a296a77", "name": "Demo account" } ] } ``` From the response, save the template `id`. You will need this value in Step 4 to activate the template, in Step 5 to confirm account assignment, and in Step 6 to issue a certificate. ## Step 4: Activate the template A template must be active before a CA can issue certificates with it. Activate the template you created using its `id`. **Request:** **curl** ```bash curl -X PUT "https://demo.one.digicert.com/certificate-authority/api/v1/template//activate" \ -H "x-api-key: " | jq '.' ``` **python** ```python import requests # Configuration BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1" SERVICE_API_TOKEN = "" TEMPLATE_ID = "" response = requests.put( f"{BASE_URL}/template/{TEMPLATE_ID}/activate", 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/certificate-authority/api/v1"; String serviceApiToken = ""; String templateId = ""; // Send request HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/template/" + templateId + "/activate")) .header("x-api-key", serviceApiToken) .method("PUT", HttpRequest.BodyPublishers.noBody()) .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/certificate-authority/api/v1"; string serviceApiToken = ""; string templateId = ""; // Send request using var client = new HttpClient(); client.DefaultRequestHeaders.Add("x-api-key", serviceApiToken); var response = await client.PutAsync($"{baseUrl}/template/{templateId}/activate", null); string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {(int)response.StatusCode}"); Console.WriteLine(responseBody); } } ``` Replace `` with the template ID from Step 3. **Example successful response (200 OK, abridged):** ```json { "id": "01234567-89ab-cdef-0123-456789abcdef", "active": true } ``` The template is now active. Certificate requests that reference this template will be accepted, provided the requesting account is assigned to the template. You confirm that assignment in the next step. ## Step 5: Confirm account assignment Get the template and verify its `accounts` array. This confirms that the account assignment you specified when creating the template was applied. **Request:** **curl** ```bash curl -X GET "https://demo.one.digicert.com/certificate-authority/api/v1/template/" \ -H "x-api-key: " | jq '.' ``` **python** ```python import requests # Configuration BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1" SERVICE_API_TOKEN = "" TEMPLATE_ID = "" response = requests.get( f"{BASE_URL}/template/{TEMPLATE_ID}", 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/certificate-authority/api/v1"; String serviceApiToken = ""; String templateId = ""; // Send request HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/template/" + templateId)) .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/certificate-authority/api/v1"; string serviceApiToken = ""; string templateId = ""; // Send request using var client = new HttpClient(); client.DefaultRequestHeaders.Add("x-api-key", serviceApiToken); var response = await client.GetAsync($"{baseUrl}/template/{templateId}"); string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {(int)response.StatusCode}"); Console.WriteLine(responseBody); } } ``` Replace `` with the template ID from Step 3. **Example successful response (200 OK, abridged):** ```json { "id": "01234567-89ab-cdef-0123-456789abcdef", "name": "TLS Server Authentication", "active": true, "accounts": [ { "id": "0bc4edb9-fbf6-4571-8cf6-33773a296a77", "name": "Demo account" } ] } ``` Confirm that `active` is `true` and that the account you plan to issue from appears in `accounts`. If it does not, update the template's account assignment with `PUT /template/{id}` before issuing. The account you use in Step 6 must be assigned to both the template and the issuing CA. ## Step 6: Issue a certificate using the template Issue an end-entity certificate that uses your new template. Referencing the template by `template_id` applies all of the constraints you defined to the issued certificate: key type, signature algorithm, subject attributes, validity, and extensions. First, generate a CSR and private key with OpenSSL: ```bash openssl req -new -newkey rsa:2048 -nodes \ -keyout server.key \ -out server.csr \ -subj "/CN=server.example.internal/O=Example Corp/C=US" \ -addext "subjectAltName=DNS:server.example.internal" ``` > **Warning** > > The `-nodes` option writes `server.key` without passphrase encryption. For production use, omit `-nodes` to encrypt the key, or restrict access to an unencrypted key with strict file permissions, such as `chmod 600 server.key`. Keep `server.key` private, and pass the PEM contents of `server.csr` as the `csr` field below. To inspect and verify the CSR before submitting it, run `openssl req -in server.csr -noout -verify -text`. **Request:** **curl** ```bash jq -n \ --arg ca_id "" \ --arg template_id "" \ --arg account_id "" \ --rawfile csr server.csr \ '{ issuer: {id: $ca_id}, template_id: $template_id, csr: $csr, account_id: $account_id, validity: {duration_unit: "days", duration_value: 90} }' | \ curl -X POST "https://demo.one.digicert.com/certificate-authority/api/v1/certificate" \ -H "x-api-key: " \ -H "Content-Type: application/json" \ --data-binary @- | jq '.' ``` **python** ```python from pathlib import Path import requests # Configuration BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1" SERVICE_API_TOKEN = "" # Request details CA_ID = "" TEMPLATE_ID = "" ACCOUNT_ID = "" csr = Path("server.csr").read_text(encoding="utf-8") payload = { "issuer": {"id": CA_ID}, "template_id": TEMPLATE_ID, "csr": csr, "account_id": ACCOUNT_ID, "validity": { "duration_unit": "days", "duration_value": 90, }, } response = requests.post( f"{BASE_URL}/certificate", headers={ "x-api-key": SERVICE_API_TOKEN, "Content-Type": "application/json" }, json=payload ) 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 java.nio.file.Files; import java.nio.file.Path; 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/certificate-authority/api/v1"; String serviceApiToken = ""; // Request details String caId = ""; String templateId = ""; String csr = Files.readString(Path.of("server.csr")); String accountId = ""; // Build payload ObjectMapper mapper = new ObjectMapper(); ObjectNode payload = mapper.createObjectNode(); ObjectNode issuer = mapper.createObjectNode(); issuer.put("id", caId); payload.set("issuer", issuer); payload.put("template_id", templateId); payload.put("csr", csr); payload.put("account_id", accountId); ObjectNode validity = mapper.createObjectNode(); validity.put("duration_unit", "days"); validity.put("duration_value", 90); payload.set("validity", validity); // Send request HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(baseUrl + "/certificate")) .header("x-api-key", serviceApiToken) .header("Content-Type", "application/json") .POST(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.IO; 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/certificate-authority/api/v1"; string serviceApiToken = ""; // Request details string caId = ""; string templateId = ""; string csr = await File.ReadAllTextAsync("server.csr"); string accountId = ""; // Build payload var payload = new JsonObject { ["issuer"] = new JsonObject { ["id"] = caId }, ["template_id"] = templateId, ["csr"] = csr, ["account_id"] = accountId, ["validity"] = new JsonObject { ["duration_unit"] = "days", ["duration_value"] = 90 } }; // 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.PostAsync($"{baseUrl}/certificate", content); string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Status Code: {(int)response.StatusCode}"); Console.WriteLine(responseBody); } } ``` Set `issuer.id` to the CA ID you noted in the prerequisites and `template_id` to the template ID from Step 3. Each example reads the PEM-encoded CSR from `server.csr`. Set `account_id` to the account you confirmed in Step 5. The `validity` object requests 90 days, which falls within the template's allowed range. **Example successful response (200 OK, abridged):** ```json { "id": "01234567-89ab-cdef-0123-456789abcdef", "serial_number": "0a1b2c3d4e5f6789" } ``` From the response, save the certificate `id`. The certificate now conforms to the profile enforced by your template. Retrieving and downloading the issued certificate are outside the scope of this tutorial. Those follow-on operations use `GET /certificate/{id}` and require the `VIEW_CM_CERTIFICATE` permission. ## Common errors and solutions For general API errors (authentication, rate limits), see the [DigiCert API error reference](https://dev.digicert.com/md/get-started/error-handling-rate-limits.md). ### Template verification or creation fails The definition submitted to `POST /template/verify` or `POST /template` does not conform to the validation schema. Common causes include: - A required block, such as `signature_algorithm`, `subject`, or `validity`, is missing. - An enumerated value is not supported, such as an unrecognized entry in `allowed_types`, `allowed_algorithms`, or `allowed_curves`. - A default value is not part of its allowed set, such as a `default_algorithm` that is not listed in `allowed_algorithms`. - The template body is not well-formed JSON. Compare your `template_json` against the schema from Step 1, fix the reported issue, and re-run the verification. ### Certificate issuance fails Inspect the returned `errors` array. Confirm that: - `issuer.id` identifies an active online intermediate CA. - The account in `account_id` is assigned to both the CA and the template. - `template_id` matches the ID returned in Step 3. - The template is active. - The CSR subject, key, and extensions conform to the template. - The requested validity is within the template's allowed range. ### Authentication and permission errors A `401` or `403` response means the API token is missing, invalid, or lacks a required permission, such as `MANAGE_CM_CUSTOM_TEMPLATE` to create or activate a template, or `MANAGE_CM_END_ENTITY_CERT` to issue a certificate. Confirm your token and its permissions against [Before you begin](#before-you-begin). For the full list of authentication and rate-limit errors, see the [DigiCert API error reference](https://dev.digicert.com/md/get-started/error-handling-rate-limits.md). ## Verify the results The workflow is complete when: - `POST /template/verify` returns `204 No Content`. - `POST /template` returns a template ID. - `GET /template/{id}` returns `active` set to `true` and includes your account ID in `accounts`. - `POST /certificate` returns a certificate ID. The certificate ID confirms that issuance succeeded. Retrieving and inspecting the issued certificate is an optional follow-on operation and requires the `VIEW_CM_CERTIFICATE` permission, which is not required to complete this tutorial. ## Next steps Now that you have created and activated a custom certificate template, you can: - **Manage templates as code**: Store your `template_json` definitions in source control and run `POST /template/verify` as a pull-request check so certificate policy changes are reviewed and validated before they reach your CA. - **Promote templates across environments**: Reuse the same verified definition to create matching templates in demo and production, keeping certificate profiles consistent across development, QA, staging, and production. - **Automate the full issuance lifecycle**: Use the certificate ID with `GET /certificate/{id}` and the end-entity lifecycle endpoints to inspect and manage certificates programmatically. Add the permissions required by each follow-on operation to your API token. - **Update or deactivate templates**: Use `PUT /template/{id}` to revise a template and `PUT /template/{id}/deactivate` to retire it when a certificate profile is no longer needed.