Skip to content

TypeScript SDK — Getting Started

@sencai/sdk is a TypeScript/JavaScript client for the Sencai Platform API (/api/v1/*), generated from the public OpenAPI spec (openapi-generator-cli, typescript-fetch template — F4.DEVPORTAL.02). It lives at packages/sdk-ts/ in the monorepo.

@sencai/sdk is not yet published to npm — public package distribution is a separate business/release decision, out of scope for the SDK generation work itself. Until then, install it from the monorepo path or directly from git:

Terminal window
# From inside the monorepo (npm workspace)
npm install ./packages/sdk-ts
# From outside the monorepo, over git
npm install git+https://github.com/sencai-space/packages.git#path:sdk-ts
import { Configuration, OrganisationApi } from "@sencai/sdk";
const config = new Configuration({
// Local dev: http://api.sencai.localhost/api/v1 or http://localhost:1337/api/v1
// Production: https://api.sencai.space/api/v1
basePath: process.env.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.
accessToken: async () => process.env.SENCAI_ACCESS_TOKEN!,
});
const organisations = new OrganisationApi(config);

basePath and accessToken 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 { Configuration, OrganisationApi } from "@sencai/sdk";
async function main() {
const config = new Configuration({
basePath: process.env.SENCAI_API_BASE_URL,
accessToken: async () => process.env.SENCAI_ACCESS_TOKEN!,
});
const organisations = new OrganisationApi(config);
// List organisations the caller belongs to.
const list = await organisations.findOrganisation({ paginationPageSize: 10 });
console.log(list.data.map((org) => org.name));
// -> [ "QA Admin Workspace", "e2e-org-d18a54e2", "e2e-org-d9e67836" ]
const targetOrg = list.data[0];
if (!targetOrg?.documentId) return;
// 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.
const invite = await organisations.organisationInviteMember({
id: targetOrg.documentId,
organisationInviteMemberRequest: {
email: "new-member@example.com",
role: "viewer",
},
});
console.log(invite.data);
// -> { id: 1266, attributes: { email: "new-member@example.com", role: "viewer",
// invitedBy: "you@example.com", invitedAt: "2026-07-05T12:02:45.279Z", status: "pending" } }
}
main();

Every non-2xx response throws ResponseError (exported from @sencai/sdk, re-exported from its runtime module) carrying the raw Response object — the typed method never returns a partially-valid result on failure. Error bodies follow Strapi’s standard envelope: { error: { status, name, message, details } }.

import { ResponseError } from "@sencai/sdk";
try {
await organisations.organisationInviteMember({
id: targetOrg.documentId!,
organisationInviteMemberRequest: { email: "new-member@example.com", role: "viewer" },
});
} catch (err) {
if (err instanceof ResponseError) {
const body = await err.response.json();
console.error(err.response.status, body.error.name, body.error.message);
// A real, commonly hit example — the organisation's plan seat limit:
// 402 PaymentRequired "Member limit reached (3/3). Upgrade your plan."
// body.error.details -> { limit: 3, current: 3, limit_source: "organisation_plan", ... }
} else {
throw err;
}
}

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 (package.jsonversion) 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.