Skip to content

Python SDK — Getting Started

sencai-sdk is a Python client for the Sencai Platform API (/api/v1/*), generated from the public OpenAPI spec (openapi-generator-cli, python template — F4.DEVPORTAL.02). It lives at sdk-python/ in the monorepo (a standalone pyproject.toml/setup.cfg package, not an npm workspace member).

sencai-sdk is not yet published to PyPI — public package distribution is a separate business/release decision, out of scope for the SDK generation work itself. The package layout is already PyPI-ready; until publication, install it from the monorepo path or directly from git:

Terminal window
# From the monorepo checkout
python3 -m venv .venv
.venv/bin/pip install -e ./sdk-python
# Directly from git
pip install "git+https://github.com/sencai-space/sdk-python.git"
from sencai_sdk import ApiClient, Configuration, OrganisationApi
config = Configuration(
# Local dev: http://api.sencai.localhost/api/v1 or http://localhost:1337/api/v1
# Production: https://api.sencai.space/api/v1
host=os.environ["SENCAI_API_BASE_URL"],
# Bearer JWT (Keycloak access token today) — or a future API-key header. The
# client only provides the injection point; it does not fetch or refresh
# the token for you.
access_token=os.environ["SENCAI_ACCESS_TOKEN"],
)
with ApiClient(config) as client:
organisations = OrganisationApi(client)

Configuration.host and access_token are always constructor parameters — the package never hardcodes a production URL.

Complete example: list organisations, invite a member

Section titled “Complete example: list organisations, invite a member”

Verified against a local run-local-dev.sh stack (backend, backend-db, auth profiles).

import os
import sencai_sdk
config = sencai_sdk.Configuration(
host=os.environ["SENCAI_API_BASE_URL"],
access_token=os.environ["SENCAI_ACCESS_TOKEN"],
)
with sencai_sdk.ApiClient(config) as client:
organisations = sencai_sdk.OrganisationApi(client)
# List organisations the caller belongs to.
result = organisations.find_organisation(pagination_page_size=10)
print([org.name for org in result.data])
# -> ['QA Admin Workspace', 'e2e-org-d18a54e2', 'e2e-org-d9e67836']
target_org = result.data[0]
# Invite an existing Strapi user into that organisation. `POST
# /organisations/{id}/invite` requires the invitee to already have a
# registered account (see the Error handling section below for what
# happens otherwise) and the caller to be admin+ in the organisation.
body = sencai_sdk.OrganisationInviteMemberRequest(email="new-member@example.com", role="viewer")
invite = organisations.organisation_invite_member(target_org.document_id, body)
print(invite.data)
# -> OrganisationInviteMember200ResponseData(id=1264, attributes=OrganisationInviteMember200ResponseDataAttributes(
# email='new-member@example.com', role='viewer', invited_by='you@example.com',
# invited_at=datetime.datetime(2026, 7, 5, 11, 56, 34, tzinfo=TzInfo(0)), status='pending'))

Every non-2xx response raises sencai_sdk.exceptions.ApiException (or a status-specific subclass, e.g. UnauthorizedException/ForbiddenException) with .status and a raw .body string — the typed method never returns a partially-valid result on failure. Error bodies follow Strapi’s standard envelope: { "error": { "status", "name", "message", "details" } }.

import json
from sencai_sdk.exceptions import ApiException
try:
organisations.organisation_invite_member(target_org.document_id, body)
except ApiException as err:
payload = json.loads(err.body)["error"]
print(err.status, payload["name"], payload["message"])
# A real, commonly hit example — the organisation's plan seat limit:
# 402 PaymentRequired "Member limit reached (3/3). Upgrade your plan."
# payload["details"] -> {"limit": 3, "current": 3, "limit_source": "organisation_plan", ...}

Other status codes you should expect from this endpoint specifically: 400 (missing/invalid email/role, or no Strapi user registered with that email — this endpoint does not invite not-yet-registered emails), 401 (missing/expired token), 403 (caller is not admin+ in the organisation), 409 (that user is already a member).

The package version (pyproject.toml/setup.pyversion) is derived from the OpenAPI spec’s info.version on every regeneration — it is never bumped by hand. A breaking API change ships as a new spec major version, which regenerates this SDK as a new major version too. See API versioning for what counts as breaking, the /api/v1//api/v2/ migration path, and the SUNSET_DATE deprecation-header convention — this page does not repeat that policy, only how it surfaces in the generated client.

For the full generated reference of every operation this client exposes, see the API Reference.