API quickstart
From zero to a verdict in three steps. Base URL and full schemas live in the OpenAPI specification; everything below is the happy path.
1. Get a key
Create an account, verify your email, then mint a key under API keys in the console. Keys look like qv_<prefix>_<secret> and are shown exactly once — only a hash is stored. Each key can carry an IP allowlist (see key security below).
2. Validate a certificate
POST a PEM certificate (optionally with intermediates) under a policy — POLICY_EIDAS_QWAC_V1 for website-authentication certificates, POLICY_EIDAS_QSEALC_V1 for electronic seals:
curl https://api.quovalis.eu/v1/validations \
-H "Authorization: Bearer $QUOVALIS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"certificate": "-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----",
"chain": ["-----BEGIN CERTIFICATE-----\n…\n-----END CERTIFICATE-----"],
"policy": "POLICY_EIDAS_QWAC_V1",
"options": { "checkRevocation": true }
}'Request bodies are capped at 256 KiB and chain takes at most 10 intermediates. The response pins the exact trust-list snapshot the verdict was computed against. A field that does not apply is an absent key, never null, and 64-bit integers such as sequenceNumber are JSON strings so no precision is lost:
{
"id": "0198f3a2-7c11-7000-8000-3f9be1a2c001",
"verdict": "VERDICT_VALID",
"policy": "POLICY_EIDAS_QWAC_V1",
"validatedAt": "2026-07-09T12:00:00Z",
"certificate": { "subject": "CN=bank.example.com,…", "notAfter": "2027-01-01T00:00:00Z", … },
"qualified": {
"isQualified": true,
"qcType": "QC_TYPE_WEB",
"trustServiceProvider": { "name": "Example QTSP", "territory": "NL", "serviceStatus": "granted" }
},
"psd2": { "roles": ["PSD2_ROLE_PSP_AS", "PSD2_ROLE_PSP_AI"], "ncaId": "NL-DNB", "authorizationNumber": "PSDNL-DNB-123456" },
"revocation": { "status": "REVOCATION_STATUS_GOOD", "method": "REVOCATION_METHOD_OCSP", "checkedAt": "2026-07-09T12:00:00Z" },
"trustList": {
"territory": "NL",
"sequenceNumber": "87",
"snapshotId": "0198f3a2-7c11-7000-8000-3f9be1a2c002"
}
}Verdicts are tri-state: VERDICT_VALID, VERDICT_INVALID (with a subIndication such as SUB_INDICATION_REVOKED or SUB_INDICATION_NOT_QUALIFIED) or VERDICT_INDETERMINATE when the service could not obtain enough data — for example when revocation infrastructure is unreachable, or when a trusted-list qualification override for the issuing service cannot be fully evaluated for the certificate (SUB_INDICATION_QUALIFICATION_UNDETERMINED). A valid result carries no subIndicationkey at all, which is why the response above has none. An indeterminate result is never silently upgraded. Qualified status reflects the trusted list's Qualifications (Sie) overrides for the issuing service, including qualification granted or withdrawn only through that extension. A validation outcome is always HTTP 200; errors use RFC 9457 problem documents.
3. Fetch stored results
# Results are stored and returned byte-identically, never recomputed. curl https://api.quovalis.eu/v1/validations/0198f3a2-7c11-7000-8000-3f9be1a2c001 \ -H "Authorization: Bearer $QUOVALIS_API_KEY"
Stored results stay fetchable for your plan's full retention window (see limits below).
More endpoints
Three read-only endpoints round out the API — full schemas in the OpenAPI specification. GET /v1/usage returns your current billing period at a glance: plan, quota, consumed, remaining, overage and your rate limit.
curl https://api.quovalis.eu/v1/usage \ -H "Authorization: Bearer $QUOVALIS_API_KEY"
GET /v1/trustlist/providers lists the trust service providers and their CA/QC services from the current trust-list snapshot, filterable by territory and paginated with cursor/limit:
curl "https://api.quovalis.eu/v1/trustlist/providers?territory=NL" \ -H "Authorization: Bearer $QUOVALIS_API_KEY"
GET /v1/trustlist/status reports ingestion health per territory — sequence numbers, snapshot ids, signature checks and staleness — so you can see exactly which trust state verdicts are being computed against:
curl https://api.quovalis.eu/v1/trustlist/status \ -H "Authorization: Bearer $QUOVALIS_API_KEY"
Errors
All non-2xx responses are RFC 9457 problem documents (application/problem+json) with a type of urn:quovalis:problem:<suffix>:
| Type suffix | Status | When |
|---|---|---|
invalid-request | 400 | Malformed JSON, unknown fields, unparseable PEM or an unsupported policy |
unauthorized | 401 | Missing, unknown or revoked API key |
key-expired | 401 | Valid key past its expiry date |
ip-not-allowed | 403 | Source IP outside the key's IP allowlist |
insufficient-scope | 403 | Key scopes do not cover the operation |
not-found | 404 | Unknown id (or a resource that is not yours) |
request-too-large | 413 | Request body over the 256 KiB cap |
rate-limited | 429 | Rate limit exceeded; Retry-After is set |
quota-exceeded | 429 | Monthly quota exhausted on a plan that blocks overage |
overage-cap-reached | 429 | Your monthly overage spend cap is reached |
internal | 500 | Unhandled failure on our side |
upstream-unavailable | 503 | A downstream dependency is unavailable; Retry-After is set |
overloaded | 503 | Platform load shedding (not a tenant limit); Retry-After is set |
Every problem body carries a requestId and every response an X-Request-Id header — quote it when contacting support and we can find the exact request in our logs. Remember that a validation outcome is never an error: INVALID and INDETERMINATE verdicts arrive as HTTP 200.
The two monthly-window 429s — quota-exceeded and overage-cap-reached — additionally carry a resetAt extension member: an RFC 3339 timestamp of when the monthly window rolls over. Parse it to back off programmatically rather than scraping the human-readable detail. There is deliberately no Retry-After on these — the wait can be weeks.
Limits
| Plan | Included validations / month | Rate limit | Over quota |
|---|---|---|---|
| Free — company domain | 500 | 1 r/s, burst 5 | blocked (429) |
| Free — personal email | 50 | 1 r/s, burst 5 | blocked (429) |
| Starter | 10,000 | 10 r/s, burst 50 | metered overage (capped) |
| Growth | 100,000 | 50 r/s, burst 200 | metered overage (capped) |
| Scale | custom | custom | contract |
Signing up with a company email address gets the company-domain free tier; personal addresses (Gmail, Hotmail, …) get the personal-email tier. Every response carries X-RateLimit-* headers; throttled and shed requests include Retry-After. Stored results are retained 90 days on the company-domain free tier, 30 days on the personal-email tier and 365 days on paid plans (details in the privacy policy).
Overage on paid plans is metered up to a configurable cap that is on by default; once the cap is reached, requests are rejected with 429 overage-cap-reached until you raise the cap in the console or a new month starts. Rates, default caps and prices are on the pricing page.
Key security
Every key supports an IP allowlist: a per-key list of up to 32 CIDR ranges (bare IPs count as /32), edited on the console API keys page. With an allowlist set, requests from any other address are rejected fail-closed with 403 ip-not-allowed — a leaked key is useless outside your infrastructure. Allowlist edits take effect immediately (60 seconds worst case).
Rotating a key (also on the API keys page) mints a replacement with the same name, allowlist and expiry, and lets you pick a grace window: 24 hours, during which both keys work while you roll out the new secret, or none, killing the old key at once.
If a key leaks, rotate it with no grace window — or revoke it outright if nothing depends on it. Revocations and rotations are effective within 60 seconds at most. If you cannot tell where the key was used from, tightening its IP allowlist is an immediate containment step.
Good to know
- Validation is not idempotent by design — every call produces a new, billable validation record.
- A verdict is a technical assessment against published trust-list and revocation data — see the Terms for the documented non-claims.
- Try certificates interactively in the console playground before writing any code.