---
title: "Issue Certificates"
description: "Use the X.509 Certificate Service to issue self-signed roots, CA-signed intermediates and leaves, ISO IACA roots, ISO Document Signer certificates, and ETSI Provider / Wallet Relying Party certificates. Every…"
stack: "Enterprise Stack (commercial) — version 1.1.1"
stack_version: "1.1.1"
stack_comparison: https://docs.walt.id/community-vs-enterprise.md
canonical_url: https://docs.walt.id/enterprise-stack/services/x509-service/issue-certificates
generated: 2026-10-01
---
# Issue Certificates

Use the X.509 Certificate Service to issue self-signed roots, CA-signed intermediates and leaves, ISO IACA roots, ISO Document Signer certificates, and ETSI Provider / Wallet Relying Party certificates. Every certificate is issued through one request shape (`UpsertGeneratedCertificateRequest`) sent to either:

- `POST /v1/{target}/x509-service-api/certificates` — issue a new certificate.
- `PUT /v1/{target}/x509-service-api/certificates` — issue or replace the certificate at a known ID.

Service reference: [Swagger API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

## Shared Path Parameters

- `orgID`: When performing operations within an organization, use the organization's Base URL or another valid host alias. For example, if your organization is named `test`, your default Base URL is `test.enterprise-sandbox.waltid.dev` on the sandbox environment.
- `target`: The X.509 Certificate service path, optionally suffixed with a certificate ID:
  - `{organizationID}.{tenantID}.{x509ServiceID}` — for `POST`, the service generates a random certificate ID.
  - `{organizationID}.{tenantID}.{x509ServiceID}.{certificateID}` — issue the certificate under a specific ID. Required for `PUT`.

## Certificate ID and storage

The certificate ID is taken from the request path, not the body. When one or more [X.509 Store services](https://docs.walt.id/enterprise-stack/services/x509-store-service/overview.md) are attached, the issued certificate is written into them under that ID (one shared child ID across all attached stores), and the response `persistResult` maps each full stored path to its outcome. When no store is attached the certificate is generated and returned but not stored, and `persistResult` is empty.

## Request fields

All issuance requests use the same body. `validFrom` and `validTo` are always required; everything else depends on the certificate you are issuing.

**Store persistence** (optional, override the service default — see [Store persistence](#store-persistence))

- `createStrategy`: _String_ - `CREATE_NONE` | `CREATE_ONE` | `CREATE_ALL`. Applies to `POST`.
- `upsertStrategy`: _String_ - `INSERT_ONE_UPDATE_ALL` | `UPSERT_ALL`. Applies to `PUT`.
- `successStrategy`: _String_ - `ANY_OK` | `ALL_OK`.
- `storeRef`: _String_ - Target reference of a single X.509 store to write into, e.g. `test.tenant1.x509-store-1`. When omitted, the store selection follows the strategy.

**Certificate shape**

- `certificateProfile`: _String_ (optional) - Applies a predefined profile and validates the issued certificate against it. See [Certificate profiles](#certificate-profiles) for the full list of names and requirements.
- `signatureAlgorithm`: _String_ (optional) - Signature algorithm used to sign the certificate. Defaults to an algorithm derived from the issuer key (`SHA-256/384/512` with ECDSA or RSA PKCS#1 depending on the key).
- `validFrom`: _String_ (required) - Validity start (`notBefore`), ISO-8601 instant, e.g. `2026-09-01T00:00:00Z`.
- `validTo`: _String_ (required) - Validity end (`notAfter`), ISO-8601 instant.

**Subject public key and subject DN** (supply exactly one key source)

- `subjectKeyRef`: _String_ - KMS key path for the subject key, e.g. `test.tenant1.kms1.leaf-key`.
- `subjectKeyJwk`: _Object_ - Subject public/private key as a JWK.
- `csrPem`: _String_ - PKCS#10 CSR PEM. The subject DN and subject alternative names are taken from the CSR and its signature is verified for proof-of-possession. Cannot be combined with `subjectDn` or `selfSigned`. See [CSR Workflows](https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows.md).
- `subjectDn`: _String_ - Subject distinguished name, e.g. `CN=Example Leaf,O=Example Org,C=US`. Required unless a CSR is provided.

**Issuer** (self-signed, or one issuer source)

- `selfSigned`: _Boolean_ (default `false`) - When `true`, the subject key also signs the certificate. No issuer certificate or issuer DN may be set.
- `issuerCertificateRef`: _String_ - Path of a stored issuer certificate, e.g. `test.tenant1.x509-store-1.example-iaca`. The issuer DN and (if not overridden) the issuer key are taken from it.
- `issuerCertificatePem`: _String_ - Issuer certificate PEM, used the same way as `issuerCertificateRef`. Required when no store is attached and the certificate is not self-signed.
- `issuerKeyRef`: _String_ - KMS key path for the signing key. Required if the issuer certificate does not record a subject key reference.
- `issuerKeyJwk`: _Object_ - Signing key as a JWK. Mutually exclusive with `issuerKeyRef`.

**Note:**

For a CA-signed certificate (any issuer other than `selfSigned`), `validFrom` / `validTo` must fall within the issuer certificate's own validity window. A request outside that window fails with `400`.

**Extensions**

- `keyUsage`: _Array of Strings_ - Key usage bits: `digitalSignature`, `nonRepudiation`, `keyEncipherment`, `dataEncipherment`, `keyAgreement`, `keyCertSign`, `cRLSign`, `encipherOnly`, `decipherOnly`.
- `extendedKeyUsageOids`: _Array of Strings_ - Extended key usage OIDs.
- `basicConstraintsCa`: _Boolean_ - `true` for a CA certificate, `false` to explicitly mark a leaf.
- `basicConstraintsPathLength`: _Integer_ - Maximum number of intermediate CAs below this certificate. Only valid when `basicConstraintsCa` is `true`.
- `subjectAlternativeNames`: _Array_ - SAN entries, each `{ "type": ..., "name": ... }` with `type` one of `dnsName`, `email`, `uri`, `ipAddress`. Overrides SANs carried in a CSR.
- `issuerAlternativeNames`: _Array_ - Issuer alternative name entries in the same `{ type, name }` form.
- `crlDistributionPointUri`: _String_ - CRL distribution point URI.
- `authorityInfoAccessCaIssuerUris`: _Array of Strings_ - Authority Information Access: URI(s) where the issuing CA's own certificate can be retrieved.
- `authorityInfoAccessOcspResponderUris`: _Array of Strings_ - Authority Information Access: URI(s) of OCSP responder(s) for this certificate.
- `certificatePolicyOids`: _Array of Strings_ - Certificate Policies extension: OID strings identifying the applicable certificate policies (e.g. a TSP-defined or ETSI policy OID).
- `qcStatements`: _Object_ (optional) - RFC 3739 / ETSI EN 319 412-5 Qualified Certificate Statements:
  - `qcCompliance`: _Boolean_ (default `false`) - Asserts the certificate is a qualified certificate (QcCompliance statement).
  - `qcSscd`: _Boolean_ (default `false`) - Asserts the private key resides in a qualified signature/seal creation device (QcSSCD statement).
  - `qcTypeOids`: _Array of Strings_ - OID(s) naming the certificate type(s) asserted by the QcType statement, e.g. `id-etsi-qct-esign` / `-eseal` / `-web`.
  - `otherStatementIds`: _Array of Strings_ - Additional statement OIDs not covered above. `statementInfo` is not supported and is omitted.

**Metadata**

- `metadata`: _Object_ - Arbitrary JSON stored alongside the certificate. The service merges in `issuerKeyRef`, `subjectKeyRef`, and `issuerCertRef` (the resolved references) on top of what you provide. To make a self-signed IACA eligible for a VICAL, include `vicalDocType` as a JSON array of mdoc document types, e.g. `"vicalDocType": ["org.iso.18013.5.1.mDL"]`.

## Certificate profiles

`certificateProfile` applies a predefined extension set to the certificate being issued and validates the result against it after issuance. `certificateProfile` is independent of `selfSigned` / issuer selection — each profile fixes whether it is self-signed or CA-issued, as shown below.

| Profile | Alias | Standard | Self-signed |
|---|---|---|---|
| [`iso-iaca-root`](#iso-iaca-root-certificate) | `org.iso.18013.5.1.iaca` | ISO/IEC 18013-5 (mDL) | Always |
| [`iso-document-signer`](#iso-document-signer-certificate) | `org.iso.18013.5.1.ds` | ISO/IEC 18013-5 (mDL) | Never — issued by an IACA |
| [`etsi-pid-provider`](#etsi-pid-provider-certificate) | — | ETSI TS 119 412-6 | Self-signed or CA-issued |
| [`etsi-wallet-provider`](#etsi-wallet-provider-certificate) | — | ETSI TS 119 412-6 | Self-signed or CA-issued |
| [`etsi-wrpac`](#etsi-wallet-relying-party-access-certificate-wrpac) | — | ETSI TS 119 411-8 | Never — issued by a national WRP provider |
| [`etsi-wrprc`](#etsi-wallet-relying-party-registration-certificate-wrprc) | — | ETSI TS 119 475 | Never — issued by a national registrar |

**Error:**

`etsi-wrprc` is a draft profile: it applies and validates the baseline end-entity certificate shape shared by every ETSI EUDI profile, but does not yet encode or validate the WRPRC-specific "registered intended use" — that requires a custom extension not modelled yet. A generated `etsi-wrprc` certificate always carries a `registeredIntendedUse` warning in the service log; it does not fail issuance.

## Self-signed root or CA certificate

Set `selfSigned: true` and provide one key via `subjectKeyRef` or `subjectKeyJwk` (or `issuerKeyJwk`); that key is both subject and signing key.

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "selfSigned": true,
  "subjectKeyRef": "test.tenant1.kms1.root-key",
  "subjectDn": "CN=Example Root CA,O=Example Org,C=US",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2031-09-01T00:00:00Z",
  "basicConstraintsCa": true,
  "basicConstraintsPathLength": 1,
  "keyUsage": ["keyCertSign", "cRLSign"]
}'
```

**Body Parameters**

- `selfSigned`: _Boolean_ (required here) - Set to `true`.
- `subjectKeyRef` / `subjectKeyJwk`: _String_ / _Object_ (required) - The root key. Also used as the signing key.
- `subjectDn`: _String_ (required) - Distinguished name of the root.
- `validFrom` / `validTo`: _String_ (required) - Validity window.
- `basicConstraintsCa` / `basicConstraintsPathLength` / `keyUsage`: _mixed_ (optional) - Recommended for a CA certificate.

**Example Response**

```json
{
  "certificate": {
    "_id": "test.tenant1.x509-store-1.b0e0f4b7-2b1a-4a2e-9d9f-6b2f0d5c1a33",
    "createdAt": "2026-09-01T09:12:44Z",
    "updatedAt": "2026-09-01T09:12:44Z",
    "metadata": {
      "issuerKeyRef": "test.tenant1.kms1.root-key",
      "subjectKeyRef": "test.tenant1.kms1.root-key"
    },
    "certificatePem": "-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----",
    "issuerDn": "CN=Example Root CA,O=Example Org,C=US",
    "issuerDnHex": "3045310f...",
    "subjectDn": "CN=Example Root CA,O=Example Org,C=US",
    "subjectDnHex": "3045310f...",
    "validFrom": "2026-09-01T00:00:00Z",
    "validTo": "2031-09-01T00:00:00Z"
  },
  "persistResult": {
    "test.tenant1.x509-store-1.b0e0f4b7-2b1a-4a2e-9d9f-6b2f0d5c1a33": "CREATED"
  }
}
```

**Response Codes**

- `200` - Certificate generated, no store attached (not persisted).
- `201` - Certificate issued and persisted.
- `409` - A certificate with this ID already exists in an attached store.
- `400` - Invalid request (e.g. conflicting key inputs, missing subject DN).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed (e.g. a store was unreachable). Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## CA-signed certificate

Omit `selfSigned` and point at an issuer. The most common source is a stored issuer certificate (`issuerCertificateRef`); the signing key is read from that certificate's recorded `subjectKeyRef` metadata unless you override it with `issuerKeyRef` / `issuerKeyJwk`.

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "issuerCertificateRef": "test.tenant1.x509-store-1.example-root-ca",
  "subjectKeyRef": "test.tenant1.kms1.leaf-key",
  "subjectDn": "CN=Example Leaf,O=Example Org,C=US",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2027-09-01T00:00:00Z",
  "keyUsage": ["digitalSignature"],
  "subjectAlternativeNames": [
    { "type": "dnsName", "name": "leaf.example.com" }
  ]
}'
```

**Body Parameters**

- `issuerCertificateRef`: _String_ (required here) - Stored issuer certificate. Use `issuerCertificatePem` instead when no store is attached.
- `issuerKeyRef` / `issuerKeyJwk`: _String_ / _Object_ (optional) - Override the signing key. Required if the issuer certificate does not record one.
- `subjectKeyRef` / `subjectKeyJwk` / `csrPem`: _mixed_ (required) - Subject public key source.
- `subjectDn`: _String_ (required unless `csrPem` is used) - Leaf distinguished name.

**Example Response**

```json
{
  "certificate": {
    "_id": "test.tenant1.x509-store-1.f5a1c0d2-8e7b-4c3a-91d0-2a7e4b9c1f66",
    "createdAt": "2026-09-01T09:20:10Z",
    "updatedAt": "2026-09-01T09:20:10Z",
    "metadata": {
      "issuerKeyRef": "test.tenant1.kms1.root-key",
      "subjectKeyRef": "test.tenant1.kms1.leaf-key",
      "issuerCertRef": "test.tenant1.x509-store-1.example-root-ca"
    },
    "certificatePem": "-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----",
    "issuerDn": "CN=Example Root CA,O=Example Org,C=US",
    "issuerDnHex": "3045310f...",
    "subjectDn": "CN=Example Leaf,O=Example Org,C=US",
    "subjectDnHex": "3043310d...",
    "validFrom": "2026-09-01T00:00:00Z",
    "validTo": "2027-09-01T00:00:00Z"
  },
  "persistResult": {
    "test.tenant1.x509-store-1.f5a1c0d2-8e7b-4c3a-91d0-2a7e4b9c1f66": "CREATED"
  }
}
```

**Response Codes**

- `200` - Certificate generated, no store attached.
- `201` - Certificate issued and persisted.
- `409` - Certificate ID already exists in an attached store.
- `400` - Invalid request (e.g. issuer not resolvable, subject DN in CSR mismatch).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## ISO IACA root certificate

Set `certificateProfile` to `iso-iaca-root` and `selfSigned` to `true`. The profile adds the ISO/IEC 18013-5 IACA extensions (`CA=true`, `pathLenConstraint=0`, `keyCertSign` + `cRLSign` marked critical, subject key identifier, issuer alternative name) and validates the result. Supported keys: `secp256r1`, `secp384r1`, `secp521r1`.

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}.iaca-prod-2026/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "selfSigned": true,
  "certificateProfile": "iso-iaca-root",
  "subjectKeyRef": "test.tenant1.kms1.iaca-key",
  "subjectDn": "CN=Example IACA,O=Example Org,C=US",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2036-09-01T00:00:00Z",
  "issuerAlternativeNames": [
    { "type": "uri", "name": "https://iaca.example.com" },
    { "type": "email", "name": "iaca@example.com" }
  ],
  "metadata": {
    "vicalDocType": ["org.iso.18013.5.1.mDL"]
  }
}'
```

**Body Parameters**

- `certificateProfile`: _String_ (required here) - `iso-iaca-root`.
- `selfSigned`: _Boolean_ (required here) - `true`.
- `subjectKeyRef` / `subjectKeyJwk`: _String_ / _Object_ (required) - The IACA key (`secp256r1` / `secp384r1` / `secp521r1`).
- `subjectDn`: _String_ (required) - IACA distinguished name. `C=` must be a valid ISO 3166-1 alpha-2 code.
- `issuerAlternativeNames`: _Array_ (required by profile) - At least one `uri` or `email` entry.
- `metadata.vicalDocType`: _Array of Strings_ (optional) - Required for this IACA to be picked up by the [VICAL Service](https://docs.walt.id/enterprise-stack/services/vical-service/overview.md).

**Response Codes**

- `200` / `201` - IACA issued (persisted when a store is attached).
- `400` - Profile validation failed (e.g. missing issuer alternative name, invalid country code, validity window too long).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## ISO Document Signer certificate

Set `certificateProfile` to `iso-document-signer` and issue against an IACA via `issuerCertificateRef` (a stored IACA) or `issuerCertificatePem`. The profile adds the Document Signer extensions (`digitalSignature` critical, extended key usage `1.0.18013.5.1.2`, subject/authority key identifiers, issuer alternative name from the IACA, CRL distribution point) and validates the result.

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}.ds-2026-q1/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "certificateProfile": "iso-document-signer",
  "issuerCertificateRef": "test.tenant1.x509-store-1.iaca-prod-2026",
  "subjectKeyRef": "test.tenant1.kms1.ds-key",
  "subjectDn": "CN=Example Document Signer,O=Example Org,C=US",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2027-11-01T00:00:00Z",
  "issuerAlternativeNames": [
    { "type": "uri", "name": "https://iaca.example.com" }
  ],
  "crlDistributionPointUri": "https://crl.example.com/ds.crl"
}'
```

**Body Parameters**

- `certificateProfile`: _String_ (required here) - `iso-document-signer`.
- `issuerCertificateRef` / `issuerCertificatePem`: _String_ (required) - The issuing IACA.
- `subjectKeyRef` / `subjectKeyJwk` / `csrPem`: _mixed_ (required) - Document Signer public key source.
- `subjectDn`: _String_ (required unless `csrPem` is used) - Document Signer distinguished name. Requires a `CN=`; `C=` must match the IACA's `C=`, and `ST=` must match the IACA's `ST=` if the IACA has one.
- `crlDistributionPointUri`: _String_ (required by profile) - CRL distribution point.
- `issuerKeyRef` / `issuerKeyJwk`: _String_ / _Object_ (optional) - Override the IACA signing key.

**Response Codes**

- `200` / `201` - Document Signer issued (persisted when a store is attached).
- `400` - Profile validation failed (e.g. missing commonName, country/state mismatch with the IACA, validity window outside the IACA window or over the profile maximum).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## ETSI PID Provider certificate

Set `certificateProfile` to `etsi-pid-provider`. Issues an end-entity certificate for a PID (Personal Identification Data) Provider, per ETSI TS 119 412-6 clause 4 — the certificate whose private key signs SD-JWT VC / mdoc Person Identification Data. May be self-signed or CA-issued. The profile adds `basicConstraints{cA=false}` and `keyUsage{digitalSignature}` (both critical), a subject key identifier, and a `qcStatements` QcCompliance + QcType (`id-etsi-qct-pid`, `0.4.0.194126.1.1`) statement automatically — you do not need to set `qcStatements` yourself unless you want to assert additional statements.

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "selfSigned": true,
  "certificateProfile": "etsi-pid-provider",
  "subjectKeyRef": "test.tenant1.kms1.pid-provider-key",
  "subjectDn": "CN=Example PID Provider,O=Example PID Provider AT,organizationIdentifier=VATAT-U12345678,C=AT",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2031-09-01T00:00:00Z",
  "certificatePolicyOids": ["1.3.6.1.4.1.55555.1.1"]
}'
```

**Body Parameters**

- `certificateProfile`: _String_ (required here) - `etsi-pid-provider`.
- `selfSigned`: _Boolean_ (optional) - `true` for a self-signed PID Provider certificate; omit and set an issuer for a CA-issued one.
- `subjectDn`: _String_ (required) - Must include `C=` and `CN=`, plus `O=` (legal person) or `givenName=`/`surname=`/`pseudonym=` + `serialNumber=` (natural person). Presence of `organizationIdentifier=` selects the legal-person shape. When CA-issued, the issuer certificate's subject DN is validated the same way.
- `certificatePolicyOids`: _Array of Strings_ (required by profile) - At least one TSP-defined policy OID.
- `authorityInfoAccessCaIssuerUris` / `authorityInfoAccessOcspResponderUris`: _Array of Strings_ (required by profile unless self-signed) - CA issuer / OCSP responder location(s).

**Response Codes**

- `200` / `201` - Certificate issued (persisted when a store is attached).
- `400` - Profile validation failed (e.g. missing certificate policy OID, missing `authorityInfoAccess` on a CA-issued certificate, malformed subject/issuer DN, unsupported key algorithm/size).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## ETSI Wallet Provider certificate

Set `certificateProfile` to `etsi-wallet-provider`. Issues an end-entity certificate for a Wallet Provider, per ETSI TS 119 412-6 clause 5.1 — the certificate whose private key signs Wallet Instance / Unit Attestations (WIA/WUA). Otherwise identical to the [ETSI PID Provider certificate](#etsi-pid-provider-certificate): may be self-signed or CA-issued, same DN shape and `certificatePolicyOids` / `authorityInfoAccess` requirements, same extensions applied automatically — except the automatic `qcStatements` QcType OID is `id-etsi-qct-wal` (`0.4.0.194126.1.2`).

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "selfSigned": true,
  "certificateProfile": "etsi-wallet-provider",
  "subjectKeyRef": "test.tenant1.kms1.wallet-provider-key",
  "subjectDn": "CN=Example Wallet Provider,O=Example Wallet Provider AT,organizationIdentifier=VATAT-U87654321,C=AT",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2031-09-01T00:00:00Z",
  "certificatePolicyOids": ["1.3.6.1.4.1.55555.2.1"]
}'
```

**Body Parameters**

- `certificateProfile`: _String_ (required here) - `etsi-wallet-provider`.
- Same remaining fields as [ETSI PID Provider certificate](#etsi-pid-provider-certificate).

**Response Codes**

Same as [ETSI PID Provider certificate](#etsi-pid-provider-certificate).

## ETSI Wallet Relying Party Access Certificate (WRPAC)

Set `certificateProfile` to `etsi-wrpac`. Issues an end-entity certificate a Relying Party uses to authenticate a presentation request to a wallet, per ETSI TS 119 411-8 clause 6.6.1. Always CA-issued by a national WRP provider — there is no self-signed variant. The profile adds `basicConstraints{cA=false}`, `keyUsage{digitalSignature}` (both critical), and a subject key identifier; unlike the Provider profiles, `certificatePolicies` and `qcStatements` are **not** added automatically and must be supplied in the request.

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "certificateProfile": "etsi-wrpac",
  "issuerCertificateRef": "test.tenant1.x509-store-1.wrp-national-provider",
  "subjectKeyRef": "test.tenant1.kms1.wrp-key",
  "subjectDn": "CN=Example Relying Party,O=Example Relying Party AT,C=AT",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2027-09-01T00:00:00Z",
  "certificatePolicyOids": ["0.4.0.194118.1.2"],
  "subjectAlternativeNames": [
    { "type": "uri", "name": "https://relying-party.example.com" }
  ],
  "crlDistributionPointUri": "https://crl.example.com/wrpac.crl",
  "authorityInfoAccessCaIssuerUris": ["https://ca.example.com/wrp-national-provider.crt"]
}'
```

**Body Parameters**

- `certificateProfile`: _String_ (required here) - `etsi-wrpac`.
- `issuerCertificateRef` / `issuerCertificatePem`: _String_ (required) - The issuing national WRP provider certificate.
- `certificatePolicyOids`: _Array of Strings_ (required by profile) - Exactly one WRPAC policy OID (ETSI TS 119 411-8 clause 5.3): `0.4.0.194118.1.1` (NCP natural person), `0.4.0.194118.1.2` (NCP legal person), `0.4.0.194118.1.3` (QCP natural person), `0.4.0.194118.1.4` (QCP legal person).
- `qcStatements`: _Object_ (required if the policy OID above is a QCP one) - Set `qcCompliance: true` and `qcSscd: true`; for the legal-person QCP OID also set `qcTypeOids` (any non-empty QcType statement). Not required, and normally omitted, for the NCP policy OIDs.
- `subjectAlternativeNames`: _Array_ (required by profile) - At least one `uri` or `email` entry carrying Relying Party contact information.
- `crlDistributionPointUri` and/or `authorityInfoAccessOcspResponderUris`: _String_ / _Array of Strings_ (required by profile) - At least one revocation mechanism (CRL or OCSP).
- `authorityInfoAccessCaIssuerUris` / `authorityInfoAccessOcspResponderUris`: _Array of Strings_ (required by profile) - WRPAC is always CA-issued, so `authorityInfoAccess` is mandatory.

**Response Codes**

- `200` / `201` - Certificate issued (persisted when a store is attached).
- `400` - Profile validation failed (e.g. missing or non-unique WRPAC policy OID, missing contact SAN, no revocation mechanism, missing `qcStatements` for a QCP policy, malformed DN).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## ETSI Wallet Relying Party Registration Certificate (WRPRC)

Set `certificateProfile` to `etsi-wrprc`. Issues an end-entity certificate that registers a Relying Party with a national registrar, per ETSI TS 119 475. Always CA-issued by a national registrar — there is no self-signed variant. The profile adds the same baseline extensions as [WRPAC](#etsi-wallet-relying-party-access-certificate-wrpac) (`basicConstraints{cA=false}`, `keyUsage{digitalSignature}`, subject key identifier); `certificatePolicyOids` and `authorityInfoAccess` must be supplied in the request, and the subject DN is always treated as a legal person.

**Error:**

`etsi-wrprc` is a draft profile — it does not yet validate the WRPRC-specific "registered intended use" (the scope a registrar authorizes the Relying Party to request). See [Certificate profiles](#certificate-profiles).

**Option: CURL**

Endpoint: `POST /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'POST' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "certificateProfile": "etsi-wrprc",
  "issuerCertificateRef": "test.tenant1.x509-store-1.wrp-national-provider",
  "subjectKeyRef": "test.tenant1.kms1.wrp-registration-key",
  "subjectDn": "CN=Example Relying Party,O=Example Relying Party AT,C=AT",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2027-09-01T00:00:00Z",
  "certificatePolicyOids": ["1.3.6.1.4.1.55555.3.1"],
  "authorityInfoAccessCaIssuerUris": ["https://ca.example.com/wrp-national-provider.crt"]
}'
```

**Body Parameters**

- `certificateProfile`: _String_ (required here) - `etsi-wrprc`.
- `issuerCertificateRef` / `issuerCertificatePem`: _String_ (required) - The issuing national registrar certificate.
- `subjectDn`: _String_ (required unless `csrPem` is used) - Legal-person DN: requires `C=`, `CN=`, and `O=`.
- `certificatePolicyOids`: _Array of Strings_ (required by profile) - At least one policy OID; ETSI TS 119 475 does not yet define a fixed WRPRC OID, so use whatever the issuing registrar's CPS defines.
- `authorityInfoAccessCaIssuerUris` / `authorityInfoAccessOcspResponderUris`: _Array of Strings_ (required by profile) - WRPRC is always CA-issued, so `authorityInfoAccess` is mandatory.

**Response Codes**

- `200` / `201` - Certificate issued (persisted when a store is attached).
- `400` - Profile validation failed (e.g. missing certificate policy OID, missing `authorityInfoAccess`, malformed DN).
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## Issue or replace a certificate (PUT)

`PUT /certificates` writes the certificate at the ID given in the path, creating it if it does not exist and updating it if it does.

**Error:**

`PUT /certificates` requires at least one attached X.509 Store service — there is nowhere to upsert a certificate without one. Use `POST /certificates` for stateless issuance.

**Option: CURL**

Endpoint: `PUT /v1/{target}/x509-service-api/certificates` | [API Reference](https://enterprise.sandbox.walt.id/swagger/index.html#/Service%20%7C%20X.509%20Certificate%20Service)

**Example Request**

```bash
curl -X 'PUT' \
  'https://{orgID}.enterprise-sandbox.waltid.dev/v1/{target}.leaf-service-a/x509-service-api/certificates' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer {yourToken}' \
  -H 'Content-Type: application/json' \
  -d '{
  "issuerCertificateRef": "test.tenant1.x509-store-1.example-root-ca",
  "subjectKeyRef": "test.tenant1.kms1.leaf-key",
  "subjectDn": "CN=Service A,O=Example Org,C=US",
  "validFrom": "2026-09-01T00:00:00Z",
  "validTo": "2027-09-01T00:00:00Z",
  "keyUsage": ["digitalSignature"],
  "upsertStrategy": "UPSERT_ALL"
}'
```

**Path Parameters**

- `target`: _resourceIdentifier_ (required) - Must include the certificate ID: `{organizationID}.{tenantID}.{x509ServiceID}.{certificateID}`.

**Body Parameters**

- Same as `POST /certificates`, with `upsertStrategy` instead of `createStrategy`.

**Response Codes**

- `200` - Existing certificate replaced.
- `201` - Certificate created.
- `400` - Invalid request.
- `401` - Invalid or missing authentication token.
- `500` - The certificate was generated but a required store write failed. Check `persistResult` for which store reported `FAILED`; safe to retry with the same request.

## Response model

Both endpoints return an `UpsertCertificateResponse`:

- `certificate`: _Object_ - The issued certificate.
  - `_id`: _String_ - Full path. When a store is attached this is `{store}.{certificateID}`; otherwise `{x509ServiceID}.{certificateID}`.
  - `certificatePem`: _String_ - PEM-encoded certificate.
  - `issuerDn` / `issuerDnHex`, `subjectDn` / `subjectDnHex`: _String_ - Distinguished names in string and hex-DER form.
  - `validFrom` / `validTo`: _String_ - Effective validity window (truncated to whole seconds).
  - `metadata`: _Object_ - Stored metadata, including the resolved `issuerKeyRef`, `subjectKeyRef`, `issuerCertRef`, and any `vicalDocType` you supplied.
  - `createdAt` / `updatedAt`: _String_ - Persistence timestamps.
- `persistResult`: _Object_ - Maps each full stored certificate path to its outcome: `CREATED`, `UPDATED`, `ALREADY_EXISTS`, or `FAILED`. Empty when no store is attached.

## Store persistence

The service's default persistence strategy (set at [Setup](https://docs.walt.id/enterprise-stack/services/x509-service/setup.md#persistence-strategy)) decides which attached stores receive the certificate and when the request counts as successful. Any request can override it:

- `upsertStrategy` (`PUT`) — how the write certificates across stores.
- `successStrategy` — `ANY_OK` or `ALL_OK`.
- `storeRef` — restrict the write to one named store.

**Note:**

With `successStrategy: ANY_OK` a partial failure still returns success as long as at least one store write succeeds; inspect `persistResult` for per-store `FAILED` entries. If every write fails (`ANY_OK`), or any write fails (`ALL_OK`), the request returns `500` even though the certificate was generated — the `FAILED` entries in `persistResult` say which store(s) rejected the write.

## Deprecated endpoints

The following endpoints and request shapes are still served for backward compatibility but are superseded by `POST` / `PUT /certificates`:

| Deprecated | Replacement |
|---|---|
| `POST /x509-service-api/certificate` (generic, key-descriptor / issuer objects) | `POST /x509-service-api/certificates` with flat `subjectKeyRef` / `issuerKeyRef` / `issuer` fields |
| `POST /x509-service-api/iso/iacas` | `POST /x509-service-api/certificates` with `certificateProfile: "iso-iaca-root"`, `selfSigned: true` |
| `POST /x509-service-api/iso/document-signers` | `POST /x509-service-api/certificates` with `certificateProfile: "iso-document-signer"` |
| `POST /x509-service-api/csrs` with `type: "generic"` / `type: "iso-document-signer"` and `subjectKeyDescriptor` | `POST /x509-service-api/csrs` with flat `subjectKeyRef` / `subjectKeyJwk` and `subjectDn` — see [CSR Workflows](https://docs.walt.id/enterprise-stack/services/x509-service/csr-workflows.md) |

New integrations should not use the deprecated endpoints. They accept the old `kms-hosted-key-descriptor` / `jwk-encoded-key-descriptor` objects and the old `storedCertificateId` body field, none of which exist on `POST` / `PUT /certificates`.
