This tutorial walks you through your first end-to-end integration with the Ligantic SDK: install the package, authenticate with an API key, list a Space, read a Record, and run a Flow.
By the end you will have a small script that talks to a real Ligantic Space and you will have seen the result-based error model in action.
npm install @ligantic/sdkThe Ligantic client is the only thing you need to construct. It takes your API key and (optionally) a base URL.
import { Ligantic } from "@ligantic/sdk";
const client = new Ligantic({
apiKey: process.env.LIGANTIC_API_KEY!,
});The default base URL is https://ligantic.cloud/api/v1. Override baseUrl only for staging or self-hosted deployments. All constructor options are listed in SDK API Reference — LiganticOptions.
Call identity.me() to confirm the key is valid. The SDK never throws — it returns a Result, so you check the ok flag. For why, see The Result-Based Error Model.
const me = await client.identity.me();
if (me.ok) {
console.log("Authenticated as", me.value.id);
} else {
console.error("Auth failed:", me.error.code, me.error.message);
}If you see UNAUTHORIZED, the key is invalid or revoked.
const spaces = await client.spaces.list();
if (spaces.ok) {
for (const space of spaces.value) {
console.log(space.id, space.title);
}
} else {
console.error(spaces.error.message);
}Pick one Space and hold onto its id — every Space-scoped call below needs it, passed as spaceId.
First list the Schemas in the Space, then list the Records in one of them.
if (!spaces.ok) {
console.error(spaces.error.message);
process.exit(1);
}
const spaceId = spaces.value[0]?.id;
if (!spaceId) {
console.error("No spaces found");
process.exit(1);
}
const schemas = await client.schemas.list(spaceId);
if (!schemas.ok) {
console.error(schemas.error.message);
process.exit(1);
}
const schemaId = schemas.value[0].id;
const records = await client.records.list(spaceId, schemaId, { limit: 5 });
if (!records.ok) {
console.error(records.error.message);
process.exit(1);
}
for (const record of records.value.entities) {
console.log(record.id, record.data);
}A Flow run is asynchronous. flows.run() returns the run's ID (flowExecutionId) immediately; you then poll the run until it reaches a terminal status.
const flows = await client.flows.list(spaceId);
if (!flows.ok) {
console.error(flows.error.message);
process.exit(1);
}
const flowId = flows.value[0].id;
const executed = await client.flows.run(spaceId, flowId, { inputs: {} });
if (!executed.ok) {
console.error(executed.error.message);
process.exit(1);
}
const runId = executed.value.flowExecutionId;
// Poll the run once per second, for up to 30 checks.
const terminalStatuses = new Set(["succeeded", "failed", "cancelled"]);
let status: string | undefined;
for (let attempt = 0; attempt < 30; attempt++) {
const run = await client.flows.getRun(spaceId, flowId, runId);
if (!run.ok) {
console.error(run.error.message);
process.exit(1);
}
status = run.value.status;
if (terminalStatuses.has(status)) break;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
console.log("status:", status);The loop stops at a terminal status (succeeded, failed, or cancelled), or after 30 checks, whichever comes first.
For the full polling pattern, see How to Run a Flow from CI.
You have:
@ligantic/sdk.Every call followed the same shape — a Result you check with .ok — and the SDK never threw. That is the whole model.