Skip to main content

Python vs. TypeScript SDK comparison

This document compares the two official client SDKs for the Credo AI integration service:

  • Python SDKpycredoai (v0.1.0), in production.
  • TypeScript SDK@credo-ai/sdk (v0.0.4).

Package versions above are read from each SDK's manifest at build time (generated 2026-05-29).

Both SDKs are generated from the same OpenAPI specification served by this service, target the /api/v1/integration prefix, and accept the same CREDOAI_API_KEY, CREDOAI_API_URL, and CREDOAI_TENANT environment variables. Beyond that shared foundation, they diverge in meaningful ways.

Install

pip install pycredoai

See Installation for runtime requirements and version pinning.

Quickstart

Same task in each SDK — list the first 10 use cases:

from credoai import CredoAI

client = CredoAI()
use_cases = client.use_cases.lists()
for uc in use_cases[:10]:
print(uc.id, uc.name)

At-a-glance summary

DimensionPythonTypeScript
Packagepycredoai@credo-ai/sdk
Entry pointCredoAI / AsyncCredoAI classescreateCredoAIClient() factory
HTTP transporthttpxnative fetch
Sync supportYes (CredoAI)No (Promise-based only)
Async supportYes (AsyncCredoAI)Yes (all methods return Promises)
RuntimeCPython (>=3.10)Node 18+, Bun, Deno, browsers (ESM only)
Error modelRaised exception hierarchy{ data, error } result tuple
Auto-pagination helpersYes (list_all, async_list_all)No
Context managersYes (with / async with)No
Token refreshOne-shot at initCached with TTL, auto-refresh
Default serializationPlain JSON (Pydantic)Plain JSON on the high-level client; JSON:API available through the low-level request API
Runtime validationYes (Pydantic)No (types-only)
Built-in retry/backoffNoNo

Resource coverage

Both SDKs are generated from the same OpenAPI spec, so the overlap on API resources is large — but not identical.

In both SDKs

Python accessorTypeScript accessor
client.use_casesclient.useCases
client.modelsclient.models
client.vendorsclient.vendors
client.policy_controlsclient.policyControls
client.policy_packsclient.policyPacks
client.policy_control_basesclient.policyControlBases
client.policy_control_versionsclient.policyControlVersions
client.questionnairesclient.questionnaires
client.risk_typesclient.riskTypes
client.risk_scenariosclient.riskScenarios
client.workflow_stagesclient.workflowStages
client.systemclient.system
client.authenticationclient.authentication

Nested/relationship operations (e.g. use_cases → controls, use_cases → policy_packs, models → questionnaires, vendors → use_cases) are covered in both SDKs, though exposed differently — see Nested resources.

Python only

  • audit_logs — the Python SDK exposes audit-log list/query/get endpoints. The TypeScript SDK does not include an audit-logs resource.
  • health — the Python SDK exposes client.health.check_liveness() and client.health.check_readiness(). The TypeScript SDK's generator intentionally skips health-tag endpoints (documented under "Known limitations" in its README). If you need liveness/readiness probes from a TypeScript application, call /livez and /readyz manually.

TypeScript only

No resources are exposed exclusively by the TypeScript SDK. Its surface is a strict subset of the Python SDK's.

Client construction

Python

Two separate classes, chosen at import time, each with a context-manager form that manages the underlying httpx connection pool:

from credoai import CredoAI, AsyncCredoAI

client = CredoAI() # synchronous
async_client = AsyncCredoAI() # asynchronous

with CredoAI() as client:
...

async with AsyncCredoAI() as client:
...

TypeScript

A single factory function returning one client. All methods are asynchronous. There is no sync variant and no context-manager equivalent — the fetch-based transport has no explicit connection lifecycle to clean up.

import { createCredoAIClient } from '@credo-ai/sdk';
const client = createCredoAIClient('acme');

Error handling

The two SDKs take fundamentally different philosophical approaches here.

Python: typed exception hierarchy

Errors are raised. The SDK inspects the HTTP response and throws a specific subclass:

  • ApiError (base) — carries status_code, message, request_id, error_details.
  • AuthenticationError (401/403)
  • RateLimitError (429) — exposes parsed retry_after, limit, remaining, reset_time.
  • ServerError (5xx)
  • TimeoutError, ConnectionError, ValidationError

Callers use try/except and can branch on the exception type.

TypeScript: result tuple

Every method returns { data, error }. There is a single CredoAIError class exposing .status and .body — no subclasses, no parsing of rate-limit headers, no request-ID extraction. An isCredoAIError() type guard is exported for narrowing unknown errors. Callers that want structured rate-limit handling must inspect error.body or response headers themselves.

from credoai import CredoAI
from credoai.errors import ApiError, RateLimitError

client = CredoAI()
try:
uc = client.use_cases.get(use_case_id="missing")
except RateLimitError as e:
print(f"retry after {e.retry_after}s")
except ApiError as e:
print(f"{e.status_code}: {e.message}")

Pagination

Both SDKs use cursor-based pagination, returning a PaginatedResponse<T> wrapper with items and pagination metadata (next_cursor/nextCursor, has_more/hasMore).

Python-only convenience: every list-capable resource exposes list_all() (sync generator) and async_list_all() (async generator) that transparently walk the cursor until exhausted. The TypeScript SDK has no equivalent — callers must loop on nextCursor themselves.

for uc in client.use_cases.list_all():
print(uc.name)

Authentication and token lifecycle

Both SDKs accept an API key and tenant, call /auth/token to exchange them for a JWT, then send Authorization: Bearer <jwt> on subsequent requests.

The lifecycle differs:

  • Python performs the token exchange once at client initialization. There is no automatic refresh; a long-lived client will eventually see the token expire.
  • TypeScript caches the token and refreshes on expiry using the server's expires_in (falling back to 55 minutes if absent). It also deduplicates concurrent refreshes so a burst of parallel requests triggers only one token exchange.

The TypeScript SDK also accepts a pre-exchanged bearer token directly through createCredoAIClient options, bypassing API-key exchange entirely. The Python client's top-level __init__ accepts api_key only; passing a pre-exchanged bearer requires using the underlying credoai.auth handler directly (authenticate_with_bearer).

Nested resources

The same nested operations are available in both SDKs but surfaced differently.

  • Python offers two styles: direct sub-resource access (client.use_cases.controls.list(use_case_id=...)) and a fluent accessor pattern (client.use_case("id").controls.list()) via a dedicated credoai.fluent module.
  • TypeScript exposes nested resources as typed properties only (client.useCases.controls.list('use-case-id', { pageLimit: 25 })). There is no fluent/chained-handle equivalent.

Typing and validation

  • Python uses Pydantic v2 models throughout. Requests and responses are validated at runtime; malformed payloads raise ValidationError before they reach your code.
  • TypeScript provides compile-time types only. Responses are deserialized but not validated against a schema. A response that disagrees with its declared type surfaces as a downstream runtime error rather than at the deserialization boundary.

Serialization format

The integration service's /api/v1/integration endpoints — the only surface both high-level clients talk to — accept and emit plain JSON, not JSON:API envelopes. Neither SDK is performing format conversion on this path.

  • Python serializes requests and deserializes responses via Pydantic, with snake_casecamelCase aliasing handled by field aliases. There is no JSON:API path in the public API.
  • TypeScript defaults to plain JSON for the high-level client. Its low-level createRequest() additionally supports format: 'json-api' (via jsonapi-serializer) for advanced callers who need to hit non-integration endpoints that speak JSON:API. Python has no equivalent at this layer.

Retry and timeout behavior

Neither SDK currently ships retry/backoff logic.

  • Python defaults to a 30-second timeout and exposes timeout, verify_ssl, and an httpx_args passthrough for event hooks, proxies, and other httpx configuration.
  • TypeScript has no default timeout; a timeout option (milliseconds) can be passed and is applied via AbortSignal.timeout(). Per-call requestInit is available through the low-level createRequest() API but, per the README, is not exposed on generated high-level resource methods.

Runtime and distribution

  • Python targets CPython (>=3.10), ships as a standard uv-built wheel, and has no browser story.
  • TypeScript is ESM-only, isomorphic, and has no CommonJS build. It runs in Node 18+ (native fetch), Deno, Bun, and modern browsers — this is the primary strategic advantage of the TypeScript SDK.

Low-level / escape hatches

  • Python: AuthenticatedClient (the base class of CredoAI) for direct httpx-based calls, and httpx_args for event hooks, proxies, and custom SSL.
  • TypeScript: createRequest() as a typed low-level request factory with templated path strings (e.g. 'GET /use_cases/:useCaseId'), plus exported serialize/deserialize helpers for JSON:API.

Observability

  • Python has no built-in client-side logging (it relies on the standard logging module if the host application wires one up) but supports arbitrary httpx event hooks via httpx_args.
  • TypeScript reads a CREDOAI_DEBUG environment variable and, when set, logs token-exchange activity and request URLs to the console.

The Python ApiError captures and surfaces the X-Request-ID header; the TypeScript CredoAIError does not expose a request-ID field.

Choosing between them

Pick based on runtime, not on feature set:

  • Server-side Python workloads, data pipelines, CLIs, notebooks → Python SDK. Richer ergonomics: pagination iterators, sync support, typed exceptions, Pydantic validation, audit-log and health-probe access.
  • Browser apps, full-stack TypeScript services, edge/serverless runtimes → TypeScript SDK. Only option that runs outside Python, with better token lifecycle management out of the box.

If you need audit logs or liveness/readiness probes from a TypeScript app today, you will need to issue those requests manually (or open an issue to unskip health-tag generation and add an auditLogs resource).

Known gaps (both SDKs)

  • No built-in retry or exponential backoff.
  • No file upload / multipart helpers.
  • No streaming-response helpers (underlying transports support it; the SDKs don't expose it).
  • No Model Context Protocol (MCP) or AI-assistant integration hooks inside the SDK packages themselves. The integration service exposes an MCP server of its own (configured via ENABLE_MCP_SERVER and related settings); it is not reachable through either SDK.