Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

ACME (Let's Encrypt)

Issue and auto-renew public TLS certificates via any RFC 8555 ACME provider: Let's Encrypt, ZeroSSL, BuyPass, Google Trust Services, or an enterprise ACME server. ScaiVault is the ACME client — you configure it once and it handles challenges, ordering, and renewal.

Base path: /v1/pki/acme/

Pick a provider#

Provider Notes
letsencrypt Free, widely supported. Rate limits apply — see https://letsencrypt.org/docs/rate-limits/.
zerossl Free tier, 90-day certs.
buypass Free 180-day certs.
google Google Trust Services. Requires GCP account setup.
custom Any RFC 8555 endpoint. Provide directory_url.

Each provider has a production and staging environment. Use staging during development — its rate limits are looser and its certs aren't trusted, so you won't burn through your production quota.

Register an ACME account#

bash
1
2
3
4
5
6
7
8
9
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/accounts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "letsencrypt-production",
    "provider": "letsencrypt",
    "environment": "production",
    "email": "certs@acme.example"
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
resp = httpx.post(
    "https://scaivault.scailabs.ai/v1/pki/acme/accounts",
    headers={"Authorization": f"Bearer {os.environ['SCAIVAULT_TOKEN']}"},
    json={
        "name": "letsencrypt-production",
        "provider": "letsencrypt",
        "environment": "production",
        "email": "certs@acme.example",
    },
)
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const resp = await fetch("https://scaivault.scailabs.ai/v1/pki/acme/accounts", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAIVAULT_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "letsencrypt-production",
    provider: "letsencrypt",
    environment: "production",
    email: "certs@acme.example",
  }),
});

Response:

json
1
2
3
4
5
6
7
8
{
  "id": "acme_abc",
  "provider": "letsencrypt",
  "environment": "production",
  "email": "certs@acme.example",
  "status": "active",
  "created_at": "2026-04-23T14:00:00Z"
}

ScaiVault generates an account key during registration and stores it internally; you don't see or handle it.

How it works#

sequenceDiagram participant App participant SV as ScaiVault participant ACME as Let's Encrypt participant DNS as DNS Provider App->>SV: POST /pki/acme/issue<br/>(domains, challenge_type, auto_renew) SV->>ACME: create order ACME-->>SV: challenges (e.g. TXT _acme-challenge.<br/>example = token) SV->>DNS: create TXT record DNS-->>SV: ok Note over SV: poll authoritative<br/>nameservers SV->>ACME: challenge ready ACME->>ACME: verify TXT ACME-->>SV: order valid + cert SV->>DNS: delete TXT record SV-->>App: certificate.issued event

Pick a challenge type#

Three options, for three situations.

Challenge When to use What ScaiVault needs
http-01 The domain points at a host you control, reachable on port 80. ScaiVault serves /.well-known/acme-challenge/<token> — route that path to it.
dns-01 Wildcards (*.acme.example), or the host isn't web-reachable on port 80. A DNS provider configured in ScaiVault (Route 53, Cloudflare, Google DNS, and others).
tls-alpn-01 Port 80 isn't available. You control port 443. ScaiVault answers ACME on the TLS-ALPN-01 protocol on port 443.

DNS-01 is the most reliable — it doesn't care about network topology and supports wildcards. Configure a DNS provider once (see the admin UI under PKI → DNS Providers) and use dns-01 for everything.

Issue a certificate#

bash
1
2
3
4
5
6
7
8
9
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/issue \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "acme_abc",
    "domains": ["api.acme.example", "www.acme.example"],
    "challenge_type": "dns-01",
    "auto_renew": true
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
resp = httpx.post(
    "https://scaivault.scailabs.ai/v1/pki/acme/issue",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
        "account_id": "acme_abc",
        "domains": ["api.acme.example", "www.acme.example"],
        "challenge_type": "dns-01",
        "auto_renew": True,
    },
)
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const resp = await fetch("https://scaivault.scailabs.ai/v1/pki/acme/issue", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    account_id: "acme_abc",
    domains: ["api.acme.example", "www.acme.example"],
    challenge_type: "dns-01",
    auto_renew: true,
  }),
});

Response while the order is in progress:

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "order_id": "acme_order_xyz",
  "status": "pending",
  "domains": ["api.acme.example", "www.acme.example"],
  "challenges": [
    {
      "domain": "api.acme.example",
      "type": "dns-01",
      "status": "pending",
      "token": "...",
      "key_authorization": "..."
    }
  ],
  "created_at": "2026-04-23T14:00:00Z"
}

Poll order status:

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     https://scaivault.scailabs.ai/v1/pki/acme/orders/acme_order_xyz

When complete:

json
1
2
3
4
5
6
7
{
  "order_id": "acme_order_xyz",
  "status": "valid",
  "certificate_id": "cert_abc",
  "domains": ["api.acme.example", "www.acme.example"],
  "not_after": "2026-07-22T14:00:00Z"
}

Fetch the certificate PEM the same way as any issued cert:

bash
1
2
curl -H "Authorization: Bearer $TOKEN" \
     https://scaivault.scailabs.ai/v1/pki/certificates/cert_abc/pem

Wildcard certificates#

Require dns-01. Most providers don't accept HTTP-01 for wildcards.

bash
1
2
3
4
5
6
7
8
9
curl -X POST https://scaivault.scailabs.ai/v1/pki/acme/issue \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": "acme_abc",
    "domains": ["*.api.acme.example"],
    "challenge_type": "dns-01",
    "auto_renew": true
  }'

DNS providers#

ScaiVault writes the DNS TXT challenge record via an integrated DNS provider. Configure the provider once:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
curl -X POST https://scaivault.scailabs.ai/v1/pki/dns-providers \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "route53-acme",
    "type": "aws_route53",
    "config": {
      "access_key_id_path": "infra/aws/route53/access-key-id",
      "secret_access_key_path": "infra/aws/route53/secret-access-key",
      "hosted_zone_id": "Z1234567890ABC"
    }
  }'

Credentials are themselves references to ScaiVault secrets — they don't go in the DNS provider body directly.

Supported providers include AWS Route 53, Cloudflare, Google Cloud DNS, Azure DNS, DigitalOcean, Hetzner, and RFC 2136 dynamic DNS. Provider-specific config keys vary; check the admin UI or DNS Providers Reference.

Auto-renewal#

When auto_renew: true (default), ScaiVault re-runs the ACME flow ~30 days before not_after. Behaviors:

  • Re-uses the same account.
  • Same domains, same challenge type.
  • New certificate becomes a new version of the same "managed certificate" object.
  • certificate.renewed event fires on success.
  • certificate.renewal_failed fires on failure, with retries on exponential backoff.

To renew early (before the 30-day window), force it:

bash
1
2
curl -X POST https://scaivault.scailabs.ai/v1/pki/certificates/cert_abc/renew \
  -H "Authorization: Bearer $TOKEN"

List ACME accounts and orders#

bash
1
2
curl -H "Authorization: Bearer $TOKEN" https://scaivault.scailabs.ai/v1/pki/acme/accounts
curl -H "Authorization: Bearer $TOKEN" https://scaivault.scailabs.ai/v1/pki/acme/orders

Filter orders by status: pending, valid, invalid, deactivated, revoked, expired.

Revoke#

ACME-issued certs can be revoked via ACME (preferred) or the standard revocation endpoint:

bash
1
2
3
4
curl -X POST https://scaivault.scailabs.ai/v1/pki/revoke \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"serial_number": "1A:2B:...", "reason": "key_compromise", "use_acme": true}'

use_acme: true routes the revocation through the ACME server. Without it, ScaiVault only marks the cert revoked in its own store.

Rate limit awareness#

Let's Encrypt rate limits (subject to change):

  • 50 certificates per registered domain per week
  • 5 duplicate certificates per week
  • 300 pending authorizations per account

If you hit them you get backend_rate_limited from ScaiVault. Use staging for development and CI.

What's next#

Updated 2026-05-17 14:30:19 View source (.md) rev 5