Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Modellen Tools & Services
Oplossingen
Organisaties Ontwikkelaars Internet Service Providers Managed Service Providers AI-in-a-Box
Kenniscentrum
Ondersteuning Documentation Blog Downloads
Bedrijf
Over ons Onderzoek Vacatures Investeren Contact
Inloggen

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 to register a service-to-service application and share the credentials.

Once you have them, export:

bash
1
2
3
4
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
1
pip install scaiscribe

TypeScript#

bash
1
npm install @scaiscribe/sdk

.NET#

bash
1
dotnet add package ScaiLabs.ScaiScribe

:::

3. Build a document#

::: tabs

Python#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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
1
2
3
4
5
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#

Updated 2026-07-03 13:00:09 View source (.md) rev 17