Error Handling

Every error response from the API uses the same JSON envelope, regardless of the endpoint. Check the HTTP status code first, then switch on code for programmatic branching.

The error envelope

FieldTypeDescription
error string Human-readable summary of what went wrong. Safe to display.
code string Machine-readable error code - stable across releases, safe to switch on.
request_id string UUID that correlates to server-side logs. Always include this when contacting support.
errors object Field-level validation messages keyed by field name. Only present on validation_error.
details object Extra context to help you act on the error. Shape varies by error type; present when there is actionable context beyond error and code. See the reference table below.

Minimal error (401)

{
  "error": "Authentication credentials were not provided.",
  "code": "authentication_failed",
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

With field errors (400)

{
  "error": "Validation failed.",
  "code": "validation_error",
  "request_id": "550e8400-e29b-41d4-a716-446655440001",
  "errors": {
    "legal_name": ["This field is required."],
    "entity_type": ["Must be 'company' or 'individual'."]
  }
}

With details (409)

{
  "error": "A check is already in progress.",
  "code": "conflict",
  "request_id": "550e8400-e29b-41d4-a716-446655440002",
  "details": {
    "existing_check_id": "d67177bf-6871-45bf-85f2-95a1e52ccc64",
    "status": "pending"
  }
}

Error code reference

All possible code values, the HTTP status they always appear on, and the details shape when present.

HTTPcodeWhen it occursdetails keys
400 validation_error Request body failed field validation - check errors for per-field messages. -
400 invalid_request Request is structurally valid but logically invalid - e.g. wrong workflow state, unsupported operation. current_status (when a status transition is rejected)
401 authentication_failed Missing, malformed, or revoked API key. -
402 insufficient_credits The tenant has no remaining credits for this feature. -
403 permission_denied Valid key but not authorised to access this resource. -
404 not_found Resource does not exist or is not accessible to your tenant. -
409 conflict Action conflicts with current state - e.g. a duplicate screening or check already in progress. existing_check_id, status
409 idempotency_conflict The same Idempotency-Key header was reused with a different request body. -
429 rate_limited Too many requests. Back off and retry -
500 server_error Unexpected internal error on KYC Genie's side. Report request_id to support. -
502 upstream_error An external service (e.g. a webhook endpoint) returned a non-2xx response or was unreachable. http_status_code (remote status, or absent if unreachable)

Invalid request (400) - with details

{
  "error": "This response has already been submitted.",
  "code": "invalid_request",
  "request_id": "550e8400-e29b-41d4-a716-446655440003",
  "details": {
    "current_status": "submitted"
  }
}

Insufficient credits (402)

{
  "error": "Insufficient credits to perform this action.",
  "code": "insufficient_credits",
  "request_id": "550e8400-e29b-41d4-a716-446655440004"
}

Upstream error (502)

{
  "error": "Webhook endpoint returned HTTP 405",
  "code": "upstream_error",
  "request_id": "6d078d05-c86c-4728-9ae9-89ab22b0e1d2",
  "details": {
    "http_status_code": 405
  }
}

Which errors to retry

Not all errors are worth retrying. The table below gives the recommended strategy for each code.

codeRetry?Strategy
validation_error No Fix the request body first - retrying without changes will get the same error.
invalid_request No The request is logically invalid for the current state. Check details.current_status.
authentication_failed No Check your API key. Rotating and retrying once is fine.
insufficient_credits No Top up credits in the dashboard before retrying.
permission_denied No Your key doesn't have access to this resource.
not_found No Resource does not exist. Verify the ID.
conflict Sometimes Wait for the in-progress operation to complete, then retry if needed. Check details for the existing resource.
idempotency_conflict No Generate a new Idempotency-Key if you genuinely want a second request.
rate_limited Yes Exponential back-off starting at 1 s. The Python SDK retries 429s automatically.
server_error Yes Retry up to 3 times with exponential back-off. Include request_id if you report the issue.
upstream_error Sometimes The remote endpoint is outside KYC Genie's control. Fix the remote service or URL, then retry.

Rate limited (429)

{
  "error": "Request was throttled. Expected available in 1 second.",
  "code": "rate_limited",
  "request_id": "550e8400-e29b-41d4-a716-446655440005"
}

Python SDK - exception hierarchy

Every API error raises a typed exception. Catch specific subclasses for precise handling, or catch the base KYCGenieAPIError for a catch-all. The exception class is determined by code in the response body - not the HTTP status code - so the same status can produce different exception types.

codeException classExtra attribute
validation_error KYCGenieValidationError e.data.errors - field-level messages
invalid_request KYCGenieValidationError e.data.details
authentication_failed KYCGenieAuthenticationError -
insufficient_credits KYCGenieInsufficientCreditsError -
permission_denied KYCGeniePermissionError -
not_found KYCGenieNotFoundError -
conflict KYCGenieConflictError e.data.details
rate_limited KYCGenieRateLimitError -
upstream_error KYCGenieUpstreamError e.remote_status_code - HTTP status returned by the remote, or None if unreachable
server_error KYCGenieServerError -

All exception classes share these attributes:

AttributeTypeDescription
e.status_code int HTTP status code
e.message str Human description from the API docs
e.data.error str Human-readable error from the response body
e.data.code str Machine-readable error code
e.data.request_id str Request ID for support escalation
e.data.errors dict Field-level errors - only on validation_error
e.data.details dict Extra context - varies by error type

Recommended catch pattern

from kycgenie import KYCGenie
from kycgenie.errors import (
    KYCGenieValidationError,
    KYCGenieNotFoundError,
    KYCGenieInsufficientCreditsError,
    KYCGenieConflictError,
    KYCGenieRateLimitError,
    KYCGenieUpstreamError,
    KYCGenieServerError,
    KYCGenieAPIError,
)

client = KYCGenie(api_key="YOUR_API_KEY")

try:
    entity = client.entities.create(legal_name="Acme Ltd")

except KYCGenieValidationError as e:
    # 400 - fix the request
    print(f"Validation failed: {e.data.errors}")

except KYCGenieInsufficientCreditsError:
    # 402 - top up credits
    print("No credits remaining")

except KYCGenieConflictError as e:
    # 409 - resource already exists or in-progress
    existing_id = (e.data.details or {}).get("existing_check_id")
    print(f"Conflict - existing resource: {existing_id}")

except KYCGenieRateLimitError:
    # 429 - SDK retries automatically, but you can catch manually
    print("Rate limit hit, back off and retry")

except KYCGenieServerError as e:
    # 500 - report to support with request_id
    print(f"Server error, request_id: {e.data.request_id}")

except KYCGenieAPIError as e:
    # catch-all for anything else
    print(f"API error {e.status_code}: {e.data.code} - {e.data.error}")

Upstream error with details

from kycgenie.errors import KYCGenieUpstreamError

try:
    result = client.webhooks.test_delivery(
        webhook_url="https://example.com/webhook",
    )
    print(f"Delivered - status: {result.http_status_code}")

except KYCGenieUpstreamError as e:
    remote = e.remote_status_code  # int or None
    if remote:
        print(f"Endpoint returned HTTP {remote}")
    else:
        print("Endpoint was unreachable")
    print(f"Request ID: {e.data.request_id}")

Accessing field-level errors

from kycgenie.errors import KYCGenieValidationError

try:
    client.entities.create(legal_name="", entity_type="unknown")

except KYCGenieValidationError as e:
    for field, messages in (e.data.errors or {}).items():
        for msg in messages:
            print(f"  {field}: {msg}")
    # legal_name: This field may not be blank.
    # entity_type: Must be 'company' or 'individual'.