---
audience: developers
summary: Official Python, TypeScript, and .NET SDKs for ScaiScribe.
title: SDKs
path: reference/sdks
status: published
---


# SDKs

ScaiScribe ships three official client SDKs. All cover the same surface — document creation, editing, ingestion, plus an `admin` sub-client for operator surfaces.

## Python

**Install** — `pip install scaiscribe`

**Requires** — Python 3.10+

```python
from scaiscribe import Client, ScaiKeyAuth

auth = ScaiKeyAuth(client_id="...", client_secret="...")

# Sync
with Client("https://scaiscribe.scailabs.ai", auth=auth) as scribe:
    doc = scribe.create_document(format="docx", theme="office")
    scribe.add_element(doc.doc_id, {"type": "heading", "level": 1, "text": "Hi"})
    result = scribe.finalize(doc.doc_id, formats=["docx", "pdf"])

# Async — same surface, AsyncClient
from scaiscribe import AsyncClient
async with AsyncClient("https://scaiscribe.scailabs.ai", auth=auth) as scribe:
    doc = await scribe.create_document(format="docx")
    await scribe.add_element(doc.doc_id, {"type": "paragraph", "markdown": "Hi from async."})
    result = await scribe.finalize(doc.doc_id)
```

The Python SDK ships with `Client` (sync, `httpx.Client`-backed) and `AsyncClient` (async, `httpx.AsyncClient`-backed). Same methods on both; pick whichever fits your runtime.

## TypeScript

**Install** — `npm install @scaiscribe/sdk`

**Requires** — Node 20+ or any modern browser

```ts
import { Client, ScaiKeyAuth } from "@scaiscribe/sdk";

const scribe = new Client("https://scaiscribe.scailabs.ai", {
  auth: new ScaiKeyAuth({ clientId: "...", clientSecret: "..." }),
});

const doc = await scribe.createDocument({ format: "docx", theme: "office" });
await scribe.addElement(doc.doc_id, { type: "heading", level: 1, text: "Hi" });
const result = await scribe.finalize(doc.doc_id, { formats: ["docx", "pdf"] });
```

The TS SDK is `fetch`-based and async-only (idiomatic for the JS runtime). Browser usage works the same way; just ensure CORS is permitted from your origin (set up by your ScaiScribe operator).

## .NET

**Install** — `dotnet add package ScaiLabs.ScaiScribe`

**Requires** — .NET 8.0+

```csharp
using ScaiLabs.ScaiScribe;

using var auth = new ScaiKeyAuth(clientId: "...", clientSecret: "...");
using var scribe = new ScaiScribeClient("https://scaiscribe.scailabs.ai", auth);

var doc = await scribe.CreateDocumentAsync(new CreateDocumentInput
{
    Format = "docx",
    Theme = "office",
});
await scribe.AddElementAsync(doc.DocId, new Dictionary<string, object>
{
    ["type"] = "heading", ["level"] = 1, ["text"] = "Hi",
});
var result = await scribe.FinalizeAsync(doc.DocId, new FinalizeInput
{
    Formats = new[] { "docx", "pdf" },
});
```

`HttpClient`-based, async-only, idiomatic naming. Implements `IDisposable` for both the auth and the client.

## Shared design

The three SDKs are deliberately aligned:

- **Same conceptual surface**: `Client` (with optional `AsyncClient` in Python), `DocumentEnvelope`, `JobEnvelope`, `RenderArtefact`, `PatchOp`, plus an `admin` sub-namespace.
- **Same auth model**: `ScaiKeyAuth` (`client_credentials` against ScaiKey, transparent refresh) is the default. `BearerAuth(token)` for pre-minted tokens.
- **Same error mapping**: every spec §6.6 error envelope maps to a typed exception with `.code`, `.message`, `.details`, `.request_id` carried through.
- **Same sync-wrap semantics**: long ops (`finalize`, `ingest_document`, `preview`) default `sync=true`; pass `sync=false` to get a `JobEnvelope` back and poll yourself.

## Operations covered

| Method (Python / TS casing in parens) | Endpoint |
|---|---|
| `create_document` / `createDocument` | `POST /v1/documents` |
| `get_document` / `getDocument` | `GET /v1/documents/{id}` |
| `delete_document` / `deleteDocument` | `DELETE /v1/documents/{id}` |
| `close_document` / `closeDocument` | `POST /v1/documents/{id}/close` |
| `patch_document` / `add_element` | `PATCH /v1/documents/{id}` |
| `finalize` | `POST /v1/documents/{id}/finalize` |
| `preview` | `POST /v1/documents/{id}/preview` |
| `from_template` / `fromTemplate` | `POST /v1/documents/from_template` |
| `ingest_document` / `ingestDocument` | `POST /v1/documents/ingest` |
| `get_job` / `getJob` | `GET /v1/jobs/{id}` |
| `upload_asset` / `list_assets` | `POST/GET /v1/assets` |
| `upload_font` / `list_fonts` / `delete_font` | `POST/GET/DELETE /v1/fonts` |
| `upload_template` / `list_templates` | `POST/GET /v1/templates` |
| `health` | `GET /v1/health` |
| `admin.*` (12 endpoints) | `/v1/admin/*` |

## Admin sub-client

For operator surfaces (roles, users, partner/tenant inspection, job result inspection, quota rollups), each SDK exposes an `admin` sub-client:

```python
# Python
scribe.admin.health()
scribe.admin.list_jobs(status="failed", limit=20)
scribe.admin.grant_role("usr_alice", role="superadmin")

# Quota rollups
scribe.admin.partner_quota_rollup("prt_acme", axis="renders_per_day")
scribe.admin.tenant_user_quota_rollup("tnt_acme", axis="renders_per_day")

# Webhook secret rotation (superadmin only)
rot = scribe.admin.rotate_webhook_secret()
print(rot.new_secret)   # install on ScaiKey side + update .env immediately
```

```ts
// TypeScript
await scribe.admin.health();
await scribe.admin.listJobs({ status: "failed", limit: 20 });
await scribe.admin.grantRole("usr_alice", { role: "superadmin" });

await scribe.admin.partnerQuotaRollup("prt_acme", { axis: "renders_per_day" });
await scribe.admin.tenantUserQuotaRollup("tnt_acme");

const rot = await scribe.admin.rotateWebhookSecret();
console.log(rot.new_secret);
```

```csharp
// .NET
await scribe.Admin.GetHealthAsync();
await scribe.Admin.ListJobsAsync(status: "failed", limit: 20);
await scribe.Admin.GrantRoleAsync("usr_alice", new GrantRoleInput { Role = "superadmin" });

await scribe.Admin.PartnerQuotaRollupAsync("prt_acme", axis: "renders_per_day");
await scribe.Admin.TenantUserQuotaRollupAsync("tnt_acme");

var rot = await scribe.Admin.RotateWebhookSecretAsync();
Console.WriteLine(rot.NewSecret);
```

You need a token with admin-level scope to use these. Browser-based admin sign-in goes through the [admin UI](https://scaiscribe.scailabs.ai/admin/) instead.

### Admin endpoint coverage

| Endpoint | Python | TS | .NET |
|---|---|---|---|
| `GET /v1/admin/config` | `admin.config()` | `admin.config()` | `Admin.GetConfigAsync()` |
| `GET /v1/admin/me` | `admin.me()` | `admin.me()` | `Admin.GetMeAsync()` |
| `GET /v1/admin/health` | `admin.health()` | `admin.health()` | `Admin.GetHealthAsync()` |
| `GET /v1/admin/jobs` | `admin.list_jobs()` | `admin.listJobs()` | `Admin.ListJobsAsync()` |
| `GET /v1/admin/jobs/{id}` | `admin.get_job()` | `admin.getJob()` | `Admin.GetJobAsync()` |
| `GET /v1/admin/users` | `admin.list_users()` | `admin.listUsers()` | `Admin.ListUsersAsync()` |
| `GET /v1/admin/users/{id}/roles` | `admin.list_user_roles()` | `admin.listUserRoles()` | `Admin.ListUserRolesAsync()` |
| `POST /v1/admin/users/{id}/roles` | `admin.grant_role()` | `admin.grantRole()` | `Admin.GrantRoleAsync()` |
| `DELETE /v1/admin/users/{id}/roles/{rid}` | `admin.revoke_role()` | `admin.revokeRole()` | `Admin.RevokeRoleAsync()` |
| `GET /v1/admin/tenants` | `admin.list_tenants()` | `admin.listTenants()` | `Admin.ListTenantsAsync()` |
| `GET /v1/admin/partners` | `admin.list_partners()` | `admin.listPartners()` | `Admin.ListPartnersAsync()` |
| `POST /v1/admin/scaikey/rotate-webhook-secret` | `admin.rotate_webhook_secret()` | `admin.rotateWebhookSecret()` | `Admin.RotateWebhookSecretAsync()` |
| `GET /v1/admin/quotas/partner/{id}` | `admin.partner_quota_rollup()` | `admin.partnerQuotaRollup()` | `Admin.PartnerQuotaRollupAsync()` |
| `GET /v1/admin/quotas/tenant/{id}/users` | `admin.tenant_user_quota_rollup()` | `admin.tenantUserQuotaRollup()` | `Admin.TenantUserQuotaRollupAsync()` |

## Generated spec types

Python and TypeScript SDKs ship types generated from the published JSON Schema. Import directly when you're building a document spec programmatically:

```python
# Python (in scaiscribe._generated.spec, re-exported)
from scaiscribe.spec import ScaiscribeDocumentSpecification
```

```ts
// TypeScript
import type { ScaiscribeDocumentSpecification } from "@scaiscribe/sdk/spec";
```

The .NET SDK uses hand-written wire-level types; a future release will add an NSwag codegen target.

## Versioning

All three SDKs follow semver. We pin to the spec's `schema_version` (currently `0.1`):

- `0.1.x` SDKs target `schema_version: "0.1"`
- Breaking changes to the spec bump the major; SDKs follow.
- Within a major, additive backend changes (new optional fields, new endpoints) are minor bumps.
