PEM certificate decoder API

Decodes one PEM certificate: subject, issuer, validity and days left, DNS and IP names, key type and size, signature algorithm, CA flag, key usages, self-signed and SHA-256 and SHA-1 fingerprints. Email addresses are never returned. Nothing is fetched, so trust and revocation are not checked.

POST https://api.lookproof.dev/v1/tls/pem-decode

Updated 2026-10-09

Examples use the Base (eip155:8453) network.

Input

A JSON body with content-type: application/json.

NameTypeRequiredLimitsDescription
pemstringYes1–16384 charactersOne PEM CERTIFICATE block, at most 16384 characters. Private keys are refused.

Example request

curl -i -X POST https://api.lookproof.dev/v1/tls/pem-decode \
  -H 'content-type: application/json' \
  -d '{"pem": "-----BEGIN CERTIFICATE-----\nMIIBqjCCAVCgAwIBAgIDDoo/MAoGCCqGSM49BAMCMB0xGzAZBgNVBAMMEkV4YW1w\nbGUgSXNzdWluZyBDQTAeFw0yNjA5MDEwMDAwMDBaFw0yNjExMzAwMDAwMDBaMDAx\nGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTEUMBIGA1UECgwLRXhhbXBsZSBMdGQw\nWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATyMmt7KcF7ouU025TiogjNkCQ2iiS/\n4t5guVuIjznTSD3p0AA45RGsaR10XOStJvi9BN4ruU9SHi9OkKpyP7Y7o2wwajAM\nBgNVHRMBAf8EAjAAMEUGA1UdEQQ+MDyCD3d3dy5leGFtcGxlLmNvbYILZXhhbXBs\nZS5jb22HBMAAAgqBFmhvc3RtYXN0ZXJAZXhhbXBsZS5jb20wEwYDVR0lBAwwCgYI\nKwYBBQUHAwEwCgYIKoZIzj0EAwIDSAAwRQIhAN6TOxiBdfALdsoU3cuapoJrFFm1\neOCqJSG8/3SW9/LOAiAaGfFCWjWof5LnsIGPntTa6TGwqFHv/wxuOpq37L+PNw==\n-----END CERTIFICATE-----\n"}'
import asyncio
import os

from eth_account import Account
from x402 import x402Client
from x402.http.clients import x402HttpxClient
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact import register_exact_evm_client


async def main() -> None:
    signer = EthAccountSigner(Account.from_key(os.environ["EVM_PRIVATE_KEY"]))
    client = register_exact_evm_client(x402Client(), signer, networks="eip155:8453")
    async with x402HttpxClient(client) as http:
        response = await http.post('https://api.lookproof.dev/v1/tls/pem-decode', json={'pem': '-----BEGIN CERTIFICATE-----\nMIIBqjCCAVCgAwIBAgIDDoo/MAoGCCqGSM49BAMCMB0xGzAZBgNVBAMMEkV4YW1w\nbGUgSXNzdWluZyBDQTAeFw0yNjA5MDEwMDAwMDBaFw0yNjExMzAwMDAwMDBaMDAx\nGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTEUMBIGA1UECgwLRXhhbXBsZSBMdGQw\nWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATyMmt7KcF7ouU025TiogjNkCQ2iiS/\n4t5guVuIjznTSD3p0AA45RGsaR10XOStJvi9BN4ruU9SHi9OkKpyP7Y7o2wwajAM\nBgNVHRMBAf8EAjAAMEUGA1UdEQQ+MDyCD3d3dy5leGFtcGxlLmNvbYILZXhhbXBs\nZS5jb22HBMAAAgqBFmhvc3RtYXN0ZXJAZXhhbXBsZS5jb20wEwYDVR0lBAwwCgYI\nKwYBBQUHAwEwCgYIKoZIzj0EAwIDSAAwRQIhAN6TOxiBdfALdsoU3cuapoJrFFm1\neOCqJSG8/3SW9/LOAiAaGfFCWjWof5LnsIGPntTa6TGwqFHv/wxuOpq37L+PNw==\n-----END CERTIFICATE-----\n'})
        response.raise_for_status()
        print(response.json())


asyncio.run(main())
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY);
const fetchWithPayment = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});

const response = await fetchWithPayment("https://api.lookproof.dev/v1/tls/pem-decode", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({"pem": "-----BEGIN CERTIFICATE-----\nMIIBqjCCAVCgAwIBAgIDDoo/MAoGCCqGSM49BAMCMB0xGzAZBgNVBAMMEkV4YW1w\nbGUgSXNzdWluZyBDQTAeFw0yNjA5MDEwMDAwMDBaFw0yNjExMzAwMDAwMDBaMDAx\nGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTEUMBIGA1UECgwLRXhhbXBsZSBMdGQw\nWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATyMmt7KcF7ouU025TiogjNkCQ2iiS/\n4t5guVuIjznTSD3p0AA45RGsaR10XOStJvi9BN4ruU9SHi9OkKpyP7Y7o2wwajAM\nBgNVHRMBAf8EAjAAMEUGA1UdEQQ+MDyCD3d3dy5leGFtcGxlLmNvbYILZXhhbXBs\nZS5jb22HBMAAAgqBFmhvc3RtYXN0ZXJAZXhhbXBsZS5jb20wEwYDVR0lBAwwCgYI\nKwYBBQUHAwEwCgYIKoZIzj0EAwIDSAAwRQIhAN6TOxiBdfALdsoU3cuapoJrFFm1\neOCqJSG8/3SW9/LOAiAaGfFCWjWof5LnsIGPntTa6TGwqFHv/wxuOpq37L+PNw==\n-----END CERTIFICATE-----\n"}),
});
console.log(await response.json());

curl: unpaid request — returns 402 with the price (PAYMENT-REQUIRED). The Python and JavaScript clients pay it and send the request again. Only ordinary wallets (EOA) can pay; smart-contract wallets get a 402 without charge. See payments.

Example response

{
  "checked_at": "2026-10-24T12:00:00Z",
  "data": {
    "days_remaining": 36,
    "decoded": true,
    "extended_key_usage": [
      "serverAuth"
    ],
    "is_ca": false,
    "issuer": "CN=Example Issuing CA",
    "key_bits": 256,
    "key_type": "ec",
    "key_usage": [],
    "not_after": "2026-11-30T00:00:00Z",
    "not_before": "2026-09-01T00:00:00Z",
    "notes": [
      "Nothing is fetched: the chain, revocation and trust in the issuer are not checked."
    ],
    "omitted": [
      "san_email"
    ],
    "path_length": null,
    "san_count": 4,
    "san_dns": [
      "www.example.com",
      "example.com"
    ],
    "san_ip": [
      "192.0.2.10"
    ],
    "self_issued": false,
    "self_signed": false,
    "serial_number": "e8a3f",
    "sha1_fingerprint": "75712bf2227c215c28a2e214ed2f79c007d70769",
    "sha256_fingerprint": "4d0289557971058d3eeee0dea9449d2da6709778c69c92849f2996e4f3120227",
    "signature_algorithm": "ecdsa-with-SHA256",
    "subject": "O=Example Ltd,CN=www.example.com",
    "subject_cn": "www.example.com",
    "validity_days": 90,
    "version": "v3"
  },
  "endpoint": "tls.pem_decode",
  "findings": [
    {
      "code": "PEM_PERSONAL_DATA_OMITTED",
      "evidence": {
        "count": 1,
        "items": [
          {
            "fields": "san_email"
          }
        ]
      },
      "explanation": "The block holds an email address (emailAddress in a name, or an rfc822Name in the subject alternative names) or a name type that can identify a person, such as a user principal name. Lookproof never returns or stores those. The evidence lists which kinds were present; all other fields are shown.",
      "message": "Email addresses and personal name types were left out of the result.",
      "recommendation": null,
      "severity": "info"
    }
  ],
  "meta": {
    "cache_hit": false,
    "duration_ms": 42,
    "fresh_until": "2026-10-24T12:05:00Z",
    "observed_at": "2026-10-24T12:00:00Z",
    "resolver": null,
    "source": "offline"
  },
  "request_id": "req_aaaaaaaaaaaaaaaaaaaaaaaaaa",
  "schema_version": "1.0",
  "status": "info"
}

Response fields

FieldMeaning
request_idUnique id of this request. Quote it when you contact support.
endpointThe endpoint id, for example mail.spf.
schema_versionVersion of the response format, currently 1.0.
checked_atWhen the check ran (ISO 8601, UTC).
statusok, info, warning or error from the worst finding, or unknown when the check was incomplete. unknown is not an error in the domain.
dataThe check's own data. Its fields are listed below.
findingsWhat was found: code, severity, message, explanation and evidence.
metaDuration, cache use, resolver and how long the result stays fresh.

Fields in data

FieldType
decodedboolean
subjectstring or null
subject_cnstring or null
issuerstring or null
serial_numberstring or null
versionone of v1, v3 or null
not_beforestring or null
not_afterstring or null
days_remaininginteger or null
validity_daysinteger or null
san_dnsarray of string
san_iparray of string
san_countinteger
omittedarray of one of subject_email, issuer_email, san_email, san_other
key_typeone of rsa, ec, ed25519, ed448, dsa, other or null
key_bitsinteger or null
signature_algorithmstring or null
self_issuedboolean
self_signedboolean
is_caboolean or null
path_lengthinteger or null
key_usagearray of string
extended_key_usagearray of string
sha256_fingerprintstring or null
sha1_fingerprintstring or null
notesarray of string

Status codes

StatusCodeMeaningDo you pay?
200—The result is delivered, also when it has negative findings.Yes: charged once per payment; redelivery of the same payment is free
402—Payment is required, or the attached payment was refused. The body states the price.No
400bad_requestThe request is malformed.No
409payment_in_progress, payment_bound_to_other_requestThis payment is already being processed, or it is bound to a different request. No new payment is taken.No
413payload_too_largeThe body is larger than 64 KB.No
422invalid_inputThe input failed validation on a request that carries a payment; nothing is charged. Without a payment, invalid input gets a 402 whose error field names the problem.No
429rate_limitedToo many requests from this IP address or payer.No
502check_failedThe check could not be completed.No
503busy, unavailable, payments_unavailableThe service is busy or temporarily unavailable.No
504deadline_exceededThe check ran out of time.No

Errors use the common error body. See errors.

Findings this endpoint can return

Certificates (6)
PEM data (4)