A better certificate handoff
Use the free API when the browser flow is not enough.
Certificate issuance should not force every team into a paid dashboard, a local agent, or a one-size-fits-all command. The SSL Generator API exposes the same focused flow as the browser tool so you can connect it to an internal portal, deployment runbook, or DNS-aware automation.
The API is deliberately session-based. A session holds one domain request, its challenge material, and, after issuance, the download archive. This keeps the integration easy to reason about while giving your workflow a clear boundary for credentials and cleanup.
Why developers choose it
The certificate API without the certificate tax.
Free from request to download
There is no paid tier, subscription, or account requirement in the supplied workflow. Test as often as you need against Let's Encrypt staging before moving a verified flow to production.
Bring your own validation path
Choose DNS-01 when your workflow can create TXT records or needs a wildcard. Choose HTTP-01 when you can place one exact file on a public web server.
Keep the response useful
Every request returns the exact challenge material, lifecycle state, and next URL. Your integration does not need to translate ACME objects just to tell an operator what to do.
Deploy-ready files
Successful sessions return a ZIP with the leaf certificate, private key, intermediate chain, root CA, full chain, and deployment notes for the domain.
Quick start
From domain to PEM in four calls.
Replace https://your-host with the HTTPS address where you run SSL Generator. Use staging first.
-
01
Create a certificate session
Submit a domain, a validation method, and confirmation that you accept the Let's Encrypt Subscriber Agreement. Save the opaque
session_idreturned by the API.curl --request POST https://your-host/ssl/gen \ --header "Content-Type: application/json" \ --data '{ "domain": "example.com", "verification": "dns", "email": "you@example.com", "accept_terms": true }'201 Created{ "session_id": "opaque-random-session-id", "domain": "example.com", "verification": "dns-01", "status": "challenge_ready", "challenge_url": "/ssl/gen/opaque-random-session-id/challenge/", "expires_at": "2026-09-05T12:00:00+00:00" } -
02
Get and publish the challenge
Request the session challenge. For DNS, add the returned TXT record. For HTTP, serve the returned content exactly at the returned path over public port 80.
curl https://your-host/ssl/gen/$SESSION_ID/challenge/DNS-01 response{ "verification": "dns-01", "record": { "type": "TXT", "name": "_acme-challenge.example.com", "value": "challenge-value" } }Optional preflight: call
POST /ssl/gen/$SESSION_ID/challenge/verify/to run an advisory check. Let's Encrypt remains the authority that validates the proof. HTTP preflight is disabled by default. -
03
Ask Let's Encrypt to issue
Once the record or file is publicly reachable, issue the certificate. The API answers the ACME challenge, waits for issuance, and returns a download URL when the order is complete.
curl --request POST https://your-host/ssl/gen/$SESSION_ID/cert200 OK{ "session_id": "opaque-random-session-id", "status": "issued", "download_url": "/ssl/download/opaque-random-session-id/" }Still processing? A
202 Acceptedresponse includesstatus: "pending_ca". Retry the same certificate endpoint; do not create a new order. -
04
Download and protect the bundle
Download the archive before the session expires. Most web servers use
fullchain.pemandprivate_key.pem; secure the private key immediately after download.curl --location \ --output example.com-certificate.zip \ https://your-host/ssl/download/$SESSION_ID/
Endpoint reference
A small API with a complete certificate lifecycle.
/ssl/gen201Create a certificate session and receive a challenge URL. The default verification mode is DNS.
| Field | Required | Details |
|---|---|---|
domain | Yes | One domain name, including *.example.com for a wildcard. |
verification | No | dns, dns-01, http, or http-01. Defaults to dns. |
email | No | Optional contact address for the Let's Encrypt account. |
accept_terms | Yes | Must be true after accepting the Let's Encrypt Subscriber Agreement. |
/ssl/gen/{session_id}/challenge/200Retrieve the exact DNS TXT record or HTTP file URL, path, and content required to prove domain control.
/ssl/gen/{session_id}/challenge/verify/200Run an optional, advisory preflight check. A successful preflight is useful signal, but it does not replace Let's Encrypt validation.
/ssl/gen/{session_id}/cert200 / 202Answer the ACME challenge and request the certificate. Retry this endpoint if the API returns 202 Accepted while the CA is still processing.
/ssl/download/{session_id}/200Download the finished ZIP archive. The endpoint is available only after the session reaches issued.
Response guide
Handle status codes intentionally.
- 201 / 200
- The request completed. Read the response for the next URL or artifact.
- 202
- Let's Encrypt is still processing. Retry the same
/certrequest. - 409
- The session is in a conflicting lifecycle state, such as an issuance already in progress.
- 404
- No active session matches the supplied session ID.
- 422
- Fix invalid input, a rejected challenge, or missing agreement confirmation before retrying.
- 502
- An ACME or network operation failed. Inspect the detail and retry the active session when appropriate.
- 410
- The session has expired. Start a new certificate session.
Code examples
Start with the language your workflow already speaks.
JavaScript
Create a session and fetch its validation instructions.
const baseUrl = "https://your-host";
const sessionResponse = await fetch(`${baseUrl}/ssl/gen`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
domain: "example.com",
verification: "dns",
accept_terms: true
})
});
if (!sessionResponse.ok) throw new Error("Session failed");
const session = await sessionResponse.json();
const challenge = await fetch(`${baseUrl}${session.challenge_url}`)
.then(response => response.json());
console.log(challenge.record);
Python
Read the DNS record that your DNS automation needs to publish.
import requests
base_url = "https://your-host"
session_response = requests.post(
f"{base_url}/ssl/gen",
json={
"domain": "example.com",
"verification": "dns",
"accept_terms": True,
},
)
session_response.raise_for_status()
session = session_response.json()
challenge = requests.get(
f"{base_url}/ssl/gen/{session['session_id']}/challenge/"
).json()
print(challenge["record"])
After your DNS provider or web server publishes the proof, call the certificate endpoint shown in the quick start. Keep the session ID out of logs, tickets, browser URLs, and untrusted build output.
Production checklist
Make the first successful run boring.
A staging certificate is intentionally untrusted by browsers, but its validation flow matches production.
Begin in staging. The server defaults to SSL_ACME_ENV=staging. Validate your DNS or HTTP automation there before using production rate limits.
Use DNS-01 for wildcards. Certificates such as *.example.com cannot use HTTP-01 validation.
Allow propagation time. Public DNS and HTTP visibility are what matter to Let's Encrypt, not only what your internal network can resolve.
Retry pending issuance. When POST /cert returns 202, retry that exact endpoint instead of creating another ACME order.
Store the archive safely. Move the ZIP and its unencrypted private_key.pem into your normal secret and deployment controls immediately.
Security by default, responsibility by design
A session ID is a credential. Treat it like one.
The certificate private key is generated inside the server-side session and is not sent to Let's Encrypt; the CA receives a certificate signing request instead. The resulting private key is included in the downloadable archive, so the session ID can grant access to sensitive material while the session is active.
Review the complete OpenAPI contractUse HTTPS
Terminate TLS before exposing the API. Do not send session IDs or certificate bundles over an untrusted connection.
Add your access layer
The supplied API has no built-in user authentication. Put authentication, authorization, or trusted-network controls in front of it before wider exposure.
Keep state private
Never place SSL_API_STATE_DIR under a static web root, shared folder, or source repository. Protect it with owner-only access.
Plan for expiry
Sessions become unavailable after their retention deadline. Expired records and session directories are removed during later session cleanup.
Frequently asked questions
The practical details before you integrate.
Is the SSL Generator API really free?
Yes. The supplied workflow has no paid plan, subscription, or account requirement. Let's Encrypt also provides certificates at no cost. Start against staging so testing does not consume production rate limits.
Does the API need an API key?
The supplied API does not require one. That makes it important to add your own authentication or a trusted network boundary before making the service accessible outside a controlled environment.
Can I issue a wildcard certificate?
Yes. Submit a domain such as *.example.com and use DNS validation. Let's Encrypt does not allow wildcard issuance through HTTP-01.
What files are in the completed download?
The ZIP includes the server certificate, unencrypted private key, intermediate certificate, root CA, combined full chain, and deployment notes. Most servers use fullchain.pem with private_key.pem.
What should I do if issuance returns 202?
Let's Encrypt is still processing the order. Wait briefly and retry the same POST /ssl/gen/{session_id}/cert request. Do not begin another session for the same in-progress order.
One API. No certificate tax.
Start with a free staging request.
Prove the integration, publish the challenge, and make certificate issuance one less manual task in your workflow.