Skip to content

Errors

Kanoniv uses RFC 7807 Problem Details for all error responses. Every error returns a structured JSON object with a consistent shape, making it straightforward to handle errors programmatically.

The error object

All errors return Content-Type: application/problem+json with this structure:

json
{
  "type": "https://api.kanoniv.com/errors/validation-error",
  "title": "Validation failed",
  "status": 400,
  "detail": "Field 'email' is required",
  "instance": "/v1/ingest/batch",
  "errors": [
    { "field": "email", "message": "required" }
  ]
}
AttributeTypeDescription
typestringA URI that identifies the error type. Use this for programmatic error handling.
titlestringA short, human-readable summary of the problem type.
statusintegerThe HTTP status code.
detailstringA human-readable explanation specific to this occurrence.
instancestringThe request path that generated the error. Optional.
errorsarrayField-level validation errors. Only present on validation failures.

Error types

Every error type URI maps to a specific category. Use the type suffix for programmatic handling.

Type suffixHTTP StatusTitleWhen it happens
validation-error400Validation failedRequest body fails schema validation or business rules.
bad-request400Bad requestMalformed request, missing required parameters.
unauthorized401UnauthorizedMissing or invalid API key / JWT token.
payment-required402Payment requiredTrial has expired. Upgrade to continue.
forbidden403ForbiddenValid credentials but insufficient permissions (e.g., admin-only endpoint).
not-found404Not foundThe requested resource does not exist or does not belong to your tenant.
conflict409ConflictResource already exists (e.g., duplicate webhook URL, duplicate source name).
payload-too-large413Payload too largeRequest body exceeds the size limit.
quota-exceeded429Quota exceededIdentity count or rate limit exceeded. Respect Retry-After header.
internal-error500Internal server errorSomething went wrong on our end. Retry with exponential backoff.

HTTP status codes

Kanoniv uses standard HTTP status codes grouped into three ranges:

  • 2xx - The request succeeded.
  • 4xx - The request failed due to something on the client side. Fix the request and retry.
  • 5xx - The request failed due to something on the server side. Retry with backoff.
CodeMeaning
200OK - Request succeeded.
201Created - Resource was created.
204No Content - Successful delete, no response body.
400Bad Request - Invalid parameters or request body.
401Unauthorized - Invalid or missing authentication.
402Payment Required - Trial expired.
403Forbidden - Insufficient permissions.
404Not Found - Resource does not exist.
409Conflict - Resource already exists.
413Payload Too Large - Request body exceeds limit.
429Too Many Requests - Rate or quota limit exceeded.
500Internal Server Error - Retry with backoff.

Handling errors

Python SDK

The Python SDK maps HTTP errors to typed exceptions. Catch specific types for targeted handling.

python
from kanoniv import (
    KanonivError,        # Base - catch all API errors
    ValidationError,     # 400 - bad request body
    AuthenticationError, # 401 - invalid credentials
    ForbiddenError,      # 403 - insufficient permissions
    NotFoundError,       # 404 - resource not found
    ConflictError,       # 409 - duplicate resource
    RateLimitError,      # 429 - rate/quota limit hit
    ServerError,         # 5xx - server-side issue
)

try:
    client.resolve_rt.realtime(
        source_name="crm",
        external_id="cus_123",
        data={"email": "[email protected]"},
    )
except NotFoundError:
    print("Source not found - check the source name")
except RateLimitError as e:
    print(f"Rate limited - retry after {e.retry_after}s")
except ValidationError as e:
    print(f"Bad request: {e.detail}")
except KanonivError as e:
    print(f"API error {e.status_code}: {e}")

TypeScript SDK

The TypeScript SDK provides the same error hierarchy as Python, using class-based exceptions.

typescript
import {
  KanonivClient,
  KanonivError,        // Base - catch all API errors
  ValidationError,     // 400 - bad request body
  AuthenticationError, // 401 - invalid credentials
  ForbiddenError,      // 403 - insufficient permissions
  NotFoundError,       // 404 - resource not found
  ConflictError,       // 409 - duplicate resource
  RateLimitError,      // 429 - rate/quota limit hit
  ServerError,         // 5xx - server-side issue
} from '@kanoniv/sdk';

try {
  await client.resolveRt.realtime({
    sourceName: 'crm',
    externalId: 'cus_123',
    data: { email: '[email protected]' },
  });
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log('Source not found - check the source name');
  } else if (err instanceof RateLimitError) {
    console.log(`Rate limited - retry after ${err.retryAfter}s`);
  } else if (err instanceof ValidationError) {
    console.log(`Bad request: ${err.message}`);
  } else if (err instanceof KanonivError) {
    console.log(`API error ${err.statusCode}: ${err.message}`);
  }
}

Go SDK

The Go SDK uses sentinel errors with errors.Is() for matching specific error types.

go
import "errors"

result, err := client.ResolveRt.Realtime(ctx, &kanoniv.RealtimeResolveParams{
    SourceName: "crm",
    ExternalID: "cus_123",
    Data:       map[string]interface{}{"email": "[email protected]"},
})
if err != nil {
    var apiErr *kanoniv.KanonivError
    if errors.As(err, &apiErr) {
        switch {
        case errors.Is(err, kanoniv.ErrNotFound):
            fmt.Println("Source not found - check the source name")
        case errors.Is(err, kanoniv.ErrRateLimit):
            fmt.Printf("Rate limited - retry after %.0fs\n", apiErr.RateLimitInfo())
        case errors.Is(err, kanoniv.ErrValidation):
            fmt.Printf("Bad request: %s\n", apiErr.Message)
        default:
            fmt.Printf("API error %d: %s\n", apiErr.StatusCode, apiErr.Message)
        }
    }
}

curl

Check the HTTP status code and parse the JSON body:

bash
curl -s -w "\n%{http_code}" https://api.kanoniv.com/v1/entities/nonexistent \
  -H "X-API-Key: kn_..." | {
  read body
  read code
  if [ "$code" -ne 200 ]; then
    echo "Error $code: $(echo $body | jq -r '.detail')"
  fi
}

Validation errors

When a request body fails validation, the response includes an errors array with per-field details:

json
{
  "type": "https://api.kanoniv.com/errors/validation-error",
  "title": "Validation failed",
  "status": 400,
  "detail": "Request body has validation errors",
  "errors": [
    { "field": "url", "message": "URL must use HTTPS" },
    { "field": "event_types", "message": "At least one event type is required" }
  ]
}

Rate limiting

When you exceed the rate limit, the response includes a Retry-After header. The Python SDK respects this automatically.

http
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/problem+json

{
  "type": "https://api.kanoniv.com/errors/quota-exceeded",
  "title": "Quota exceeded",
  "status": 429,
  "detail": "Rate limit exceeded. Retry after 30 seconds."
}

Rate limit headers are included on every response:

http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1707134400

Best practices

  1. Handle errors by type, not status code. The type field is stable and specific. Status codes are shared across error types (e.g., both validation-error and bad-request return 400).

  2. Retry 5xx errors with exponential backoff. Server errors are transient. Use jitter to avoid thundering herd.

  3. Never retry 4xx errors without changing the request. Client errors indicate a problem with your request - retrying the same request will fail again.

  4. Log the full error object for debugging. The detail field often contains the specific issue. For 5xx errors, include the instance field when contacting support.

  5. Use idempotency keys for safe retries. When retrying POST requests, include an Idempotency-Key header to prevent duplicate operations. See Idempotency.

The identity and delegation layer for AI agents.