Create and activate a custom certificate template
18 minute read
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:
API token with the following permissions:
VIEW_CM_CA- View the issuing CA and its account assignmentsVIEW_CM_CUSTOM_TEMPLATE- View certificate templates and validation schemasMANAGE_CM_CUSTOM_TEMPLATE- Create and activate custom certificate templatesMANAGE_CM_END_ENTITY_CERT- Issue end-entity certificates (required only for the final step)
GET /ca and select an item with cert_type set to intermediate, hosted_type set to online, and status set to active.GET /ca/{id} and inspect the account_assignments array. The id in each assignment is a DigiCert ONE account UUID.jq --version to verify.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.
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 -X GET "https://demo.one.digicert.com/certificate-authority/api/v1/template/schema" \
-H "x-api-key: <api-token>" | jq '.schema | fromjson'import json
import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1"
SERVICE_API_TOKEN = "<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))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 = "<api-token>";
// Send request
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/template/schema"))
.header("x-api-key", serviceApiToken)
.GET()
.build();
HttpResponse<String> 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)
);
}
}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 = "<api-token>";
// 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.
{
"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:
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: <api-token>" \
-H "Content-Type: application/json" \
--data-binary @-import json
from pathlib import Path
import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1"
SERVICE_API_TOKEN = "<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}")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 = "<api-token>";
// 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<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status Code: " + response.statusCode());
}
}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 = "<api-token>";
// 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. 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:
TEMPLATE_JSON=$(jq -c '.' template.json)
jq -n \
--arg template_json "$TEMPLATE_JSON" \
--arg account_id "<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: <api-token>" \
-H "Content-Type: application/json" \
--data-binary @- | jq '.'import json
from pathlib import Path
import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1"
SERVICE_API_TOKEN = "<api-token>"
# Request details
ACCOUNT_ID = "<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())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 = "<api-token>";
// Request details
String templateJson = Files.readString(Path.of("template.json"));
String accountId = "<account-id>";
// 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<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status Code: " + response.statusCode());
System.out.println(response.body());
}
}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 = "<api-token>";
// Request details
string templateJson = await File.ReadAllTextAsync("template.json");
string accountId = "<account-id>";
// 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):
{
"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 -X PUT "https://demo.one.digicert.com/certificate-authority/api/v1/template/<template-id>/activate" \
-H "x-api-key: <api-token>" | jq '.'import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1"
SERVICE_API_TOKEN = "<api-token>"
TEMPLATE_ID = "<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())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 = "<api-token>";
String templateId = "<template-id>";
// 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<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/certificate-authority/api/v1";
string serviceApiToken = "<api-token>";
string templateId = "<template-id>";
// 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 <template-id> with the template ID from Step 3.
Example successful response (200 OK, abridged):
{
"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 -X GET "https://demo.one.digicert.com/certificate-authority/api/v1/template/<template-id>" \
-H "x-api-key: <api-token>" | jq '.'import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1"
SERVICE_API_TOKEN = "<api-token>"
TEMPLATE_ID = "<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())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 = "<api-token>";
String templateId = "<template-id>";
// Send request
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/template/" + templateId))
.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/certificate-authority/api/v1";
string serviceApiToken = "<api-token>";
string templateId = "<template-id>";
// 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 <template-id> with the template ID from Step 3.
Example successful response (200 OK, abridged):
{
"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:
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"
-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:
jq -n \
--arg ca_id "<ca-id>" \
--arg template_id "<template-id>" \
--arg account_id "<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: <api-token>" \
-H "Content-Type: application/json" \
--data-binary @- | jq '.'from pathlib import Path
import requests
# Configuration
BASE_URL = "https://demo.one.digicert.com/certificate-authority/api/v1"
SERVICE_API_TOKEN = "<api-token>"
# Request details
CA_ID = "<ca-id>"
TEMPLATE_ID = "<template-id>"
ACCOUNT_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())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 = "<api-token>";
// Request details
String caId = "<ca-id>";
String templateId = "<template-id>";
String csr = Files.readString(Path.of("server.csr"));
String accountId = "<account-id>";
// 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<String> response = client.send(
request,
HttpResponse.BodyHandlers.ofString()
);
System.out.println("Status Code: " + response.statusCode());
System.out.println(response.body());
}
}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 = "<api-token>";
// Request details
string caId = "<ca-id>";
string templateId = "<template-id>";
string csr = await File.ReadAllTextAsync("server.csr");
string accountId = "<account-id>";
// 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):
{
"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.
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, orvalidity, is missing. - An enumerated value is not supported, such as an unrecognized entry in
allowed_types,allowed_algorithms, orallowed_curves. - A default value is not part of its allowed set, such as a
default_algorithmthat is not listed inallowed_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.ididentifies an active online intermediate CA.- The account in
account_idis assigned to both the CA and the template. template_idmatches 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. For the full list of authentication and rate-limit errors, see the DigiCert API error reference.
Verify the results
The workflow is complete when:
POST /template/verifyreturns204 No Content.POST /templatereturns a template ID.GET /template/{id}returnsactiveset totrueand includes your account ID inaccounts.POST /certificatereturns 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_jsondefinitions in source control and runPOST /template/verifyas 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 andPUT /template/{id}/deactivateto retire it when a certificate profile is no longer needed.