---
title: SDKs
path: sdk
status: published
---

# SDKs

The ScaiLog SDK is the emit side of the system: your service calls it to write a
structured log entry, and it ships that entry to the local `scailog-agent` (or a
standalone server's embedded agent). ScaiLog ships four SDKs — Python,
TypeScript, Go, and .NET — that are **wire-identical**: the same log emitted from
any of them is encrypted at ingest and decrypts correctly back through DSAR, and
each SDK re-checks the shared Annex A.3 checksum vector in its own test suite so
the wire format cannot drift between languages.

## The shared model

Every SDK enforces the same contract, so the rules you learn in one language
carry over unchanged to the others.

- **A mandatory, validated `call_site_id`.** The first argument to every emit
  call is a developer-assigned call-site id. It must match
  `^[A-Z0-9]+(-[A-Z0-9]+){2,}$` (at least three uppercase-alphanumeric segments,
  e.g. `SCAI-CHECKOUT-PAID-001`). The `AUTO-` and `SCAILOG-` prefixes are
  reserved. Call-site provenance is first-class in ScaiLog: GDPR remediation is
  "fix one call site."
- **Field-level PI marking with `pi(value, type)`.** You mark a single named
  field as personal information by wrapping its value, e.g.
  `pi("jan@example.nl", "direct_identifier")`. Markers attach to *fields* only —
  never to the whole entry and never to the free-text `message`, which is indexed
  in plaintext (FTS5) and must stay PI-free.
- **A subject is required whenever any PI is present.** If an entry carries one
  or more `pi()` fields, it must also carry a `subject` (the data-subject id).
  This is enforced in the SDK before the entry leaves the process, and again at
  the server (`SUBJECT_REQUIRED`).
- **Reserved field names are rejected.** Field names that collide with reserved
  top-level keys (`event_id`, `tenant`, `service`, `env`, `site`, `level`, `ts`,
  `msg`, `fields`, `pi`, `subject`, `trace_id`, `request_id`) are not allowed.
- **ULID event ids.** Each entry gets a lexicographically sortable ULID, returned
  by the emit call.
- **Socket NDJSON with a non-silent stderr fallback.** Entries are queued in
  process and sent as newline-delimited JSON over a Unix domain socket to the
  local agent. If the queue is full or the socket is unavailable, the entry is
  written to stderr in the same NDJSON shape rather than being silently dropped —
  and PI field *values* are redacted first (the field name and PI type stay, so
  the drop is auditable) so stderr never leaks the plaintext ScaiLog exists to
  protect.

The six log levels are the same everywhere: `trace`, `debug`, `info`, `warn`,
`error`, `fatal`.

## Language pages

| SDK | Package | Page |
| --- | --- | --- |
| Python (reference) | `scailog` (PyPI) | [Python SDK](/docs/scailog/sdk/python) |
| TypeScript | `@scailabs/scailog` (npm) | [TypeScript SDK](/docs/scailog/sdk/typescript) |
| Go | `github.com/scailabs/scailog-go` | [Go SDK](/docs/scailog/sdk/go) |
| .NET | `ScaiLabs.ScaiLog` (net8.0) | [.NET SDK](/docs/scailog/sdk/dotnet) |

The Python SDK is the reference implementation; the other three mirror it. The
Python base install is **pure stdlib** — `pip install scailog` pulls zero
third-party dependencies, so a service that only emits logs stays featherweight.

## Validating call sites in CI

Beyond the runtime SDK, the zero-dependency `scailog-ci` tool statically checks
your call sites at build time — constant, unique, well-formed `call_site_id`; no
message interpolation; `pi()` accompanied by a subject — and can push a call-site
manifest to the server. See
[CI call-site validation](/docs/scailog/sdk/ci-validation).
