Certificates
TLS certificates InfraNest tracks or issues, and when they expire.
Common tasks
Only in the app: Ordering and renewing certificates.
How it works in the app: help article.
Scopes: Read with domains.read or monitoring.read, change with monitoring.write. See Tokens and scopes.
Certificates 15
Every endpoint below takes a token in the Authorization: Bearer header and answers JSON. Errors use the shared format in Errors. Base address: https://api.infranest.io/api.
Certificates
List certificates
Every TLS certificate in the organization, expired and expiring first, each with its health, findings and who renews it. Narrow it with domain_id or monitoring_check_id; the list is not paginated.
/organizations/{organization}/certificatesmonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
[
{
"id": 10,
"organization_id": 1,
"source": "manual",
"monitoring_check_id": null,
"domain_id": null,
"cloud_server_id": null,
"provider_credential_id": null,
"fingerprint": "61e58004edb797c08c1261beb441b56e31cc903bde63441cd503dca9aa79f099",
"subject": "vpn.northwind.example",
"issuer": "vpn.northwind.example",
"serial": "EAABC774",
"sans": [
"vpn.northwind.example"
],
"not_before": "2025-01-03T12:00:00.000000Z",
"not_after": "2026-01-03T12:00:00.000000Z",
"purpose": "tls_server",
"key_algo": "rsa",
"key_bits": 4096,
"sig_algo": "SHA256-RSA",
"trust": "self_signed",
"validation": "OV",
"is_wildcard": false,
"is_multi_san": false,
"managed": false,
"renewal_source": null,
"classification_override": null,
"status": "expired",
"superseded_by_id": null,
"dismissed_at": null,
"deployment_locations": [],
"owner": null,
"notes": null,
"first_seen_at": "2025-11-16T12:00:00.000000Z",
"last_seen_at": "2026-01-15T06:00:00.000000Z",
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"subject_org": null,
"subject_location": null,
"chain": null,
"chain_status": null,
"sources": [
{
"source": "manual",
"last_seen": "2026-01-15T06:00:00+00:00",
"first_seen": "2025-11-16T12:00:00+00:00"
}
],
"crl_url": null,
"ocsp_url": null,
"caa_status": null,
"caissuer_url": null,
"no_auto_merge": false,
"price_yearly": "0.00",
"price_source": "manual",
"price_currency": "EUR",
"billing_interval": "yearly",
"interval_count": 1,
"renewal_type": "internal",
"renewal_type_basis": "guess",
"served_status": null,
"served_checked_at": null,
"subscription_ends_at": null,
"seller": null,
"role": null,
"ct_finding": null,
"owner_user_id": null,
"custom_fields": [],
"renewer": null,
"findings": [
{
"tier": "problem",
"code": "expired",
"severity": "critical",
"subject": {
"type": "certificate",
"id": 10,
"name": "vpn.northwind.example"
},
"certificate_ids": [
10
],
"addresses": [],
"attention_key": "certificate:10",
"due_at": "2026-01-03T12:00:00+00:00",
"evidence": {
"expires_at": "2026-01-03T12:00:00+00:00",
"days_left": -12,
"renews_itself": false,
"renewal_type": "internal",
"renewal_type_basis": "guess",
"renewal_source": null,
"issuer": "vpn.northwind.example",
"served_status": null,
"role": null,
"renewer": null
},
"copy": {
"title": {
"key": "certificates.rules.expired.title",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
},
"detail": {
"key": "certificates.rules.expired.detail",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
},
"short": {
"key": "certificates.rules.expired.short",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.expired.fix",
"route": "/certificates/10"
},
"dismissible": false,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
}
],
"finding": {
"tier": "problem",
"code": "expired",
"severity": "critical",
"subject": {
"type": "certificate",
"id": 10,
"name": "vpn.northwind.example"
},
"certificate_ids": [
10
],
"addresses": [],
"attention_key": "certificate:10",
"due_at": "2026-01-03T12:00:00+00:00",
"evidence": {
"expires_at": "2026-01-03T12:00:00+00:00",
"days_left": -12,
"renews_itself": false,
"renewal_type": "internal",
"renewal_type_basis": "guess",
"renewal_source": null,
"issuer": "vpn.northwind.example",
"served_status": null,
"role": null,
"renewer": null
},
"copy": {
"title": {
"key": "certificates.rules.expired.title",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
},
"detail": {
"key": "certificates.rules.expired.detail",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
},
"short": {
"key": "certificates.rules.expired.short",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.expired.fix",
"route": "/certificates/10"
},
"dismissible": false,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
},
"health": {
"grade": "warning",
"letter": null,
"issues": [
{
"code": "self_signed",
"severity": "warning"
}
]
},
"in_use": false,
"tags": [],
"check": null,
"domain": null
},
{
"id": 2,
"organization_id": 1,
"source": "probe",
"monitoring_check_id": 2,
"domain_id": 3,
"cloud_server_id": null,
"provider_credential_id": null,
"fingerprint": "b6b045177e3469ad718546c6233a2b7f0166d10e466deeaa6d16f6244f6654db",
"subject": "northwind-app.dev",
"issuer": "Let's Encrypt (R11)",
"serial": "B4B32EC4",
"sans": [
"northwind-app.dev",
"*.northwind-app.dev"
],
"not_before": "2025-11-07T12:00:00.000000Z",
"not_after": "2026-02-05T12:00:00.000000Z",
"purpose": "tls_server",
"key_algo": "ecdsa",
"key_bits": 256,
"sig_algo": "ECDSA-SHA256",
"trust": "public",
"validation": "DV",
"is_wildcard": true,
"is_multi_san": true,
"managed": true,
"renewal_source": "Let's Encrypt (R11)",
"classification_override": null,
"status": "expiring_soon",
"superseded_by_id": null,
"dismissed_at": null,
"deployment_locations": [
{
"endpoint": "northwind-app.dev:443",
"last_seen": "2026-01-15T11:50:00+00:00",
"first_seen": "2026-01-08T12:00:00+00:00"
}
],
"owner": null,
"notes": null,
"first_seen_at": "2025-11-16T12:00:00.000000Z",
"last_seen_at": "2026-01-15T06:00:00.000000Z",
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"subject_org": null,
"subject_location": null,
"chain": [
{
"subject": "northwind-app.dev",
"issuer": "Let's Encrypt (R11)",
"is_ca": false,
"self_signed": false,
"not_after": "2026-02-05T12:00:00+00:00",
"fingerprint": "b6b045177e3469ad718546c6233a2b7f0166d10e466deeaa6d16f6244f6654db"
},
{
"subject": "Let's Encrypt (R11)",
"issuer": "ISRG Root X1",
"is_ca": true,
"self_signed": false,
"not_after": "2030-01-15T12:00:00+00:00",
"fingerprint": "c12c8a7dcb3f10b2cfd6efb6d14e6516f76cb6d3537ffae6a1f804b618d0cf3c"
}
],
"chain_status": "trusted",
"sources": [
{
"source": "probe",
"last_seen": "2026-01-15T06:00:00+00:00",
"first_seen": "2025-11-16T12:00:00+00:00"
}
],
"crl_url": null,
"ocsp_url": null,
"caa_status": null,
"caissuer_url": "http://r11.i.lencr.org/",
"no_auto_merge": false,
"price_yearly": "0.00",
"price_source": "manual",
"price_currency": "EUR",
"billing_interval": "yearly",
"interval_count": 1,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"served_status": "served",
"served_checked_at": "2026-01-15T11:50:00.000000Z",
"subscription_ends_at": null,
"seller": null,
"role": null,
"ct_finding": null,
"owner_user_id": null,
"custom_fields": [],
"renewer": null,
"findings": [
{
"tier": "problem",
"code": "renewal_overdue",
"severity": "warning",
"subject": {
"type": "certificate",
"id": 2,
"name": "northwind-app.dev"
},
"certificate_ids": [
2
],
"addresses": [
"northwind-app.dev:443"
],
"attention_key": "certificate_renewal:2",
"due_at": "2026-02-05T12:00:00+00:00",
"evidence": {
"expires_at": "2026-02-05T12:00:00+00:00",
"days_left": 21,
"renews_itself": true,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"renewal_source": "Let's Encrypt (R11)",
"issuer": "Let's Encrypt (R11)",
"served_status": "served",
"role": null,
"renewer": null,
"renews_around_at": "2026-01-06T00:00:00+00:00",
"last_seen_at": "2026-01-15T06:00:00+00:00"
},
"copy": {
"title": {
"key": "certificates.rules.renewal_overdue.title",
"params": {
"renews_at": "2026-01-06T00:00:00+00:00",
"expires_at": "2026-02-05T12:00:00+00:00",
"n": 9,
"name": "northwind-app.dev"
}
},
"detail": {
"key": "certificates.rules.renewal_overdue.detail",
"params": {
"renews_at": "2026-01-06T00:00:00+00:00",
"expires_at": "2026-02-05T12:00:00+00:00",
"n": 9,
"name": "northwind-app.dev"
}
},
"short": {
"key": "certificates.rules.renewal_overdue.short",
"params": {
"renews_at": "2026-01-06T00:00:00+00:00",
"expires_at": "2026-02-05T12:00:00+00:00",
"n": 9,
"name": "northwind-app.dev"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.renewal_overdue.fix",
"route": "/certificates/2"
},
"dismissible": true,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
}
],
"finding": {
"tier": "problem",
"code": "renewal_overdue",
"severity": "warning",
"subject": {
"type": "certificate",
"id": 2,
"name": "northwind-app.dev"
},
"certificate_ids": [
2
],
"addresses": [
"northwind-app.dev:443"
],
"attention_key": "certificate_renewal:2",
"due_at": "2026-02-05T12:00:00+00:00",
"evidence": {
"expires_at": "2026-02-05T12:00:00+00:00",
"days_left": 21,
"renews_itself": true,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"renewal_source": "Let's Encrypt (R11)",
"issuer": "Let's Encrypt (R11)",
"served_status": "served",
"role": null,
"renewer": null,
"renews_around_at": "2026-01-06T00:00:00+00:00",
"last_seen_at": "2026-01-15T06:00:00+00:00"
},
"copy": {
"title": {
"key": "certificates.rules.renewal_overdue.title",
"params": {
"renews_at": "2026-01-06T00:00:00+00:00",
"expires_at": "2026-02-05T12:00:00+00:00",
"n": 9,
"name": "northwind-app.dev"
}
},
"detail": {
"key": "certificates.rules.renewal_overdue.detail",
"params": {
"renews_at": "2026-01-06T00:00:00+00:00",
"expires_at": "2026-02-05T12:00:00+00:00",
"n": 9,
"name": "northwind-app.dev"
}
},
"short": {
"key": "certificates.rules.renewal_overdue.short",
"params": {
"renews_at": "2026-01-06T00:00:00+00:00",
"expires_at": "2026-02-05T12:00:00+00:00",
"n": 9,
"name": "northwind-app.dev"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.renewal_overdue.fix",
"route": "/certificates/2"
},
"dismissible": true,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
},
"health": {
"grade": "good",
"letter": "A",
"issues": []
},
"in_use": true,
"tags": [
{
"id": 6,
"name": "Northwind Platform",
"color": "#14b8a6",
"pivot": {
"taggable_type": "App\\Models\\SslCertificate",
"taggable_id": 2,
"tag_id": 6
}
}
],
"check": {
"id": 2,
"name": "App — northwind-app.dev",
"target": "https://northwind-app.dev",
"interval_minutes": 0
},
"domain": {
"id": 3,
"name": "northwind-app",
"tld": "dev",
"full_name": "northwind-app.dev",
"archivable": true,
"dns_host": null,
"management": "tracked",
"sync_halt": null
}
}
]{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Get a certificate
One certificate with its domain, server, monitor, owner and tags, where it is served and its place in the renewal series. Also includes its findings and whether its files can be fetched from the provider.
/organizations/{organization}/certificates/{sslCertificate}monitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/1" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/1" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/1', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/1",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/1", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"id": 1,
"organization_id": 1,
"source": "probe",
"monitoring_check_id": 1,
"domain_id": 1,
"cloud_server_id": null,
"provider_credential_id": null,
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9",
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"serial": "2395BA42",
"sans": [
"northwind.com",
"www.northwind.com"
],
"not_before": "2025-12-30T12:00:00.000000Z",
"not_after": "2026-03-30T12:00:00.000000Z",
"purpose": "tls_server",
"key_algo": "rsa",
"key_bits": 2048,
"sig_algo": "SHA256-RSA",
"trust": "public",
"validation": "DV",
"is_wildcard": false,
"is_multi_san": true,
"managed": true,
"renewal_source": "Let's Encrypt (R3)",
"classification_override": null,
"status": "valid",
"superseded_by_id": null,
"dismissed_at": null,
"deployment_locations": [
{
"endpoint": "northwind.com:443",
"last_seen": "2026-01-15T11:50:00+00:00",
"first_seen": "2026-01-08T12:00:00+00:00"
}
],
"owner": null,
"notes": null,
"first_seen_at": "2025-11-16T12:00:00.000000Z",
"last_seen_at": "2026-01-15T06:00:00.000000Z",
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"subject_org": null,
"subject_location": null,
"chain": [
{
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"is_ca": false,
"self_signed": false,
"not_after": "2026-03-30T12:00:00+00:00",
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9"
},
{
"subject": "Let's Encrypt (R3)",
"issuer": "ISRG Root X1",
"is_ca": true,
"self_signed": false,
"not_after": "2030-01-15T12:00:00+00:00",
"fingerprint": "e04ca19ed66485fb479283c98fbfb4cda1ab67de1965304902bec3f6a55a2c8e"
}
],
"chain_status": "trusted",
"sources": [
{
"source": "probe",
"last_seen": "2026-01-15T06:00:00+00:00",
"first_seen": "2025-11-16T12:00:00+00:00"
}
],
"crl_url": null,
"ocsp_url": null,
"caa_status": "ok",
"caissuer_url": "http://r3.i.lencr.org/",
"no_auto_merge": false,
"price_yearly": "0.00",
"price_source": "manual",
"price_currency": "EUR",
"billing_interval": "yearly",
"interval_count": 1,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"served_status": "served",
"served_checked_at": "2026-01-15T11:50:00.000000Z",
"subscription_ends_at": null,
"seller": null,
"role": null,
"ct_finding": null,
"owner_user_id": null,
"health": {
"grade": "good",
"letter": "A",
"issues": []
},
"in_use": true,
"tags": [
{
"id": 5,
"name": "Northwind Retail",
"color": "#0ea5e9",
"pivot": {
"taggable_type": "App\\Models\\SslCertificate",
"taggable_id": 1,
"tag_id": 5
}
}
],
"check": {
"id": 1,
"name": "Marketing site",
"target": "https://northwind.com",
"interval_minutes": 0
},
"domain": {
"id": 1,
"name": "northwind",
"tld": "com",
"full_name": "northwind.com",
"archivable": true,
"dns_host": null,
"management": "tracked",
"sync_halt": null
},
"cloud_server": null,
"provider_credential": null,
"superseded_by": null,
"owner_user": null,
"owner_candidates": [
{
"id": 1,
"name": "Alex Rivera"
},
{
"id": 3,
"name": "Priya Raghunathan"
}
],
"bundle_available": false,
"whereabouts": {
"role": "current",
"current_certificate": null,
"renewer": null,
"served_on": [
{
"endpoint_id": 1,
"host": "northwind.com",
"port": 443,
"as": "edge",
"proxied": true,
"last_checked_at": "2026-01-15T11:50:00+00:00",
"last_check_source": "scan",
"zone": {
"id": 1,
"name": "northwind.com",
"domain_id": 1
},
"dns_record": {
"id": 1,
"type": "A",
"name": "@",
"value": "198.51.100.10"
},
"cloud_server": {
"id": 1,
"name": "web-01"
},
"monitor": {
"id": 1,
"name": "Marketing site"
},
"renewer": null
}
],
"lifecycle": {
"previous": null,
"next": null,
"replaced_at": null,
"renewals": 0,
"lead_days": null,
"expected_renewal_at": "2026-02-28T12:00:00+00:00"
},
"rollout": null
},
"findings": []
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Update a certificate
Changes InfraNest’s record of the certificate: owner, notes, subscription end date, seller, tags and any corrections to how it is classified (purpose, trust, validation, managed, renewal type). Only the fields you send change; owner_user_id must be a member of the organization.
/organizations/{organization}/certificates/{sslCertificate}monitoring.writeChanges dataAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Bodyapplication/json
ownerstring, nullableoptionalMust not be greater than 255 characters.
owner_user_idinteger, nullableoptionalA member who owns it; checked against the organization below.
notesstring, nullableoptionalMust not be greater than 2000 characters.
subscription_ends_atstring, nullableoptionalA paid certificate's subscription and who sold it. Must be a valid date.
sellerstring, nullableoptionalMust not be greater than 255 characters.
classification_overrideobject, nullableoptionaltag_idsarrayoptionalRequest
curl -X PATCH "https://api.infranest.io/api/organizations/42/certificates/1" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"owner":"Platform team","notes":"Renewed automatically by the load balancer."}'$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
$body = @'
{
"owner": "Platform team",
"notes": "Renewed automatically by the load balancer."
}
'@
Invoke-RestMethod -Method Patch -Uri "https://api.infranest.io/api/organizations/42/certificates/1" -Headers $headers -ContentType "application/json" -Body $body<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('PATCH', 'https://api.infranest.io/api/organizations/42/certificates/1', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
'json' => [
'owner' => 'Platform team',
'notes' => 'Renewed automatically by the load balancer.',
],
]);
echo $response->getBody();import os
import requests
response = requests.patch(
"https://api.infranest.io/api/organizations/42/certificates/1",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
json={
"owner": "Platform team",
"notes": "Renewed automatically by the load balancer.",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"owner": "Platform team",
"notes": "Renewed automatically by the load balancer."
}),
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
req, err := http.NewRequest("PATCH", "https://api.infranest.io/api/organizations/42/certificates/1", strings.NewReader(`{"owner":"Platform team","notes":"Renewed automatically by the load balancer."}`))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"id": 1,
"organization_id": 1,
"source": "probe",
"monitoring_check_id": 1,
"domain_id": 1,
"cloud_server_id": null,
"provider_credential_id": null,
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9",
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"serial": "2395BA42",
"sans": [
"northwind.com",
"www.northwind.com"
],
"not_before": "2025-12-30T12:00:00.000000Z",
"not_after": "2026-03-30T12:00:00.000000Z",
"purpose": "tls_server",
"key_algo": "rsa",
"key_bits": 2048,
"sig_algo": "SHA256-RSA",
"trust": "public",
"validation": "DV",
"is_wildcard": false,
"is_multi_san": true,
"managed": true,
"renewal_source": "Let's Encrypt (R3)",
"classification_override": null,
"status": "valid",
"superseded_by_id": null,
"dismissed_at": null,
"deployment_locations": [
{
"endpoint": "northwind.com:443",
"last_seen": "2026-01-15T11:50:00+00:00",
"first_seen": "2026-01-08T12:00:00+00:00"
}
],
"owner": "Platform team",
"notes": "Renewed automatically by the load balancer.",
"first_seen_at": "2025-11-16T12:00:00.000000Z",
"last_seen_at": "2026-01-15T06:00:00.000000Z",
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"subject_org": null,
"subject_location": null,
"chain": [
{
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"is_ca": false,
"self_signed": false,
"not_after": "2026-03-30T12:00:00+00:00",
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9"
},
{
"subject": "Let's Encrypt (R3)",
"issuer": "ISRG Root X1",
"is_ca": true,
"self_signed": false,
"not_after": "2030-01-15T12:00:00+00:00",
"fingerprint": "e04ca19ed66485fb479283c98fbfb4cda1ab67de1965304902bec3f6a55a2c8e"
}
],
"chain_status": "trusted",
"sources": [
{
"source": "probe",
"last_seen": "2026-01-15T06:00:00+00:00",
"first_seen": "2025-11-16T12:00:00+00:00"
}
],
"crl_url": null,
"ocsp_url": null,
"caa_status": "ok",
"caissuer_url": "http://r3.i.lencr.org/",
"no_auto_merge": false,
"price_yearly": "0.00",
"price_source": "manual",
"price_currency": "EUR",
"billing_interval": "yearly",
"interval_count": 1,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"served_status": "served",
"served_checked_at": "2026-01-15T11:50:00.000000Z",
"subscription_ends_at": null,
"seller": null,
"role": null,
"ct_finding": null,
"owner_user_id": null,
"tags": [
{
"id": 5,
"name": "Northwind Retail",
"color": "#0ea5e9",
"pivot": {
"taggable_type": "App\\Models\\SslCertificate",
"taggable_id": 1,
"tag_id": 5
}
}
],
"owner_user": null
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.write scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.write",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "validation_failed",
"message": "The owner field is required.",
"hint": "Fix the fields listed in \"errors\" and send the request again.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#validation_failed",
"errors": {
"owner": [
"The owner field is required."
]
},
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied write_disabled organization_suspended
404 not_found
422 validation_failed
429 rate_limited
Get a certificate’s files from the provider
Asks the provider the certificate came from for its files and returns the files; each fetch is recorded in your audit log. Requires write access, and is refused when the certificate did not come from a connected provider that can hand files back or is not linked to a domain.
/organizations/{organization}/certificates/{sslCertificate}/bundlemonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/1/bundle" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/1/bundle" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/1/bundle', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/1/bundle",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1/bundle', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/1/bundle", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
No success response is recorded for this endpoint yet.
Preview the CAA fix for a certificate
The exact CAA record that would let this certificate’s authority issue it again, the DNS zone it would go in, the CAA records already there and any other certificate it would block. Refused when the certificate’s CAA policy does not block its renewal; nothing is changed.
/organizations/{organization}/certificates/{sslCertificate}/caa-fixmonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/1/caa-fix" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/1/caa-fix" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/1/caa-fix', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/1/caa-fix",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1/caa-fix', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/1/caa-fix", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
No success response is recorded for this endpoint yet.
Dismiss or restore a certificate
Sets the certificate aside so it no longer counts towards domain coverage. Send dismissed: false to restore it.
/organizations/{organization}/certificates/{sslCertificate}/dismissmonitoring.writeChanges dataAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Request
curl -X POST "https://api.infranest.io/api/organizations/42/certificates/1/dismiss" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Post -Uri "https://api.infranest.io/api/organizations/42/certificates/1/dismiss" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.infranest.io/api/organizations/42/certificates/1/dismiss', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.post(
"https://api.infranest.io/api/organizations/42/certificates/1/dismiss",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1/dismiss', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("POST", "https://api.infranest.io/api/organizations/42/certificates/1/dismiss", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"id": 1,
"organization_id": 1,
"source": "probe",
"monitoring_check_id": 1,
"domain_id": 1,
"cloud_server_id": null,
"provider_credential_id": null,
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9",
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"serial": "2395BA42",
"sans": [
"northwind.com",
"www.northwind.com"
],
"not_before": "2025-12-30T12:00:00.000000Z",
"not_after": "2026-03-30T12:00:00.000000Z",
"purpose": "tls_server",
"key_algo": "rsa",
"key_bits": 2048,
"sig_algo": "SHA256-RSA",
"trust": "public",
"validation": "DV",
"is_wildcard": false,
"is_multi_san": true,
"managed": true,
"renewal_source": "Let's Encrypt (R3)",
"classification_override": null,
"status": "valid",
"superseded_by_id": null,
"dismissed_at": "2026-01-15T12:00:00.000000Z",
"deployment_locations": [
{
"endpoint": "northwind.com:443",
"last_seen": "2026-01-15T11:50:00+00:00",
"first_seen": "2026-01-08T12:00:00+00:00"
}
],
"owner": null,
"notes": null,
"first_seen_at": "2025-11-16T12:00:00.000000Z",
"last_seen_at": "2026-01-15T06:00:00.000000Z",
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"subject_org": null,
"subject_location": null,
"chain": [
{
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"is_ca": false,
"self_signed": false,
"not_after": "2026-03-30T12:00:00+00:00",
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9"
},
{
"subject": "Let's Encrypt (R3)",
"issuer": "ISRG Root X1",
"is_ca": true,
"self_signed": false,
"not_after": "2030-01-15T12:00:00+00:00",
"fingerprint": "e04ca19ed66485fb479283c98fbfb4cda1ab67de1965304902bec3f6a55a2c8e"
}
],
"chain_status": "trusted",
"sources": [
{
"source": "probe",
"last_seen": "2026-01-15T06:00:00+00:00",
"first_seen": "2025-11-16T12:00:00+00:00"
}
],
"crl_url": null,
"ocsp_url": null,
"caa_status": "ok",
"caissuer_url": "http://r3.i.lencr.org/",
"no_auto_merge": false,
"price_yearly": "0.00",
"price_source": "manual",
"price_currency": "EUR",
"billing_interval": "yearly",
"interval_count": 1,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"served_status": "served",
"served_checked_at": "2026-01-15T11:50:00.000000Z",
"subscription_ends_at": null,
"seller": null,
"role": null,
"ct_finding": null,
"owner_user_id": null,
"tags": [
{
"id": 5,
"name": "Northwind Retail",
"color": "#0ea5e9",
"pivot": {
"taggable_type": "App\\Models\\SslCertificate",
"taggable_id": 1,
"tag_id": 5
}
}
]
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.write scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.write",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "idempotency_in_progress",
"message": "A request with this Idempotency-Key is still being handled.",
"hint": "Wait a few seconds, then send the same request with the same key again to get its result.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#idempotency_in_progress",
"reference": "k3j9x0b2qa"
}{
"code": "validation_failed",
"message": "The name field is required.",
"hint": "Fix the fields listed in \"errors\" and send the request again.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#validation_failed",
"errors": {
"name": [
"The name field is required."
]
},
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied write_disabled organization_suspended
404 not_found
409 idempotency_in_progress
422 validation_failed idempotency_key_reused
429 rate_limited
Set a certificate’s price
Sets the yearly price InfraNest counts for this certificate, with optional currency and billing interval. Send an empty price to go back to the provider’s figure.
/organizations/{organization}/certificates/{sslCertificate}/pricingmonitoring.writeChanges dataAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Request
curl -X PUT "https://api.infranest.io/api/organizations/42/certificates/1/pricing" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"price_yearly":59,"currency":"EUR"}'$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
$body = @'
{
"price_yearly": 59,
"currency": "EUR"
}
'@
Invoke-RestMethod -Method Put -Uri "https://api.infranest.io/api/organizations/42/certificates/1/pricing" -Headers $headers -ContentType "application/json" -Body $body<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('PUT', 'https://api.infranest.io/api/organizations/42/certificates/1/pricing', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
'json' => [
'price_yearly' => 59,
'currency' => 'EUR',
],
]);
echo $response->getBody();import os
import requests
response = requests.put(
"https://api.infranest.io/api/organizations/42/certificates/1/pricing",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
json={
"price_yearly": 59,
"currency": "EUR",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1/pricing', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"price_yearly": 59,
"currency": "EUR"
}),
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
req, err := http.NewRequest("PUT", "https://api.infranest.io/api/organizations/42/certificates/1/pricing", strings.NewReader(`{"price_yearly":59,"currency":"EUR"}`))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"id": 1,
"organization_id": 1,
"source": "probe",
"monitoring_check_id": 1,
"domain_id": 1,
"cloud_server_id": null,
"provider_credential_id": null,
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9",
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"serial": "2395BA42",
"sans": [
"northwind.com",
"www.northwind.com"
],
"not_before": "2025-12-30T12:00:00.000000Z",
"not_after": "2026-03-30T12:00:00.000000Z",
"purpose": "tls_server",
"key_algo": "rsa",
"key_bits": 2048,
"sig_algo": "SHA256-RSA",
"trust": "public",
"validation": "DV",
"is_wildcard": false,
"is_multi_san": true,
"managed": true,
"renewal_source": "Let's Encrypt (R3)",
"classification_override": null,
"status": "valid",
"superseded_by_id": null,
"dismissed_at": null,
"deployment_locations": [
{
"endpoint": "northwind.com:443",
"last_seen": "2026-01-15T11:50:00+00:00",
"first_seen": "2026-01-08T12:00:00+00:00"
}
],
"owner": null,
"notes": null,
"first_seen_at": "2025-11-16T12:00:00.000000Z",
"last_seen_at": "2026-01-15T06:00:00.000000Z",
"created_at": "2026-01-15T12:00:00.000000Z",
"updated_at": "2026-01-15T12:00:00.000000Z",
"subject_org": null,
"subject_location": null,
"chain": [
{
"subject": "northwind.com",
"issuer": "Let's Encrypt (R3)",
"is_ca": false,
"self_signed": false,
"not_after": "2026-03-30T12:00:00+00:00",
"fingerprint": "6f6f72db300125c26f8b2c866124ffb57e60a201177144ed5e5b6711eba24cb9"
},
{
"subject": "Let's Encrypt (R3)",
"issuer": "ISRG Root X1",
"is_ca": true,
"self_signed": false,
"not_after": "2030-01-15T12:00:00+00:00",
"fingerprint": "e04ca19ed66485fb479283c98fbfb4cda1ab67de1965304902bec3f6a55a2c8e"
}
],
"chain_status": "trusted",
"sources": [
{
"source": "probe",
"last_seen": "2026-01-15T06:00:00+00:00",
"first_seen": "2025-11-16T12:00:00+00:00"
}
],
"crl_url": null,
"ocsp_url": null,
"caa_status": "ok",
"caissuer_url": "http://r3.i.lencr.org/",
"no_auto_merge": false,
"price_yearly": "59.00",
"price_source": "manual",
"price_currency": "EUR",
"billing_interval": "yearly",
"interval_count": 1,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"served_status": "served",
"served_checked_at": "2026-01-15T11:50:00.000000Z",
"subscription_ends_at": null,
"seller": null,
"role": null,
"ct_finding": null,
"owner_user_id": null
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.write scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.write",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "validation_failed",
"message": "The name field is required.",
"hint": "Fix the fields listed in \"errors\" and send the request again.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#validation_failed",
"errors": {
"name": [
"The name field is required."
]
},
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied write_disabled organization_suspended
404 not_found
422 validation_failed
429 rate_limited
Get a certificate’s timeline
The certificate’s history across its renewals: when it was issued, renewed and expired, plus the changes recorded in your audit log. For certificates you renew and pay for yourself, it also marks the day to order the renewal and when the subscription ends.
/organizations/{organization}/certificates/{sslCertificate}/timelinemonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
sslCertificateintegerrequiredThe SSL certificate's ID.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/1/timeline" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/1/timeline" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/1/timeline', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/1/timeline",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/1/timeline', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/1/timeline", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"events": [
{
"type": "issued",
"at": "2025-12-30T12:00:00+00:00",
"fingerprint": "6f6f72db3001"
}
],
"generations": 1,
"markers": []
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Get certificate coverage per domain
For each of your domains, whether a certificate covers it (covered, expiring, expired or uncovered) and the best certificate that does. Superseded and dismissed certificates are left out.
/organizations/{organization}/certificates/coveragemonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/coverage" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/coverage" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/coverage', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/coverage",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/coverage', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/coverage", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
[
{
"domain_id": 11,
"domain": "northwind.cloud",
"status": "covered",
"certificate": {
"id": 6,
"subject": "*.northwind.cloud",
"not_after": "2026-03-17T12:00:00.000000Z",
"status": "valid",
"source": "hetzner"
}
},
{
"domain_id": 1,
"domain": "northwind.com",
"status": "covered",
"certificate": {
"id": 8,
"subject": "cdn.northwind.com",
"not_after": "2026-11-11T12:00:00.000000Z",
"status": "valid",
"source": "cloudflare"
}
}
]{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
List certificate findings
Everything InfraNest found about your certificates that may need attention, from problems to recommendations, with counts per tier.
/organizations/{organization}/certificates/findingsmonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/findings" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/findings" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/findings', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/findings",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/findings', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/findings", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"findings": [
{
"tier": "problem",
"code": "expired",
"severity": "critical",
"subject": {
"type": "certificate",
"id": 10,
"name": "vpn.northwind.example"
},
"certificate_ids": [
10
],
"addresses": [],
"attention_key": "certificate:10",
"due_at": "2026-01-03T12:00:00+00:00",
"evidence": {
"expires_at": "2026-01-03T12:00:00+00:00",
"days_left": -12,
"renews_itself": false,
"renewal_type": "internal",
"renewal_type_basis": "guess",
"renewal_source": null,
"issuer": "vpn.northwind.example",
"served_status": null,
"role": null,
"renewer": null
},
"copy": {
"title": {
"key": "certificates.rules.expired.title",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
},
"detail": {
"key": "certificates.rules.expired.detail",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
},
"short": {
"key": "certificates.rules.expired.short",
"params": {
"expires_at": "2026-01-03T12:00:00+00:00",
"name": "vpn.northwind.example"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.expired.fix",
"route": "/certificates/10"
},
"dismissible": false,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
},
{
"tier": "problem",
"code": "domain_expires_first",
"severity": "warning",
"subject": {
"type": "certificate",
"id": 12,
"name": "northwind.shop"
},
"certificate_ids": [
12
],
"addresses": [
"northwind.shop:443"
],
"attention_key": "domain:10",
"due_at": "2026-01-26T00:00:00+00:00",
"evidence": {
"expires_at": "2026-03-17T12:00:00+00:00",
"days_left": 61,
"renews_itself": true,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"renewal_source": "Let's Encrypt (R11)",
"issuer": "Let's Encrypt (R11)",
"served_status": "served",
"role": null,
"renewer": null,
"domain_id": 10,
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"domain_auto_renew": false
},
"copy": {
"title": {
"key": "certificates.rules.domain_expires_first.title",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
},
"detail": {
"key": "certificates.rules.domain_expires_first.detail",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
},
"short": {
"key": "certificates.rules.domain_expires_first.short",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.domain_expires_first.fix",
"route": "/domains/10"
},
"dismissible": true,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
}
],
"counts": {
"problem": 6,
"recommendation": 6,
"info": 0
}
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Preview domains to add from certificates
The domain names your certificates use that InfraNest does not track yet, where each is registered as far as is known, and the certificates that use it. Also says how many more domains your plan allows; nothing is changed.
/organizations/{organization}/certificates/fixes/domainsdomains.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/fixes/domains" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/fixes/domains" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/fixes/domains', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/fixes/domains",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/fixes/domains', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/fixes/domains", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"domains": [],
"room": null,
"allowed": true
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the domains.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "domains.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
List certificate fixes already applied
The certificate recommendations already acted on, newest first: monitors and domains added from a recommendation and CAA records added to unblock a renewal, with who did it and when.
/organizations/{organization}/certificates/fixes/donemonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/fixes/done" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/fixes/done" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/fixes/done', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/fixes/done",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/fixes/done', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/fixes/done", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"lines": []
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Preview monitors to add for certificates
The addresses InfraNest suggests watching with an SSL monitor, the monitor each would get and the certificates involved. Also says how many more monitors your plan allows; nothing is changed.
/organizations/{organization}/certificates/fixes/monitorsmonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/fixes/monitors" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/fixes/monitors" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/fixes/monitors', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/fixes/monitors",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/fixes/monitors', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/fixes/monitors", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"addresses": [
{
"address": "archive.northwind.com:443",
"host": "archive.northwind.com",
"port": 443,
"certificates": [
{
"id": 18,
"name": "archive.northwind.com"
}
],
"monitor": {
"name": "archive.northwind.com",
"type": "ssl",
"target": "archive.northwind.com",
"interval_seconds": 3600
},
"unsupported": null
},
{
"address": "billing.northwind.nl:443",
"host": "billing.northwind.nl",
"port": 443,
"certificates": [
{
"id": 14,
"name": "billing.northwind.nl"
}
],
"monitor": {
"name": "billing.northwind.nl",
"type": "ssl",
"target": "billing.northwind.nl",
"interval_seconds": 3600
},
"unsupported": null
}
],
"room": null,
"allowed": true
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Get the certificate overview
An address-by-address view of the certificates your visitors actually receive, grouped by domain, with a summary, what needs you, the renewals due in the next 90 days and recommendations. Also lists the members who can be made a certificate’s owner.
/organizations/{organization}/certificates/overviewmonitoring.readRead onlyAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Request
curl "https://api.infranest.io/api/organizations/42/certificates/overview" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json"$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
Invoke-RestMethod -Method Get -Uri "https://api.infranest.io/api/organizations/42/certificates/overview" -Headers $headers<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.infranest.io/api/organizations/42/certificates/overview', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
]);
echo $response->getBody();import os
import requests
response = requests.get(
"https://api.infranest.io/api/organizations/42/certificates/overview",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/overview', {
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
},
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://api.infranest.io/api/organizations/42/certificates/overview", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"summary": {
"state": "needs_you",
"addresses_needing_you": 5,
"other_problems": 1,
"first_problem": {
"endpoint_id": 7
},
"todo": [
{
"endpoint_id": 7,
"host": "northwind.shop",
"port": 443,
"address": "northwind.shop:443",
"finding": {
"tier": "problem",
"code": "domain_expires_first",
"severity": "warning",
"subject": {
"type": "certificate",
"id": 12,
"name": "northwind.shop"
},
"certificate_ids": [
12
],
"addresses": [
"northwind.shop:443"
],
"attention_key": "domain:10",
"due_at": "2026-01-26T00:00:00+00:00",
"evidence": {
"expires_at": "2026-03-17T12:00:00+00:00",
"days_left": 61,
"renews_itself": true,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"renewal_source": "Let's Encrypt (R11)",
"issuer": "Let's Encrypt (R11)",
"served_status": "served",
"role": null,
"renewer": null,
"domain_id": 10,
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"domain_auto_renew": false
},
"copy": {
"title": {
"key": "certificates.rules.domain_expires_first.title",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
},
"detail": {
"key": "certificates.rules.domain_expires_first.detail",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
},
"short": {
"key": "certificates.rules.domain_expires_first.short",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.domain_expires_first.fix",
"route": "/domains/10"
},
"dismissible": true,
"advisor": null,
"decision": null,
"automation": null,
"sources": [],
"also": []
},
"attention_keys": [
"domain:10"
]
}
],
"decided": [],
"addresses": 12,
"domains": 5,
"renew_automatically": 8,
"monitored": 5,
"recommendations": 6,
"watched": 1,
"next_renewal": {
"host": "staging.northwind-app.dev",
"more": 0,
"at": "2026-01-18T00:00:00+00:00",
"who": "automatic"
}
},
"timeline": [
{
"certificate_id": 16,
"subject": "northwind-mail.com",
"issuer": "Sectigo RSA Organization Validation Secure Server CA",
"expires_at": "2026-02-02T12:00:00+00:00",
"renews_at": null,
"who": "you",
"endpoint_ids": [
11
],
"hosts": [
"mx1.northwind-mail.com"
],
"listed": true
}
],
"needs_you": [
{
"kind": "problem",
"at": "2026-01-26T00:00:00+00:00",
"now": false,
"severity": "warning",
"endpoint_id": 7,
"hosts": [
"northwind.shop"
],
"reason": {
"key": "certificates.rules.domain_expires_first.short",
"params": {
"domain": "northwind.shop",
"domain_expires_at": "2026-01-26T00:00:00+00:00",
"expires_at": "2026-03-17T12:00:00+00:00",
"name": "northwind.shop"
}
},
"who": null
}
],
"groups": [
{
"key": "domain:northwind-mail.com",
"domain_id": 16,
"name": "northwind-mail.com",
"in_domains": true,
"rows": [
{
"domain_id": 16,
"endpoint_id": 11,
"host": "mx1.northwind-mail.com",
"port": 993,
"address": "mx1.northwind-mail.com:993",
"status": "problem",
"severity": "warning",
"reason": {
"key": "certificates.rules.rollout_incomplete.short",
"params": {
"address": "mx1.northwind-mail.com:993",
"name": "northwind-mail.com",
"done": 1,
"total": 2,
"expires_at": "2026-02-02T12:00:00+00:00",
"server": "mail-01"
}
},
"check_note": null,
"certificate": {
"id": 16,
"subject": "northwind-mail.com",
"issuer": "Sectigo RSA Organization Validation Secure Server CA",
"not_after": "2026-02-02T12:00:00+00:00",
"status": "superseded",
"role": null
},
"origin_certificate": null,
"served_by": {
"kind": "server",
"provider": "transip",
"provider_label": "TransIP",
"cloud_server": {
"id": 14,
"name": "mail-01"
},
"resolved_ips": [
"198.51.100.166"
],
"edge_network": null,
"network": null,
"display": {
"kind": "server",
"name": "mail-01",
"ip": "198.51.100.166"
},
"dns_record": {
"id": 53,
"type": "A",
"name": "mx1",
"value": "198.51.100.166",
"zone": {
"id": 9,
"name": "northwind-mail.com"
}
}
},
"renewal": {
"type": "manual_paid",
"source": null,
"renews_itself": false,
"actor": "you"
},
"ownership": {
"is": "yours",
"basis": "domain",
"override": null
},
"monitor": null,
"last_checked_at": "2026-01-15T11:50:00+00:00",
"last_check_source": "scan",
"last_attempt_at": "2026-01-15T11:50:00+00:00",
"failing": null,
"findings": [
{
"tier": "problem",
"code": "rollout_incomplete",
"severity": "warning",
"subject": {
"type": "address",
"id": 11,
"name": "mx1.northwind-mail.com:993"
},
"certificate_ids": [
16
],
"addresses": [
"mx1.northwind-mail.com:993"
],
"attention_key": "certificate_rollout:11",
"due_at": "2026-02-02T12:00:00+00:00",
"evidence": {
"endpoint_id": 11,
"serves_certificate_id": 16,
"successor_id": 15,
"expires_at": "2026-02-02T12:00:00+00:00",
"done": 1,
"total": 2,
"last_checked_at": "2026-01-15T11:50:00+00:00"
},
"copy": {
"title": {
"key": "certificates.rules.rollout_incomplete.title",
"params": {
"address": "mx1.northwind-mail.com:993",
"name": "northwind-mail.com",
"done": 1,
"total": 2,
"expires_at": "2026-02-02T12:00:00+00:00"
}
},
"detail": {
"key": "certificates.rules.rollout_incomplete.detailServer",
"params": {
"address": "mx1.northwind-mail.com:993",
"name": "northwind-mail.com",
"done": 1,
"total": 2,
"expires_at": "2026-02-02T12:00:00+00:00",
"server": "mail-01"
},
"count": 2
},
"short": {
"key": "certificates.rules.rollout_incomplete.short",
"params": {
"address": "mx1.northwind-mail.com:993",
"name": "northwind-mail.com",
"done": 1,
"total": 2,
"expires_at": "2026-02-02T12:00:00+00:00",
"server": "mail-01"
}
}
},
"fix": {
"kind": "action",
"label": "certificates.rules.rollout_incomplete.fix",
"action": "certificate.scan",
"method": "POST",
"endpoint": "/organizations/1/certificates/scan",
"body": {
"host": "mx1.northwind-mail.com",
"port": 993
}
},
"dismissible": true,
"advisor": null,
"decision": null,
"automation": {
"template": "cert_rollout_not_live_alert",
"action": "send_notification",
"provider": null,
"connected": true,
"conditions": [],
"title": {
"key": "certificates.automate.cert_rollout_not_live_alert",
"params": {
"days": 0
}
}
},
"sources": [
{
"source": "check",
"tone": "bad",
"copy": {
"key": "certificates.evidence.check.oldCertificate",
"params": []
}
}
],
"also": []
}
],
"set_aside": false
}
],
"count": 2,
"problems": 1,
"next_expiry": "2026-02-02T12:00:00+00:00"
}
],
"watched": [
{
"endpoint_id": 13,
"host": "status.supplier.example",
"port": 443,
"address": "status.supplier.example:443",
"domain_id": null,
"status": "ok",
"severity": null,
"reason": null,
"check_note": null,
"certificate": {
"id": 19,
"subject": "status.supplier.example",
"issuer": "Let's Encrypt (R3)",
"not_after": "2026-03-28T12:00:00+00:00",
"status": "valid",
"role": null
},
"origin_certificate": null,
"served_by": {
"kind": "address",
"provider": null,
"provider_label": null,
"cloud_server": null,
"resolved_ips": [
"203.0.113.210"
],
"edge_network": null,
"network": null,
"display": {
"kind": "ip",
"name": "203.0.113.210",
"ip": "203.0.113.210"
},
"dns_record": null
},
"renewal": {
"type": "automatic",
"source": "Let's Encrypt (R3)",
"renews_itself": true,
"actor": "automatic"
},
"ownership": {
"is": "watched",
"basis": "none",
"override": null
},
"monitor": {
"id": 13,
"name": "Supplier status",
"status": "up",
"last_checked_at": "2026-01-15T11:58:00+00:00"
},
"last_checked_at": "2026-01-15T11:50:00+00:00",
"last_check_source": "scan",
"last_attempt_at": "2026-01-15T11:50:00+00:00",
"failing": null,
"findings": []
}
],
"recommendations": [
{
"tier": "recommendation",
"code": "chain_incomplete",
"severity": "warning",
"subject": {
"type": "certificate",
"id": 4,
"name": "docs.northwind.com"
},
"certificate_ids": [
4
],
"addresses": [
"docs.northwind.com:443"
],
"attention_key": null,
"due_at": null,
"evidence": {
"expires_at": "2026-03-08T12:00:00+00:00",
"days_left": 52,
"renews_itself": true,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"renewal_source": "Let's Encrypt (R11)",
"issuer": "Let's Encrypt (R11)",
"served_status": "served",
"role": null,
"renewer": null,
"chain_status": "incomplete"
},
"copy": {
"title": {
"key": "certificates.rules.chain_incomplete.title",
"params": {
"name": "docs.northwind.com"
}
},
"detail": {
"key": "certificates.rules.chain_incomplete.detail",
"params": {
"name": "docs.northwind.com"
}
},
"short": {
"key": "certificates.rules.chain_incomplete.short",
"params": {
"name": "docs.northwind.com"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.chain_incomplete.fix",
"route": "/certificates/4"
},
"dismissible": true,
"advisor": {
"resource_type": "certificate",
"resource_id": "4",
"check_id": "chain"
},
"decision": null,
"automation": null,
"sources": [],
"also": []
}
],
"recommendation_groups": [
{
"code": "chain_incomplete",
"severity": "warning",
"items": [
{
"tier": "recommendation",
"code": "chain_incomplete",
"severity": "warning",
"subject": {
"type": "certificate",
"id": 4,
"name": "docs.northwind.com"
},
"certificate_ids": [
4
],
"addresses": [
"docs.northwind.com:443"
],
"attention_key": null,
"due_at": null,
"evidence": {
"expires_at": "2026-03-08T12:00:00+00:00",
"days_left": 52,
"renews_itself": true,
"renewal_type": "automatic",
"renewal_type_basis": "guess",
"renewal_source": "Let's Encrypt (R11)",
"issuer": "Let's Encrypt (R11)",
"served_status": "served",
"role": null,
"renewer": null,
"chain_status": "incomplete"
},
"copy": {
"title": {
"key": "certificates.rules.chain_incomplete.title",
"params": {
"name": "docs.northwind.com"
}
},
"detail": {
"key": "certificates.rules.chain_incomplete.detail",
"params": {
"name": "docs.northwind.com"
}
},
"short": {
"key": "certificates.rules.chain_incomplete.short",
"params": {
"name": "docs.northwind.com"
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.chain_incomplete.fix",
"route": "/certificates/4"
},
"dismissible": true,
"advisor": {
"resource_type": "certificate",
"resource_id": "4",
"check_id": "chain"
},
"decision": null,
"automation": null,
"sources": [],
"also": []
}
],
"count": 1,
"copy": {
"title": {
"key": "certificates.overview.recGroups.chain_incomplete.title",
"params": {
"n": 1
}
},
"detail": {
"key": "certificates.overview.recGroups.chain_incomplete.detail",
"params": {
"n": 1
}
}
},
"fix": {
"kind": "route",
"label": "certificates.rules.chain_incomplete.fix",
"route": "/certificates/4"
},
"dismiss": [
{
"resource_type": "certificate",
"resource_id": "4",
"check_id": "chain"
}
]
}
],
"dismissed_recommendations": [],
"tidied": {
"replaced": 2,
"not_served": 0,
"provider": 0,
"merged": 1,
"dismissed": 0,
"untracked": 0
},
"untracked": [],
"inventory": [
{
"id": 10,
"subject": "vpn.northwind.example",
"issuer": "vpn.northwind.example",
"source": "manual",
"sources": [
"manual"
],
"not_after": "2026-01-03T12:00:00+00:00",
"status": "expired",
"role": "not_seen",
"hosts": [],
"superseded_by_id": null
}
],
"owner_candidates": [
{
"id": 1,
"name": "Alex Rivera"
}
]
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.read scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.read",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied
404 not_found
429 rate_limited
Assign an owner to certificates
Makes the member given in user_id the owner of each certificate in certificate_ids (up to 200). Refused when that person is not a member of the organization.
/organizations/{organization}/certificates/ownermonitoring.writeChanges dataAuthorizationheader
AuthorizationstringrequiredBearer and your API token: Bearer $INFRANEST_TOKEN. See Tokens and scopes.
Path parameters
organizationintegerrequiredYour organization's ID: the number after /organizations/ in the app's address, or from GET /organizations.
Bodyapplication/json
certificate_idsarrayoptionaluser_idintegerrequiredRequest
curl -X POST "https://api.infranest.io/api/organizations/42/certificates/owner" \
-H "Authorization: Bearer $INFRANEST_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"certificate_ids":[1],"user_id":1}'$headers = @{
Authorization = "Bearer $env:INFRANEST_TOKEN"
Accept = "application/json"
}
$body = @'
{
"certificate_ids": [
1
],
"user_id": 1
}
'@
Invoke-RestMethod -Method Post -Uri "https://api.infranest.io/api/organizations/42/certificates/owner" -Headers $headers -ContentType "application/json" -Body $body<?php
// composer require guzzlehttp/guzzle
require "vendor/autoload.php";
$client = new GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.infranest.io/api/organizations/42/certificates/owner', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('INFRANEST_TOKEN'),
'Accept' => 'application/json',
],
'json' => [
'certificate_ids' => [
1,
],
'user_id' => 1,
],
]);
echo $response->getBody();import os
import requests
response = requests.post(
"https://api.infranest.io/api/organizations/42/certificates/owner",
headers={
"Authorization": f"Bearer {os.environ['INFRANEST_TOKEN']}",
"Accept": "application/json",
},
json={
"certificate_ids": [
1,
],
"user_id": 1,
},
)
response.raise_for_status()
print(response.json())const response = await fetch('https://api.infranest.io/api/organizations/42/certificates/owner', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.INFRANEST_TOKEN}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"certificate_ids": [
1
],
"user_id": 1
}),
})
console.log(response.status, await response.text())package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
req, err := http.NewRequest("POST", "https://api.infranest.io/api/organizations/42/certificates/owner", strings.NewReader(`{"certificate_ids":[1],"user_id":1}`))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRANEST_TOKEN"))
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}Response
{
"assigned": 1,
"owner": {
"id": 1,
"name": "Alex Rivera"
}
}{
"code": "unauthenticated",
"message": "You are not signed in, or the API token is missing, wrong, expired or revoked.",
"hint": "Send a valid token in the Authorization header, as the word Bearer followed by the token. Create a new one under My account → Developer if it expired or was revoked.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#unauthenticated",
"reference": "k3j9x0b2qa"
}{
"code": "scope_missing",
"message": "This API token does not have the monitoring.write scope.",
"hint": "Create a token that includes the scope named in \"required\". Give each token only the scopes it needs.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#scope_missing",
"required": "monitoring.write",
"reference": "k3j9x0b2qa"
}{
"code": "not_found",
"message": "Nothing exists at this address, or you cannot see it.",
"hint": "Check the IDs in the address. A resource in another organization answers as not found.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#not_found",
"reference": "k3j9x0b2qa"
}{
"code": "idempotency_in_progress",
"message": "A request with this Idempotency-Key is still being handled.",
"hint": "Wait a few seconds, then send the same request with the same key again to get its result.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#idempotency_in_progress",
"reference": "k3j9x0b2qa"
}{
"code": "validation_failed",
"message": "The user id field is required.",
"hint": "Fix the fields listed in \"errors\" and send the request again.",
"retryable": false,
"docs_url": "https://developers.infranest.io/errors#validation_failed",
"errors": {
"user_id": [
"The user id field is required."
]
},
"reference": "k3j9x0b2qa"
}{
"code": "rate_limited",
"message": "This token made more than 300 requests in a minute.",
"hint": "Wait the number of seconds in \"retry_after\" (also the Retry-After header), then retry.",
"retryable": true,
"docs_url": "https://developers.infranest.io/errors#rate_limited",
"limit_key": "api_requests_per_minute",
"limit": 300,
"retry_after": 12,
"reference": "k3j9x0b2qa"
}401 unauthenticated
403 scope_missing wrong_organization test_token_required test_token_sandbox_only permission_denied write_disabled organization_suspended
404 not_found
409 idempotency_in_progress
422 validation_failed idempotency_key_reused
429 rate_limited