Error codes
A failed call answers with an HTTP status and a JSON envelope whose code says what went wrong. These are all 25 codes the API can send. New codes may appear within v1: treat one you do not know by its HTTP status.
The error envelope
Every error body has this shape; code and message are always present.
| Field | Type | Required | Description |
|---|---|---|---|
| error | object | yes | |
| error.code | string | yes | Machine-readable error code. Each response lists the codes it can carry; new codes may be added within v1, so treat an unknown code by its HTTP status. |
| error.message | string | yes | Human-readable explanation (English). |
| error.requestId | string | no | The X-Request-Id of this request — quote it when reporting a problem. Sent since v1.1. |
| error.detail | object | no | Structured context for the error, such as the offending field; omitted when there is none.
|
| error.docs | string | no | Where the error codes are explained. Sent since v1.1.
|
A real error answer — the request leaves out the required field:
curl -sS 'https://ecdsa.com/api/v1/jwt/decode' \
-H 'content-type: application/json' \
-d '{}'{
"error": {
"code": "missing_field",
"message": "Field \"token\" is required and must be a non-empty string.",
"requestId": "docs-example",
"docs": "https://ecdsa.com/developers#errors"
}
}All codes
| Code | HTTP | Meaning | When it occurs | What to do | SDKs |
|---|---|---|---|---|---|
| invalid_json | 400 | The body is not valid JSON or not a JSON object; or the input nests arrays and objects more than 64 levels deep (JSON documents inside string fields count too), or is too complex to inspect for private keys. | Typical causes: a form-encoded or plain-text body instead of JSON, a JSON array or string at the top level, or generated input with very deep nesting. The content-type header does not matter; the body does. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Send one JSON object as the body. If the input is generated, check its nesting. | Not retried |
| missing_field | 400 | A required field is absent or empty. | A field the operation requires is absent, null, empty or not a string — often a name spelled differently, such as public_key for publicKey. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Add the field the message names; the operation’s reference page lists the required fields. | Not retried |
| invalid_input | 400 | A field could not be parsed — the message explains exactly why. | A field is present but cannot be parsed: a key, signature, token or certificate in an encoding or format the operation does not recognise. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, Create a webhook endpoint | Read the message — it names the field and the reason. Check the encoding (hex, base64, PEM) and, for signatures, the curve. | Not retried |
| private_key_rejected | 400 | The request contains private-key material (a PEM PRIVATE KEY block, an OpenSSH, SSH2 or PuTTY private key, or a JWK with "d"). Never send private keys anywhere. | A whole key file pasted where only the public half is needed: a PKCS#8 or SEC1 PEM, an OpenSSH or PuTTY private key, a JWK that still carries “d” — also nested inside a string that holds JSON. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Send only public data. To work with a private key, use the browser tools — they run on your machine and send nothing. | Not retried |
| unsupported_algorithm | 400 | The token's algorithm is not one this operation verifies. | A JWT whose algorithm the operation does not verify: HS*, RS*, PS*, EdDSA, ES256K or none. Operations: Verify an ES256/ES384/ES512 JWT signature | This API verifies ES256, ES384 and ES512. Check other algorithms locally with a JWT library — an HMAC secret must never be sent anywhere. | Not retried |
| invalid_domain | 400 | Not a public DNS hostname (IP addresses, single-label names and paths are refused). | The path segment is an IP address, a name without a dot such as localhost, or text that is not a hostname. A scheme, port or path around the name is removed first. Operations: TLS signature-health grade for a domain | Send a public hostname such as github.com. IP addresses and internal names cannot be scanned from here. | Not retried |
| scan_dns_error | 400 | The domain does not resolve in public DNS. | Resolving the name in public DNS fails: no such domain (a typo, an expired registration, a name only an internal DNS knows), no A or AAAA record, or a DNS failure. Operations: TLS signature-health grade for a domain | Check the spelling and that the name has A or AAAA records. | Not retried |
| scan_private_address | 400 | The domain resolves (at least partly) to a private or reserved address. | An address of the name — IPv4, IPv6, or IPv4 embedded in IPv6 — is in a private or reserved range such as an internal network or loopback. A mix of public and private addresses is refused as a whole. Operations: TLS signature-health grade for a domain | Check a host that is reachable from the public internet — internal hosts cannot be scanned from here. | Not retried |
| webhook_url_refusedwith API keys | 400 | The webhook URL cannot receive deliveries: it is not https on port 443, its host is not a public DNS name, or the name does not resolve or resolves (at least partly) to a private or reserved address. | Creating an endpoint with an http URL, a port other than 443, an IP address or an internal name, or a host whose DNS answer includes a private or reserved address — also when only one of several addresses is. Operations: Create a webhook endpoint | Use an https URL on the default port with a public host name. The message says which rule the URL broke and, for a refused address, which address and range. | Not retried |
| unauthorized | 401 | The Authorization or X-API-Key header is malformed, the API key is not valid (wrong checksum, unknown, revoked or expired), or the operation needs a key. | An Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Remove the header to call anonymously, or send a complete, valid key. | Not retried |
| forbidden_scopewith API keys | 403 | The API key is valid but lacks a scope this operation requires; the message names it. | A key created without the scope an operation needs, such as a key with only the api scope calling the domain-health operation, which needs scan. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Call with a key that has that scope. | Not retried |
| webhook_limit_reachedwith API keys | 403 | The account already has as many webhook endpoints as its plan allows. | Creating an endpoint while the account already has as many as its plan allows; the free plan allows one. Operations: Create a webhook endpoint | Delete an endpoint you no longer need, or create the existing one again with the same URL to change its description or events. | Not retried |
| not_found | 404 | No operation exists at this path. | A path under Any operation can answer with it. | Check the path against the API reference; operations live under /api/v1. | Not retried |
| webhook_not_foundwith API keys | 404 | The API key's account has no webhook endpoint with this id. | An endpoint id that is not one of the key’s account: deleted already, mistyped, or another account’s. Operations: Delete a webhook endpoint, Send a test event | List the account’s endpoints with GET /api/v1/webhooks and use an id from there. | Not retried |
| method_not_allowed | 405 | The path exists but does not accept this HTTP method; see the Allow header. | Calling a POST operation with GET — opening it in a browser’s address bar, for example — or the reverse. Any operation can answer with it. | Use a method from the Allow header: POST for operations that take a JSON body, GET for the others. | Not retried |
| payload_too_large | 413 | The body exceeds 100 KB. | The body is larger than 100 KB, usually because a whole certificate bundle or file was pasted into one field. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Send less per call — one certificate chain, one token. | Not retried |
| rate_limited | 429 | Too many requests — see Retry-After. | Too many requests in the current window: per IP without a key, per key with one. Real scans of a domain also draw on budgets per host and overall. Operations: API self-description, Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Wait the seconds in Retry-After, then retry, and cache answers on your side. | Retried up to 2 times |
| quota_exceededwith API keys | 429 | A daily or monthly quota of the API key's account is used up. X-Quota-Reset (and Retry-After) say when it starts over; retrying earlier cannot succeed. | Calls made with a key have used up the account’s daily or monthly quota units or its daily domain scans; test keys have smaller quotas of their own. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Do not retry before the time in X-Quota-Reset: the quota starts over then, not sooner. | Not retried |
| internal_error | 500 | Unexpected server error. | A fault the API did not anticipate. It is logged with the request ID — never with the request body. Any operation can answer with it. | If it persists, report the requestId of the error body. | Not retried |
| scan_unreachable | 502 | The domain refused or dropped the connection on port 443. | The name resolves, but no TCP connection on port 443 succeeds from the internet: no HTTPS server, a firewall that refuses or drops it, or a host that is down. Operations: TLS signature-health grade for a domain | Check that the host serves HTTPS on port 443 to the public internet. | Retried up to 2 times |
| scan_tls_error | 502 | The TLS handshake failed or presented no usable certificate. | A connection on port 443 opens, but the TLS handshake ends with an alert or a protocol mismatch, or the server sends no certificate the scanner can read. Operations: TLS signature-health grade for a domain | Check the server’s TLS setup, for example with the domain health report. | Retried up to 2 times |
| service_unavailablewith API keys | 503 | API keys cannot be verified right now (the key store is unreachable); retry shortly. | The key store cannot be reached, so a presented key cannot be checked. A call that presents a key is never served anonymously. Operations: Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, TLS signature-health grade for a domain, List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Retry shortly — the SDKs do. | Retried up to 2 times |
| webhooks_unavailablewith API keys | 503 | Webhooks are switched off on this server: it has no signing keys configured. | The server has no webhook signing keys configured, so it cannot sign deliveries; every webhook operation answers with this code until keys are set. Operations: List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Nothing to change in the request: webhooks are switched off on this server for now. Try again later. | Retried up to 2 times |
| scan_timeout | 504 | The scan did not finish in time. | Resolving the name, connecting or the TLS handshake took longer than the scanner allows — a slow or overloaded host, or packets silently dropped. Operations: TLS signature-health grade for a domain | Retry later; slow or overloaded hosts can exceed the budget. | Retried up to 2 times |
| timeout | 504 | The operation did not finish within its time budget. | Work on the request ran past its time budget of 10 seconds. Input within the request limits is not expected to do that. Operations: API self-description, Verify an ECDSA signature, Convert a signature between DER and raw r‖s, Decode a JWT (no signature check), Verify an ES256/ES384/ES512 JWT signature, Decode an X.509 certificate or chain, List webhook endpoints, Create a webhook endpoint, Delete a webhook endpoint, Send a test event | Retry once; if the same input times out again, report the requestId of the error body. | Retried up to 2 times |
Retries
The official SDKs retry 429 (except quota_exceeded), 502, 503 and 504 — at most 2 times — waiting as long as Retry-After asks (at most 60 seconds), otherwise with exponential backoff and full jitter. Every operation is a pure function of its input, so a retry is always safe; your own client can follow the same rule.