PKI external CA secrets engine API
Enterprise
Appropriate Vault EnterpriseĀ license required
Use the PKI External CA secrets engine API to maintain centralized certificate management capabilities while using leaf certificates from external, ACME-compliant Certificate Authorities (CAs) including public CAs like Let's Encrypt and DigiCert.
Overview
The PKI External CA secrets engine provides a bridge between Vault and external ACME-compliant Certificate Authorities. It manages the complete certificate lifecycle including:
- ACME account management: Register and manage accounts with external ACME servers.
- Role-based access: Define roles that control which domains ACME accounts can request and how the CA issues certificates.
- Automated certificate acquisition: Handle the ACME protocol workflow automatically, including challenge fulfillment.
- Certificate caching: Retrieve issued certificates previously obtained through an ACME identifier workflow.
Key concepts
- ACME account: An account registered with an external ACME server (e.g., Let's Encrypt).
- Role: Policies that define certificate issuance, including allowed domains and challenge types.
- Order: An in-progress certificate request using the ACME workflow.
- Challenge: ACME validation method (HTTP-01, DNS-01, or TLS-ALPN-01) that proves domain ownership.
ACME account management
ACME accounts represent registered accounts with external ACME servers. Each account associates with a private key and email contacts for notifications.
Create ACME account
Create a new ACME account by registering with the specified ACME directory server.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/acme-account/:name |
Parameters
name(string: <required>)- A unique string used to reference the ACME account in roles.directory_url(string: <required>)- The ACME directory URL (e.g.,https://acme-v02.api.letsencrypt.org/directoryfor Let's Encrypt production).email_contacts(array<string>: <required>)- List of email addresses for account notifications. You must provide at least one email.key_type(string: "ec-256")- The key type to generate for the account key. Must be one of:eab_kid(string: "")- External Account Binding (EAB) key identifier. Some ACME servers require an EAB key identifier for account registration.eab_key(string: "")- External Account Binding (EAB) key (urlbase64-encoded). Some ACME servers require an EAB key for account registration.trusted_ca(string: "")- PEM-encoded trusted CA certificates for validating the TLS certificate provided by the ACME server. Usetrusted_cafor private ACME servers with custom CAs.default_nameserver(string: "")- Address of a DNS nameserver (hostorhost:port) to append to the resolver list when verifying DNS-01 challenge propagation for DNS providers that do not specify a nameserver. Leavingdefault_nameserverempty tells Vault not to append additional resolvers beyond the authoritative primary NS.
Sample payload
{
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email_contacts": ["admin@example.com", "security@example.com"],
"key_type": "ec-256"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account/letsencrypt-prod
Sample response
{
"data": {
"name": "letsencrypt-prod",
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email_contacts": ["admin@example.com", "security@example.com"],
"active_key_version": 0,
"account_keys": {
"0": {
"key_type": "ec-256",
"key_version": 0,
"key_creation_date": "2026-02-24T20:00:00Z"
}
},
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Import ACME account
Import an existing ACME account using a previously registered account key.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/acme-account/:name/import |
Parameters
name(string: <required>)- The name for the imported ACME account.account_key(string: <required>)- PEM-encoded private key for the existing ACME account.directory_url(string: <required>)- The ACME directory URL where you registered the account.trusted_ca(string: "")- PEM-encoded trusted CA certificates for the ACME server.default_nameserver(string: "")- Address of a DNS nameserver (hostorhost:port) to append to the resolver list when verifying DNS-01 challenge propagation for DNS providers that do not specify a nameserver. Leavingdefault_nameserverempty tells Vault not to append additional resolvers beyond the authoritative primary NS.
Sample payload
{
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"account_key": "-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account/imported-account/import
Sample response
{
"data": {
"name": "imported-account",
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email_contacts": ["admin@example.com"],
"active_key_version": 0,
"account_keys": {
"0": {
"key_type": "ec-256",
"key_version": 0,
"key_creation_date": "2026-02-24T20:00:00Z"
}
},
"trusted_ca": "",
"default_nameserver": "",
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Read ACME account
Retrieve information about an ACME account.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/config/acme-account/:name |
Parameters
name(string: <required>)- The name of the ACME account to read.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account/letsencrypt-prod
Sample response
{
"data": {
"account_keys": {
"0": {
"key_creation_date": "2026-06-29T12:28:24Z",
"key_type": "ec-256",
"key_version": 0
}
},
"active_key_version": 0,
"creation_date": "2026-06-29T12:28:24Z",
"default_nameserver": "",
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email_contacts": [
"test@example.com"
],
"last_updated_date": "2026-06-29T12:28:24Z",
"name": "letsencrypt-prod",
"trusted_ca": ""
}
}
Update ACME account
Update the trusted CA certificates for an ACME account. You can only update
fields trusted_ca and default_nameserver after account creation.
| Method | Path |
|---|---|
PUT | /{plugin_mount_path}/config/acme-account/:name |
Parameters
name(string: <required>)- The name of the ACME account to update.trusted_ca(string: "")- PEM-encoded trusted CA certificates for the ACME server.default_nameserver(string: "")- Address of a DNS nameserver (hostorhost:port) to use when verifying DNS-01 challenge propagation for DNS providers that do not specify a nameserver. Set to an empty string to clear a previously configured nameserver.
Sample payload
{
"trusted_ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request PUT \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account/letsencrypt-prod
Sample response
{
"data": {
"account_keys": {
"0": {
"key_creation_date": "2026-06-29T12:28:24Z",
"key_type": "ec-256",
"key_version": 0
}
},
"active_key_version": 0,
"creation_date": "2026-06-29T12:28:24Z",
"default_nameserver": "",
"directory_url": "https://acme-v02.api.letsencrypt.org/directory",
"email_contacts": [
"test@example.com"
],
"last_updated_date": "2026-06-29T12:48:24Z",
"name": "letsencrypt-prod",
"trusted_ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}
}
Delete ACME account
Delete and deactivate an ACME account with the ACME server. You cannot delete the account if any roles reference it.
| Method | Path |
|---|---|
DELETE | /{plugin_mount_path}/config/acme-account/:name |
Parameters
name(string: <required>)- The name of the ACME account to delete.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request DELETE \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account/letsencrypt-prod
List ACME accounts
List all configured ACME accounts.
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/config/acme-account |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account
Sample response
{
"data": {
"keys": ["letsencrypt-prod", "letsencrypt-staging", "digicert"]
}
}
Rotate ACME account key
Generate a new key pair and perform an ACME key change operation with the upstream server to rotate an existing ACME account key.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/acme-account/:name/rotate-key |
Parameters
name(string: <required>)- The name of the ACME account.key_type(string: "ec-256")- The key type to generate for the new account key. Must be one of:force(bool: false)- Force rotation even if pending orders for the key exist. Use with caution.
Sample payload
{
"key_type": "ec-384"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/acme-account/letsencrypt-prod/rotate-key
Sample response
{
"data": {
"name": "letsencrypt-prod",
"old_active_key_version": 0,
"new_active_key_version": 1,
"key_type": "ec-384",
"rotated_at": "2026-02-24T20:45:00Z"
}
}
Role management
Roles define policies for certificate issuance, including which domains you can request and what challenge types you can use.
Create/update role
Create or update a role. You cannot modify acme_account_name after role creation.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/role/:name |
Parameters
name(string: <required>)- The name of the role.acme_account_name(string: <required>)- The name of the ACME account to use for certificate requests. You cannot change the account name after role creation.allowed_domains(array<string>: [])- List of domains the role accepts for certificates. You can use templates with ACL Path Templating (e.g.,{{identity.entity.name}}.example.com).allowed_domain_options(array<string>: [])- Configure how Vault interpretsallowed_domains:bare_domains- Allow domains without a subdomain prefix such asexample.com.subdomains- Allow subdomains of the specified domains.wildcards- Allow wildcard certificates.globs- Allow glob patterns in domain specifications.
allowed_challenge_types(array<string>: ["http-01", "dns-01", "tls-alpn-01"])- List of ACME challenge types clients can use:. Must be from the following:http-01- HTTP-01 challengedns-01- DNS-01 challengetls-alpn-01- TLS-ALPN-01 challenge
csr_generate_key_type(string: "ec-256")- Key type to generate when using the identifier workflow (not providing a CSR). Must be one of:csr_identifier_population(string: "cn_first")- Tell Vault how to populate the CSR from provided identifiers in the identifier workflow. Must be one of:
Sample payload
{
"acme_account_name": "letsencrypt-prod",
"allowed_domains": ["example.com", "*.example.com"],
"allowed_domain_options": ["bare_domains", "subdomains", "wildcards"],
"allowed_challenge_types": ["http-01", "dns-01"],
"csr_generate_key_type": "ec-256",
"csr_identifier_population": "cn_first"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server
Sample response
{
"data": {
"name": "web-server",
"acme_account_name": "letsencrypt-prod",
"allowed_domains": ["example.com", "*.example.com"],
"allowed_domain_options": ["bare_domains", "subdomains", "wildcards"],
"allowed_challenge_types": ["http-01", "dns-01"],
"csr_generate_key_type": "ec-256",
"csr_identifier_population": "cn_first",
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Read role
Retrieve information about a role.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/role/:name |
Parameters
name(string: <required>)- The name of the role to read.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server
Sample response
{
"data": {
"name": "web-server",
"acme_account_name": "letsencrypt-prod",
"allowed_domains": ["example.com", "*.example.com"],
"allowed_domain_options": ["bare_domains", "subdomains", "wildcards"],
"allowed_challenge_types": ["http-01", "dns-01"],
"csr_generate_key_type": "ec-256",
"csr_identifier_population": "cn_first",
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Delete role
Deletes a role. To delete roles with active orders, you must use the force option.
| Method | Path |
|---|---|
DELETE | /{plugin_mount_path}/role/:name |
Parameters
name(string: <required>)- The name of the role to delete.force(bool: false)- Force deletion even when active orders exist. Use with caution.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request DELETE \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server
List roles
List all configured roles.
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/role |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/role
Sample response
{
"data": {
"keys": ["web-server", "api-server", "internal-services"]
}
}
DNS provider configuration
DNS provider configurations enable automated DNS-01 challenge fulfillment for ACME certificate orders. Configure DNS providers to allow Vault to automatically create and delete DNS TXT records required for DNS-01 challenges.
List all DNS configurations
List all configured DNS providers across all provider types.
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/config/dns |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/config/dns
Sample response
{
"data": {
"keys": [
"aws-route53/production",
"azure-dns/staging"
],
"key_info": {
"aws-route53/production": {
"name": "production",
"type": "aws-route53"
},
"azure-dns/staging": {
"name": "staging",
"type": "azure-dns"
}
}
}
}
AWS Route53 DNS provider
Configure AWS Route53 as a DNS provider for DNS-01 ACME challenges.
Create/update AWS Route53 configuration
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/dns/aws-route53/:name |
Parameters
name(string: <required>)- Unique name for the DNS provider configuration.identifiers(array<string>: <required>)- List of DNS identifiers (domain names) the provider can manage. Supports wildcard patterns with leftmost asterisk (e.g.,*.example.com).access_key_id(string: "")- AWS access key ID for Route53 API access. The access key ID is optional if you use IAM role assumption or instance profiles.secret_access_key(string: "")- AWS secret access key for Route53 API access. The secret access key is optional if you use IAM role assumption or instance profiles.region(string: "us-east-1")- AWS region for Route53 operations.hosted_zone_id(string: "")- AWS Route53 hosted zone ID.external_id(string: "")- External ID for AWS STS AssumeRole.assume_role_arn(string: "")- AWS IAM role ARN to assume for Route53 operations.ttl(duration: "60s")- TTL for DNS TXT records used in DNS-01 challenges.nameserver(string: "")- DNS server address (hostorhost:port) to use when verifying DNS-01 challenge propagation for the configured provider. When empty, Vault falls back to thedefault_nameservervalue from the ACME account (if set) and the authoritative primary NS.
Sample payload
{
"identifiers": ["*.example.com", "example.com"],
"access_key_id": "AKIAIOSFODNN7EXAMPLE",
"secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"region": "us-east-1",
"hosted_zone_id": "Z3M3LMPEXAMPLE",
"ttl": "60s"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/aws-route53/production
Sample response
{
"data": {
"name": "production",
"type": "aws-route53",
"identifiers": ["*.example.com", "example.com"],
"region": "us-east-1",
"hosted_zone_id": "Z3M3LMPEXAMPLE",
"nameserver": "",
"ttl": 60,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Read AWS Route53 configuration
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/config/dns/aws-route53/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to read.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/aws-route53/production
Sample response
{
"data": {
"name": "production",
"type": "aws-route53",
"identifiers": ["*.example.com", "example.com"],
"region": "us-east-1",
"hosted_zone_id": "Z3M3LMPEXAMPLE",
"nameserver": "",
"ttl": 60,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Delete AWS Route53 configuration
Delete a DNS provider configuration. You cannot delete a configuration if any roles reference it.
| Method | Path |
|---|---|
DELETE | /{plugin_mount_path}/config/dns/aws-route53/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to delete.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request DELETE \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/aws-route53/production
List AWS Route53 configurations
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/config/dns/aws-route53 |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/aws-route53
Sample response
{
"data": {
"keys": ["production", "staging", "development"]
}
}
Azure DNS provider
Configure Azure DNS as a DNS provider for DNS-01 ACME challenges.
Create/update Azure DNS configuration
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/dns/azure-dns/:name |
Parameters
name(string: <required>)- Unique name for the DNS provider configuration.identifiers(array<string>: <required>)- List of DNS identifiers (domain names) the provider can manage. Supports wildcard patterns with leftmost asterisk (e.g.,*.example.com).zone_name(string: "")- Azure DNS zone name.client_id(string: "")- Azure service principal client ID. Optional for managed identities.client_secret(string: "")- Azure service principal client secret. Optional for managed identities.tenant_id(string: "")- Azure tenant ID. Optional for managed identities.subscription_id(string: "")- Azure subscription ID. Optional for managed identities.resource_group_name(string: "")- Azure resource group name containing the DNS zone.environment(string: "AzurePublic")- Azure cloud environment. Must be one of:ttl(duration: "60s")- TTL for DNS TXT records used in DNS-01 challenges.nameserver(string: "")- DNS server address (hostorhost:port) to use when verifying DNS-01 challenge propagation for this provider. When empty, Vault falls back to thedefault_nameservervalue from the ACME account (if set) and the authoritative primary NS.
Sample payload
{
"identifiers": ["*.example.com", "example.com"],
"zone_name": "example.com",
"client_id": "12345678-1234-1234-1234-123456789abc",
"client_secret": "your-client-secret",
"tenant_id": "87654321-4321-4321-4321-cba987654321",
"subscription_id": "abcdef01-2345-6789-abcd-ef0123456789",
"resource_group_name": "dns-resources",
"environment": "AzurePublic",
"ttl": "60s"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/azure-dns/production
Sample response
{
"data": {
"name": "production",
"type": "azure-dns",
"identifiers": ["*.example.com", "example.com"],
"zone_name": "example.com",
"tenant_id": "87654321-4321-4321-4321-cba987654321",
"subscription_id": "abcdef01-2345-6789-abcd-ef0123456789",
"resource_group_name": "dns-resources",
"environment": "AzurePublic",
"nameserver": "",
"ttl": 60,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Read Azure DNS configuration
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/config/dns/azure-dns/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to read.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/azure-dns/production
Sample response
{
"data": {
"name": "production",
"type": "azure-dns",
"identifiers": ["*.example.com", "example.com"],
"zone_name": "example.com",
"tenant_id": "87654321-4321-4321-4321-cba987654321",
"subscription_id": "abcdef01-2345-6789-abcd-ef0123456789",
"resource_group_name": "dns-resources",
"environment": "AzurePublic",
"nameserver": "",
"ttl": 60,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Delete Azure DNS configuration
| Method | Path |
|---|---|
DELETE | /{plugin_mount_path}/config/dns/azure-dns/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to delete.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request DELETE \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/azure-dns/production
List Azure DNS configurations
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/config/dns/azure-dns |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/azure-dns
Sample response
{
"data": {
"keys": ["production", "staging"]
}
}
Google Cloud DNS provider
Configure Google Cloud DNS as a DNS provider for DNS-01 ACME challenges.
Create/update Google Cloud DNS configuration
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/dns/google-cloud-dns/:name |
Parameters
name(string: <required>)- Unique name for the DNS provider configuration.identifiers(array<string>: <required>)- List of DNS identifiers (domain names) the provider can manage. Supports wildcard patterns with leftmost asterisk (e.g.,*.example.com).credentials(string: "")- GCP service account credentials as a JSON object. Credentials are optional if you use application default credentials or workload identity.project(string: "")- GCP project name.zone_name(string: "")- GCP DNS zone name.impersonate_service_account(string: "")- Service account email to impersonate.ttl(duration: "10s")- TTL for DNS TXT records used in DNS-01 challenges.nameserver(string: "")- DNS server address (hostorhost:port) to use when verifying DNS-01 challenge propagation for this provider. When empty, Vault falls back to thedefault_nameservervalue from the ACME account (if set) and the authoritative primary NS.
Sample payload
{
"identifiers": ["*.example.com", "example.com"],
"credentials": "<GCP service account file contents>",
"project": "my-project",
"zone_name": "example-com",
"ttl": "10s"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/google-cloud-dns/production
Sample response
{
"data": {
"name": "production",
"type": "google-cloud-dns",
"identifiers": ["*.example.com", "example.com"],
"project": "my-project",
"zone_name": "example-com",
"nameserver": "",
"ttl": 10,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Read Google Cloud DNS configuration
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/config/dns/google-cloud-dns/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to read.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/google-cloud-dns/production
Sample response
{
"data": {
"name": "production",
"type": "google-cloud-dns",
"identifiers": ["*.example.com", "example.com"],
"project": "my-project",
"zone_name": "example-com",
"nameserver": "",
"ttl": 10,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Delete Google Cloud DNS configuration
| Method | Path |
|---|---|
DELETE | /{plugin_mount_path}/config/dns/google-cloud-dns/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to delete.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request DELETE \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/google-cloud-dns/production
List Google Cloud DNS configurations
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/config/dns/google-cloud-dns |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/google-cloud-dns
Sample response
{
"data": {
"keys": ["production", "staging"]
}
}
RFC2136 DNS provider
Configure RFC2136 (Dynamic DNS Update) as a DNS provider for DNS-01 ACME challenges. RFC2136 enables dynamic DNS updates using TSIG authentication.
Create/update RFC2136 configuration
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/config/dns/rfc2136/:name |
Parameters
name(string: <required>)- Unique name for the DNS provider configuration.identifiers(array<string>: <required>)- List of DNS identifiers (domain names) the provider can manage. Supports wildcard patterns with leftmost asterisk (e.g.,*.example.com).nameserver(string: <required>)- DNS server address inhostorhost:portformat (e.g.,192.168.1.1:53or192.168.1.1). RFC2136 defaults to port 53 when you do not specify a port.tsig_key_name(string: "<required>")- TSIG key name for authenticated DNS updates.tsig_secret(string: "<required>")- TSIG secret (base64 encoded) for authenticated DNS updates.tsig_algorithm(string: "hmac-sha1")- TSIG algorithm. Must be one of:ttl(duration: "60s")- TTL for DNS TXT records used in DNS-01 challenges.
Sample payload
{
"identifiers": ["*.internal.example.com", "internal.example.com"],
"nameserver": "192.168.1.53:53",
"tsig_key_name": "vault-dns-update",
"tsig_secret": "c29tZS1iYXNlNjQtZW5jb2RlZC1zZWNyZXQ=",
"tsig_algorithm": "hmac-sha256",
"ttl": "60s"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/rfc2136/internal-dns
Sample response
{
"data": {
"name": "internal-dns",
"type": "rfc2136",
"identifiers": ["*.internal.example.com", "internal.example.com"],
"nameserver": "192.168.1.53:53",
"tsig_key_name": "vault-dns-update",
"tsig_algorithm": "hmac-sha256",
"ttl": 60,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Read RFC2136 configuration
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/config/dns/rfc2136/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to read.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/rfc2136/internal-dns
Sample response
{
"data": {
"name": "internal-dns",
"type": "rfc2136",
"identifiers": ["*.internal.example.com", "internal.example.com"],
"nameserver": "192.168.1.53:53",
"tsig_key_name": "vault-dns-update",
"tsig_algorithm": "hmac-sha256",
"ttl": 60,
"creation_date": "2026-02-24T20:00:00Z",
"last_updated_date": "2026-02-24T20:00:00Z"
}
}
Delete RFC2136 configuration
| Method | Path |
|---|---|
DELETE | /{plugin_mount_path}/config/dns/rfc2136/:name |
Parameters
name(string: <required>)- Name of the DNS provider configuration to delete.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request DELETE \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/rfc2136/internal-dns
List RFC2136 configurations
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/config/dns/rfc2136 |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/config/dns/rfc2136
Sample response
{
"data": {
"keys": ["internal-dns", "backup-dns"]
}
}
DNS provider testing
Test DNS provider workflow
Test a DNS provider configuration by creating a test TXT record and optionally cleaning it up. The testing endpoint validates that the DNS provider credentials and configuration are correct before using them in production certificate workflows.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/dns/test/workflow |
Parameters
identifier(string: <required>)- The DNS identifier (domain name) to test with. Must be a domain that the DNS provider configuration is authorized to manage.provider_type(string: <required>)- The DNS provider type. Must be one of:aws-route53- AWS Route53azure-dns- Azure DNSgoogle-cloud-dns- Google Cloud DNSrfc2136- RFC2136 (Dynamic DNS Update)
provider_name(string: <required>)- The name of the DNS provider configuration to test.omit_cleanup(bool: false)- Iftrue, skip cleanup of the test DNS record. Skipping cleanup is useful for manually validating record creation. If you skip automatic cleanup, you must manually delete the record or re-run the test withomit_cleanupset tofalseto clean it up.
Sample payload
{
"identifier": "test.example.com",
"provider_type": "aws-route53",
"provider_name": "production"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/dns/test/workflow
Sample response (success)
{
"data": {
"success": true,
"message": "DNS provider test successful: TXT record was created and cleaned up successfully",
"identifier": "test.example.com",
"provider_type": "aws-route53",
"provider_name": "production",
"record_name": "_acme-challenge.test.example.com."
}
}
Sample response (failure)
{
"errors": [
"Failed to create DNS TXT record: authentication failed"
],
"data": {
"success": false,
"message": "Failed to create DNS TXT record: authentication failed",
"identifier": "test.example.com",
"provider_type": "aws-route53",
"provider_name": "production",
"record_name": "_acme-challenge.test.example.com."
}
}
Certificate order workflow
The certificate order workflow manages the complete lifecycle of acquiring certificates from external ACME CAs.
Create new order
Create a new certificate order for automatic processing by the ACME workflow.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/role/:name/new-order |
Parameters
name(string: <required>)- The name of the role to use for the order.identifiers(array<string>: "")- List of domain names to include in the certificate (e.g.,["example.com", "www.example.com"]). You must provide eitheridentifiersorcsr, but not both.csr(string: "")- PEM-encoded Certificate Signing Request. Providecsrto use the CSR workflow. Leavecsrunset to use the identifier workflow and have Vault generate the key pair. You must provide eitheridentifiersorcsr, but not both.
Sample payload (identifier workflow)
{
"identifiers": ["example.com", "www.example.com"]
}
Sample payload (CSR workflow)
{
"csr": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/new-order
Sample response
{
"data": {
"order_id": "01936d8e-7c3a-7890-b123-456789abcdef"
}
}
Check order status
Retrieve the current status of a certificate order.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/role/:name/order/:order_id/status |
Parameters
name(string: <required>)- The name of the role.order_id(string: <required>)- The order ID previously returned when creating the order.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/order/01936d8e-7c3a-7890-b123-456789abcdef/status
Sample response
{
"data": {
"challenges": {
"test.example.com": [
{
"challenge_status": "pending",
"challenge_type": "tls-alpn-01",
"expires": "2026-07-06T12:45:23Z"
},
{
"challenge_status": "pending",
"challenge_type": "dns-01",
"expires": "2026-07-06T12:45:23Z"
},
{
"challenge_status": "pending",
"challenge_type": "http-01",
"expires": "2026-07-06T12:45:23Z"
}
]
},
"creation_date": "2026-06-29T08:45:22-04:00",
"csr": "",
"expires": "2026-07-06T12:45:23Z",
"identifiers": [
"test.example.com"
],
"last_error": "",
"last_update": "2026-06-29T08:45:24-04:00",
"next_work_date": "2026-06-29T09:45:24-04:00",
"order_status": "awaiting-challenge-fulfillment",
"role_name": "web-server",
"serial_number": ""
}
}
Get order challenges
Retrieve the ACME challenges for a certificate order to determine the validation requirements.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/role/:name/order/:order_id/challenge |
Parameters
name(string: <required>)- The name of the role.order_id(string: <required>)- The order ID.identifier(string: <required>)- The identifier (domain) for which the challenge was fulfilled.challenge_type(string: <required>)- The type of challenge fulfilled. Must be one of:http-01- HTTP-01 challengedns-01- DNS-01 challengetls-alpn-01- TLS-ALPN-01 challenge
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--url-query "identifier=example.com" \
--url-query "challenge_type=http-01" \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/order/01936d8e-7c3a-7890-b123-456789abcdef/challenge
Sample response
{
"data": {
"challenge_auth": "b483Q4ouEAg-xDJ-VmpSXIDMck9_SMtYXlHz3ilLgR0.zSp7WXxIJEAVN6IMDTWgUJVfnuCmvxlUTGbKiADGQJE",
"challenge_status": "pending",
"challenge_token": "b483Q4ouEAg-xDJ-VmpSXIDMck9_SMtYXlHz3ilLgR0",
"expires": "2026-07-06T12:45:23Z"
}
}
Mark challenge as fulfilled
Tell Vault that a challenge is fulfilled and ready for validation by the ACME server.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/role/:name/order/:order_id/fulfilled-challenge |
Parameters
name(string: <required>)- The name of the role.order_id(string: <required>)- The order ID.identifier(string: <required>)- The identifier (domain) for which the challenge was fulfilled.challenge_type(string: <required>)- The type of challenge fulfilled. Must be one of:http-01- HTTP-01 challengedns-01- DNS-01 challengetls-alpn-01- TLS-ALPN-01 challenge
Sample payload
{
"identifier": "example.com",
"challenge_type": "http-01"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/order/01936d8e-7c3a-7890-b123-456789abcdef/fulfilled-challenge
Sample response
Empty response on success.
Fetch certificate
Retrieve the issued certificate for a completed order.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/role/:name/order/:order_id/fetch-cert |
Parameters
name(string: <required>)- The name of the role.order_id(string: <required>)- The order ID.certificate_format(string: "pem")- The format of the returned certificate. Must be one of:pem- PEM format (default)der- DER formatpem_bundle- PEM format with full certificate chain
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/order/01936d8e-7c3a-7890-b123-456789abcdef/fetch-cert
Sample response
{
"data": {
"authority_key_id": "b3:cc:83:6c:...:64:a5:e6:f5",
"ca_chain": ["-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"],
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"certificate_format": "pem",
"not_after": "2026-09-27T12:43:03Z",
"not_before": "2026-06-29T12:43:04Z",
"private_key": "-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----",
"private_key_type": "EC",
"serial_number": "03:e7:1f:...:a2:3d"
}
}
Revoke certificate
Revoke a certificate.
| Method | Path |
|---|---|
POST | /{plugin_mount_path}/role/:name/order/:order_id/revoke |
Parameters
name(string: <required>)- The name of the role.order_id(string: <required>)- The order ID.reason(string: "unspecified")- The reason for revocation. Valid values:unspecified: no specific reason is provided or the reason does not fit into any other category.key_compromise: known or suspected that the private key corresponding to the certificate has been compromised.ca_compromise: known or suspected that the CA's private key has been compromised.affiliation_changed: the subject of the certificate has changed its affiliation (e.g., company name change).superseded: the certificate has been superseded by a new certificate.cessation_of_operation: the certificate is no longer needed and will no longer be used.certificate_hold: the certificate is on hold and should not be considered valid.remove_from_crl: indicates that a certificate is being removed from a Delta CRL listprivilege_withdrawn: the issuing CA has withdrawn the certificate's privileges.aa_compromise: one of the attribute authorities has been compromised.
Sample payload
{
"reason": "key_compromise"
}
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request POST \
--data @payload.json \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/order/01936d8e-7c3a-7890-b123-456789abcdef/revoke
Sample response
Empty response on success.
List active orders
List all active (non-terminal) orders for a role.
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/role/:name/active-orders |
Parameters
name(string: <required>)- The name of the role.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/active-orders
Sample response
{
"data": {
"keys": [
"01936d8e-7c3a-7890-b123-456789abcdef",
"01936d8f-1234-5678-9abc-def012345678"
]
}
}
Get cached certificate
Retrieve the most recently issued (cached) certificate for a role without going through the order workflow.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/role/:name/cached |
Parameters
name(string: <required>)- The name of the role.identifiers(array<string>: "")- List of domain names to include in the certificate.min_validity_duration(int)- The minimum duration remaining of the certificate. You must provide eithermin_validity_durationormin_validity_percentage, but not both.min_validity_percentage(int)- The minimum percentage remaining of the certificate. You must provide eithermin_validity_percentageormin_validity_duration, but not both.certificate_format(string: "pem")- The format of the returned certificate:pem- PEM format (default)der- DER formatpem_bundle- PEM format with full certificate chain
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--url-query "identifiers=example.com" \
--url-query "min_validity_percentage=75" \
${VAULT_ADDR}/v1/pki-external-ca/role/web-server/cached
Sample response
{
"data": {
"authority_key_id": "b3:cc:83:6c:...:64:a5:e6:f5",
"ca_chain": ["-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"],
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
"certificate_format": "pem",
"not_after": "2026-09-27T12:43:03Z",
"not_before": "2026-06-29T12:43:04Z",
"private_key": "-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----",
"private_key_type": "EC",
"serial_number": "03:e7:1f:...:a2:3d"
}
}
Lookup operations
Lookup operations provide ways to query certificate and order information.
Lookup certificate by serial
Looks up certificate information by serial number.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/lookup/cert/:serial |
Parameters
serial(string: <required>)- The serial number of the certificate, including any colons or hyphens.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/lookup/cert/03:e7:1f:a2:3d
Sample response
{
"data": {
"order_status": "completed",
"role_name": "web-server",
"order_id": "01936d8e-7c3a-7890-b123-456789abcdef",
"identifiers": ["example.com", "www.example.com"],
"not_before": "2026-02-24T20:00:00Z",
"not_after": "2026-05-25T20:00:00Z"
}
}
Lookup order by ID
Look up order information by order ID.
| Method | Path |
|---|---|
GET | /{plugin_mount_path}/lookup/order/:order_id |
Parameters
order_id(string: <required>)- The order ID.
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
${VAULT_ADDR}/v1/pki-external-ca/lookup/order/01936d8e-7c3a-7890-b123-456789abcdef
Sample response
{
"data": {
"challenges": {
"example.com": [
{
"challenge_status": "valid",
"challenge_type": "dns-01",
"expires": "2026-07-06T13:39:32Z"
},
{
"challenge_status": "pending",
"challenge_type": "http-01",
"expires": "2026-07-06T13:39:32Z"
}
]
},
"creation_date": "2026-06-29T09:39:31-04:00",
"csr": "",
"expires": "2026-07-06T13:39:32Z",
"identifiers": ["example.com"],
"last_error": "",
"last_update": "2026-06-29T09:41:37-04:00",
"next_work_date": "0001-01-01T00:00:00Z",
"order_status": "completed",
"role_name": "web-server",
"serial_number": "2c:7f:bd:fd:e3:64:cd:5d:bf:bf:1c:50:80:79:5b:40:96:94"
}
}
List all orders
List all active and complete orders.
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/lookup/orders |
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
${VAULT_ADDR}/v1/pki-external-ca/lookup/orders
Sample response
{
"data": {
"keys": [
"01936d8e-7c3a-7890-b123-456789abcdef",
"01936d8f-1234-5678-9abc-def012345678",
"01936d90-abcd-ef01-2345-6789abcdef01"
]
}
}
List recent orders
List orders created within a specified time window, sorted by creation time (most recent first). Vault limits the response to a maximum 500 results for performance reasons. The queried duration must be between 1 hour and 1 week (168 hours).
| Method | Path |
|---|---|
LIST | /{plugin_mount_path}/lookup/orders/recent |
Parameters
within(string: "1h")- Duration to look back for recent orders (e.g.,180m,1h,24h). Must be between 1 hour and 1 week (168h).
Sample request
$ curl \
--header "X-Vault-Token: ${VAULT_TOKEN}" \
--request LIST \
--url-query "within=24h" \
${VAULT_ADDR}/v1/pki-external-ca/lookup/orders/recent
Sample response
{
"data": {
"keys": [
"01936d90-abcd-ef01-2345-6789abcdef01",
"01936d8f-1234-5678-9abc-def012345678",
"01936d8e-7c3a-7890-b123-456789abcdef"
],
"key_info": {
"01936d90-abcd-ef01-2345-6789abcdef01": {
"creation_date": "2026-06-29T15:30:00Z",
"identifiers": ["api.example.com"],
"last_update": "2026-06-29T15:31:00Z",
"order_status": "awaiting-challenge-fulfillment",
"role_name": "api-server"
},
"01936d8f-1234-5678-9abc-def012345678": {
"creation_date": "2026-06-29T14:20:00Z",
"identifiers": ["www.example.com"],
"last_update": "2026-06-29T14:22:00Z",
"order_status": "fetching-certificate",
"role_name": "web-server"
},
"01936d8e-7c3a-7890-b123-456789abcdef": {
"creation_date": "2026-06-29T13:10:00Z",
"identifiers": ["example.com"],
"last_update": "2026-06-29T13:12:00Z",
"order_status": "completed",
"role_name": "web-server"
}
}
}
}