Skip to main content
The kycert API returns standard HTTP status codes alongside a consistent JSON error body. Every error response — from authentication failures to validation issues — follows the same envelope structure, so you can write generic error-handling logic and then branch on the code field for specific conditions.

Error response format

All error responses use this structure:
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_document",
    "message": "CPF/CNPJ com quantidade de dígitos incorreta.",
    "param": "subject.doc"
  }
}
FieldTypeDescription
typestringError category — use for broad classification in error handling
codestringSpecific machine-readable code — use for programmatic logic
messagestringHuman-readable description of what went wrong
paramstring | nullThe request field that caused the error, when applicable

HTTP status codes

StatusMeaningCommon causes
200OKRequest succeeded
202AcceptedRun queued for async processing — wait for webhook
400Bad RequestInvalid parameters, malformed document, unknown API version
401UnauthorizedMissing or invalid API key
402Payment RequiredInsufficient credits or suspended billing account
403ForbiddenValid key, but insufficient scope for this operation
404Not FoundRun or resource does not exist, or belongs to another tenant
429Too Many RequestsRate limit exceeded — check Retry-After header
500Internal Server ErrorUnexpected server-side failure — retry with backoff; include request_id in support requests

Error codes

HTTPtypecodeWhen it occursWhat to do
400invalid_request_errormissing_subjectsubject.doc or subject.type is absentAdd the missing field to the request body
400invalid_request_errorinvalid_documentCPF or CNPJ has the wrong number of digitsValidate the document format before sending
400invalid_request_errorinvalid_subject_typesubject.type is not pf or pjUse "pf" for individuals, "pj" for legal entities
400invalid_request_errorsubject_type_mismatchTemplate is PJ but subject.type is "pf", or vice versaVerify the template type in the dashboard
400invalid_request_errormissing_template_idtemplate_id is absent from the request bodyInclude template_id
400invalid_request_errortemplate_not_foundtemplate_id is inactive, incorrect, or belongs to another tenantCheck the template ID in the dashboard
400invalid_request_errorinvalid_jsonRequest body is malformed JSONCheck your serialization logic
400invalid_request_errorinvalid_metadatametadata is not a flat key-value object or contains non-string valuesEnsure all metadata values are strings
400invalid_request_errorinvalid_cursorPagination cursor is corruptedUse the cursor returned by GET /bureau/runs without modification
400invalid_request_errorrun_not_foundrun_id does not exist or belongs to another tenantVerify the run ID
400invalid_request_errorunknown_api_versionThe x-kycert-api-version header specifies an unrecognized versionUse 2026-06-03 or omit the header
401authentication_errormissing_api_keyNo X-API-Key header or Authorization: Bearer tokenAdd the authentication header
401authentication_errorinvalid_api_keyKey is revoked, inactive, or does not existVerify the key in the dashboard
402billing_errorbilling_suspendedInsufficient credit balance or account suspendedAdd credits in the dashboard
403authorization_errorinsufficient_scopeKey lacks the required scopeCreate a new key with the needed scope
403authorization_errorsubject_type_not_allowedKey is restricted to PF but request is PJ (or vice versa)Check the key’s allowed subject types
429server_errorrate_limit_exceededToo many requests within the rate limit windowWait for the Retry-After duration and retry
500server_errorinternal_errorUnexpected internal failureRetry with exponential backoff; contact support with the response
500server_errorrun_failedBureau run failed to startRetry the run

Rate limiting

The kycert API enforces per-key rate limits. When you exceed the limit, the API returns 429 Too Many Requests. Every API response includes rate limit headers:
HeaderDescription
X-RateLimit-LimitMaximum requests allowed per minute for this key
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the current window resets
Retry-AfterSeconds to wait before retrying (present on 429 responses)
const res = await fetch('https://admin.kycert.com.br/api/v1/bureau/runs', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.KYCERT_API_KEY!,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(payload),
})

if (res.status === 429) {
  const retryAfter = parseInt(res.headers.get('retry-after') ?? '60', 10)
  console.log(`Rate limited. Retrying in ${retryAfter}s.`)
  await new Promise(r => setTimeout(r, retryAfter * 1000))
  // retry the request
}

Idempotency

Use the Idempotency-Key header on POST /bureau/runs to make retries safe. If you send the same Idempotency-Key value within 24 hours, the API returns the original response without creating a duplicate run. kycert stores the idempotency key as a keyed hash scoped to your tenant, with a 24-hour TTL. This means the same key value used by two different tenants does not conflict.
curl -X POST https://admin.kycert.com.br/api/v1/bureau/runs \
  -H "X-API-Key: sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a3f9b2c1-4e78-4d0a-9b2e-1c3f5e7a9d2b" \
  -d '{
    "template_id": "550e8400-e29b-41d4-a716-446655440000",
    "subject": { "type": "pf", "doc": "12345678901" },
    "external_id": "cust_abc123"
  }'
Use a UUID v4 or any sufficiently random string as the key value. Tie it to the specific operation in your system — for example, derive it from your internal customer ID and the operation type — so retries after a network failure always send the same key.
Without an Idempotency-Key, every retry of a POST /bureau/runs request creates a new run and consumes credits. Always include an idempotency key when retrying failed or timed-out requests.

Retry strategy