Python vs. TypeScript SDK comparison
This document compares the two official client SDKs for the Credo AI integration service:
- Python SDK —
pycredoai(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
- Python
- TypeScript
pip install pycredoai
npm install @credo-ai/sdk
See Installation for runtime requirements and version pinning.
Quickstart
Same task in each SDK — list the first 10 use cases:
- Python
- TypeScript
from credoai import CredoAI
client = CredoAI()
use_cases = client.use_cases.lists()
for uc in use_cases[:10]:
print(uc.id, uc.name)
import { createCredoAIClient } from '@credo-ai/sdk';
const client = createCredoAIClient('your-tenant');
const { data, error } = await client.useCases.list({ pageLimit: 10 });
if (error) {
console.error(error.status, error.body);
} else {
for (const uc of data.items) {
console.log(uc.id, uc.name);
}
}
At-a-glance summary
| Dimension | Python | TypeScript |
|---|---|---|
| Package | pycredoai | @credo-ai/sdk |
| Entry point | CredoAI / AsyncCredoAI classes | createCredoAIClient() factory |
| HTTP transport | httpx | native fetch |
| Sync support | Yes (CredoAI) | No (Promise-based only) |
| Async support | Yes (AsyncCredoAI) | Yes (all methods return Promises) |
| Runtime | CPython (>=3.10) | Node 18+, Bun, Deno, browsers (ESM only) |
| Error model | Raised exception hierarchy | { data, error } result tuple |
| Auto-pagination helpers | Yes (list_all, async_list_all) | No |
| Context managers | Yes (with / async with) | No |
| Token refresh | One-shot at init | Cached with TTL, auto-refresh |
| Default serialization | Plain JSON (Pydantic) | Plain JSON on the high-level client; JSON:API available through the low-level request API |
| Runtime validation | Yes (Pydantic) | No (types-only) |
| Built-in retry/backoff | No | No |
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 accessor | TypeScript accessor |
|---|---|
client.use_cases | client.useCases |
client.models | client.models |
client.vendors | client.vendors |
client.policy_controls | client.policyControls |
client.policy_packs | client.policyPacks |
client.policy_control_bases | client.policyControlBases |
client.policy_control_versions | client.policyControlVersions |
client.questionnaires | client.questionnaires |
client.risk_types | client.riskTypes |
client.risk_scenarios | client.riskScenarios |
client.workflow_stages | client.workflowStages |
client.system | client.system |
client.authentication | client.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 exposesclient.health.check_liveness()andclient.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/livezand/readyzmanually.
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) — carriesstatus_code,message,request_id,error_details.AuthenticationError(401/403)RateLimitError(429) — exposes parsedretry_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.
- Python
- TypeScript
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}")
import { createCredoAIClient, isCredoAIError } from '@credo-ai/sdk';
const client = createCredoAIClient('acme');
const { data, error } = await client.useCases.get('missing');
if (error && isCredoAIError(error)) {
console.error(error.status, error.body);
}
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.
- Python
- TypeScript
for uc in client.use_cases.list_all():
print(uc.name)
let cursor: string | undefined;
do {
const { data, error } = await client.useCases.list({ pageCursor: cursor });
if (error) throw error;
for (const uc of data.items) console.log(uc.name);
cursor = data.pagination.nextCursor;
} while (cursor);
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 dedicatedcredoai.fluentmodule. - 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
ValidationErrorbefore 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_case↔camelCasealiasing 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 supportsformat: 'json-api'(viajsonapi-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 anhttpx_argspassthrough for event hooks, proxies, and otherhttpxconfiguration. - TypeScript has no default timeout; a
timeoutoption (milliseconds) can be passed and is applied viaAbortSignal.timeout(). Per-callrequestInitis available through the low-levelcreateRequest()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 ofCredoAI) for directhttpx-based calls, andhttpx_argsfor 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 exportedserialize/deserializehelpers for JSON:API.
Observability
- Python has no built-in client-side logging (it relies on the standard
loggingmodule if the host application wires one up) but supports arbitraryhttpxevent hooks viahttpx_args. - TypeScript reads a
CREDOAI_DEBUGenvironment 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_SERVERand related settings); it is not reachable through either SDK.