@liforma/publisher
v0.9.0
Published
Server SDK for the Liforma Publisher API
Readme
@liforma/publisher
Server SDK for the Liforma Publisher API — create a full experience in one call, or compose uploads, backdrops, sets, costumes/clothes/hair, and characters with the low-level namespaces.
npm install @liforma/publisherOne-shot composition (happy path)
import { readFileSync } from 'node:fs';
import { createPublisher } from '@liforma/publisher';
const publisher = createPublisher(process.env.LIFORMA_PROJECT_ID!, {
apiKey: process.env.LIFORMA_API_KEY!
});
const avatars = await publisher.avatars.list();
const avatar = avatars[0]!;
const result = await publisher.experiences.createFrom({
title: 'Hotel check-in',
backdrop: {
image: { bytes: readFileSync('./lobby.png'), contentType: 'image/png' }
},
character: {
avatarId: avatar.id,
name: 'Front desk',
voice: avatar.defaultVoiceId,
sttLang: avatar.defaultSttLang,
personality: 'Friendly hotel receptionist'
},
startingMessage: 'Good evening. How may I help you?',
systemInstructions: 'The customer is checking in.',
publish: true
});
console.log(result.experience.id, result.created);ImageSource accepts Blob, { bytes, contentType }, { url } (imported into Liforma storage — never a permanent dependency), or { uploadId }. Reuse existing library items with { id } on backdrop/character/plates (id wins if both id and create fields are present). Progress uses { stage, job } because multiple plate jobs may run concurrently.
If externalId matches an existing experience or character, createFrom returns that resource’s live composition (created.* === false for reused members). Character externalId hits probe-create without wardrobe first so retries do not spawn orphan plates.
Low-level namespaces
const background = await publisher.uploadImage(lobbyPng, { contentType: 'image/png' });
const backdrop = await publisher.backdrops.create({ name: 'Hotel lobby', image: background });
const set = await publisher.sets.create({ name: 'Hotel lobby', backdropId: backdrop.id });Resource clients follow one shape:
publisher.characters.create(input)
publisher.characters.get(id)
publisher.characters.update(id, patch)
publisher.characters.archive(id)
publisher.characters.restore(id)
publisher.characters.delete(id)Job-backed resources (backdrops, costumes, clothes, hair) also expose startCreate(), which returns a PublishJob. create() is the convenience path: start → jobs.wait → get(targetId).
const uniform = await publisher.costumes.create({ avatarId, image: wholeLook });
await publisher.characters.create({
name: 'Examiner',
avatarId,
voice,
sttLang: 'en',
costumeId: uniform.id
});const job = await publisher.backdrops.startCreate(input, { signal });
const completed = await publisher.jobs.wait(job.id, { signal });
const backdrop = await publisher.backdrops.get(completed.targetId, { signal });jobs.wait() returns the completed job, not the resource. jobs.watch() is an async iterator and takes { signal, timeoutMs } only. Aborting an SDK request stops local waiting; it does not cancel a durable publishing job.
delete() returns { deleted: true, id } and is allowed only after archive, when no historical parent still references the item. GET still works after archive and includes public status. Pass forceNew: true on plate creates to skip content-addressed reuse.
Experience responses describe the current draft. Use hasPublishedRevision and hasUnpublishedChanges; published is a deprecated 0.x compatibility alias.
Use a live API key on the server only. Never ship this package or the key to a browser.
Move between projects (same org)
Construct the client for the destination project. Every move call requires sourceApiKey (preview and commit):
await dest.library.move(
{ sourceProjectId: 'proj_src', experienceIds: ['exp_…'], dryRun: true },
{ sourceApiKey: process.env.LIFORMA_SOURCE_PROJECT_KEY! }
);Same-org enable/detach/reparent only — not cross-org remix. See alpha docs for class A vs B behaviour.
Docs (alpha): https://docs.liforma.ai/_alpha/publisher-sdk
