---
audience: developers
summary: "From zero to a downloaded DOCX in five minutes \u2014 Python, TypeScript,\
  \ or .NET."
title: Quickstart
path: quickstart
status: published
---


# Quickstart

Five minutes from zero credentials to a downloaded DOCX. Pick your language.

## 1. Get credentials

ScaiScribe authenticates through ScaiKey. You need a `client_id` and `client_secret` from your tenant's ScaiKey admin. If you don't have them yet, ask whoever runs your tenant's [ScaiKey](https://scailabs.ai/docs/scaikey) to register a service-to-service application and share the credentials.

Once you have them, export:

```bash
export SCAIKEY_CLIENT_ID=...
export SCAIKEY_CLIENT_SECRET=...
export SCAISCRIBE_BASE_URL=https://scaiscribe.scailabs.ai
export SCAISCRIBE_TENANT_ID=tnt_...   # the tenant you're operating on
```

> ScaiKey service tokens (the default — `ScaiKeyAuth`) are issued globally
> and carry no `tenant_id` claim. ScaiScribe needs to know which tenant a
> document belongs to, so the SDK sends `X-ScaiScribe-Tenant-Id` on every
> request when you pass `default_tenant_id` (Python) / `defaultTenantId`
> (TS / .NET). Skip this knob when using a user-bound token (e.g. OIDC
> via `BearerAuth`) — those carry the tenant in the JWT already.

## 2. Install the SDK

::: tabs

### Python

```bash
pip install scaiscribe
```

### TypeScript

```bash
npm install @scaiscribe/sdk
```

### .NET

```bash
dotnet add package ScaiLabs.ScaiScribe
```

:::

## 3. Build a document

::: tabs

### Python

```python
import os
from scaiscribe import Client, ScaiKeyAuth

auth = ScaiKeyAuth(
    client_id=os.environ["SCAIKEY_CLIENT_ID"],
    client_secret=os.environ["SCAIKEY_CLIENT_SECRET"],
)

with Client(
    os.environ["SCAISCRIBE_BASE_URL"],
    auth=auth,
    default_tenant_id=os.environ["SCAISCRIBE_TENANT_ID"],
) as scribe:
    doc = scribe.create_document(format="docx", theme="office", title="Hello")

    scribe.add_element(doc.doc_id, {"type": "heading", "level": 1, "text": "Hello"})
    scribe.add_element(doc.doc_id, {
        "type": "paragraph",
        "markdown": "This was generated by **ScaiScribe**.",
    })

    result = scribe.finalize(doc.doc_id, formats=["docx", "pdf"])
    for r in result.outputs:
        print(f"{r.format}: {r.download_url}")
```

### TypeScript

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

const scribe = new Client(process.env.SCAISCRIBE_BASE_URL!, {
  auth: new ScaiKeyAuth({
    clientId: process.env.SCAIKEY_CLIENT_ID!,
    clientSecret: process.env.SCAIKEY_CLIENT_SECRET!,
  }),
  defaultTenantId: process.env.SCAISCRIBE_TENANT_ID!,
});

const doc = await scribe.createDocument({
  format: "docx",
  theme: "office",
  title: "Hello",
});

await scribe.addElement(doc.doc_id, { type: "heading", level: 1, text: "Hello" });
await scribe.addElement(doc.doc_id, {
  type: "paragraph",
  markdown: "This was generated by **ScaiScribe**.",
});

const result = await scribe.finalize(doc.doc_id, { formats: ["docx", "pdf"] });
for (const r of result.outputs) {
  console.log(`${r.format}: ${r.download_url}`);
}
```

### .NET

```csharp
using ScaiLabs.ScaiScribe;

using var auth = new ScaiKeyAuth(
    clientId: Environment.GetEnvironmentVariable("SCAIKEY_CLIENT_ID")!,
    clientSecret: Environment.GetEnvironmentVariable("SCAIKEY_CLIENT_SECRET")!);

using var scribe = new ScaiScribeClient(
    Environment.GetEnvironmentVariable("SCAISCRIBE_BASE_URL")!,
    auth,
    defaultTenantId: Environment.GetEnvironmentVariable("SCAISCRIBE_TENANT_ID")!);

var doc = await scribe.CreateDocumentAsync(new CreateDocumentInput
{
    Format = "docx",
    Theme = "office",
    Title = "Hello",
});

await scribe.AddElementAsync(doc.DocId, new Dictionary<string, object>
{
    ["type"] = "heading", ["level"] = 1, ["text"] = "Hello",
});
await scribe.AddElementAsync(doc.DocId, new Dictionary<string, object>
{
    ["type"] = "paragraph",
    ["markdown"] = "This was generated by **ScaiScribe**.",
});

var result = await scribe.FinalizeAsync(doc.DocId, new FinalizeInput
{
    Formats = new[] { "docx", "pdf" },
});
foreach (var r in result.Outputs)
{
    Console.WriteLine($"{r.Format}: {r.DownloadUrl}");
}
```

### curl

```bash
TOKEN=$(curl -s -X POST "https://scaikey.scailabs.ai/api/v1/platform/oauth/token" \
    -d "grant_type=client_credentials&client_id=$SCAIKEY_CLIENT_ID&client_secret=$SCAIKEY_CLIENT_SECRET&scope=api:read api:write" \
    | jq -r .access_token)

DOC=$(curl -s -X POST "$SCAISCRIBE_BASE_URL/v1/documents" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{"format":"docx","theme": "office","title":"Hello"}' | jq -r .doc_id)

curl -s -X PATCH "$SCAISCRIBE_BASE_URL/v1/documents/$DOC" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{"operations":[{"op":"add_element","element":{"type":"heading","level":1,"text":"Hello"}}]}'

curl -s -X POST "$SCAISCRIBE_BASE_URL/v1/documents/$DOC/finalize" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{"formats":["docx","pdf"],"sync":true}' | jq
```

:::

## 4. Download the artefact

Each `RenderArtefact` carries a presigned `download_url` valid for ~1 hour. Stream it:

```python
import httpx
with httpx.stream("GET", docx.download_url) as resp:
    with open("hello.docx", "wb") as f:
        for chunk in resp.iter_bytes():
            f.write(chunk)
```

## What you just did

- Authenticated to ScaiKey via OAuth `client_credentials` (the SDK did this transparently).
- Created a *draft* document — server-side resource that holds the spec under construction.
- Added two elements (heading + paragraph). The PATCH endpoint applies a list of `add_element` / `edit_element` / `delete_element` / `move_element` operations atomically.
- Finalised — the server queued render jobs across all requested formats, waited for them to complete (default 15s timeout), and returned the artefacts with presigned download URLs.

Most steps that look async (queue + job + result) are sync-wrapped under the hood — the SDK hides the 202+poll pattern unless you pass `sync=False`.

## Next

- **Themes and styling** — see [Concepts → Themes](/docs/scaiscribe/concepts).
- **Template-based generation** — register a `.docx` with placeholders, fill it with structured data: [Tutorials → Templates](/docs/scaiscribe/tutorials).
- **AI agents** — the MCP surface mirrors every operation: [Reference → MCP](/docs/scaiscribe/reference).
