@poesius/editor
v0.1.2
Published
Embed the Poesius presentation editor in your product — full chat+canvas or canvas-only for partners with their own AI chat. Session-token iframe SDK for https://poe.poesius.com.
Readme
@poesius/editor
Embed the Poesius presentation editor in your product. Your backend mints a short-lived session token; this package mounts a hosted iframe and bridges events to your UI. You keep your users, auth, and product chrome — Poesius runs the slide editor and AI.
Production API base: https://poe.poesius.com/api/v1
Hosted editor: https://editor.poesius.com (default; override only for local/dev)
Which component should you use?
| Component | Mode | When to use |
|-----------|------|-------------|
| PoesiusEditor | full (default) | You want the Poesius chat + canvas experience in one embed. Best when Poesius is the AI surface for the deck. |
| PoesiusCanvas | canvas | You already have your own chat / agent UI. Embed only the slide canvas; drive AI from your backend with the same session token. |
| createPoesiusEditor | either | Vanilla JS / non-React hosts. Pass mode: 'full' or mode: 'canvas'. |
Partner with Poesius chat (PoesiusEditor)
Use when end users should talk to Poesius inside the iframe (ask, enhance, generate) while editing slides.
import { PoesiusEditor } from '@poesius/editor';
<PoesiusEditor
sessionToken={token}
apiBase="https://poe.poesius.com/api/v1"
theme="light"
onExport={({ blobUrl, format }) => { /* download PPTX/PDF */ }}
onAuthRequired={() => { /* prompt login / upgrade path */ }}
onCreditExhausted={() => { /* show plan / credits UI */ }}
/>Partner with your own chat (PoesiusCanvas)
Use when your product owns the conversation UI. The iframe shows the deck canvas only. Your server (or your chat) calls Poesius session APIs — enhance, generate, ingest — with the same session_token. The canvas stays in sync with the bound presentation.
import { PoesiusCanvas } from '@poesius/editor';
<PoesiusCanvas
sessionToken={token}
apiBase="https://poe.poesius.com/api/v1"
onExport={({ blobUrl }) => { /* download */ }}
onInitialized={({ presentationId, capabilities }) => {
// Wire your chat to this presentation / session
}}
/>Equivalent vanilla:
createPoesiusEditor({
el: document.getElementById('editor'),
sessionToken,
apiBase: 'https://poe.poesius.com/api/v1',
mode: 'canvas', // or 'full'
});Install
npm install @poesius/editorPeer dependencies (optional — only needed for the React components): react and react-dom ≥ 18.
End-to-end integration
1. Prerequisites
- A Poesius organization API key (
poe_org_…). Keep it on your server only — never ship it to the browser. - A
presentation_idthat belongs to your org and the end user you identify asexternal_user_id. - Your frontend can reach
https://editor.poesius.comin an iframe (allow framing / CSP as needed).
2. Mint a session (server-side)
POST https://poe.poesius.com/api/v1/sessions
// NEVER put the org key in the browser
const API = 'https://poe.poesius.com/api/v1';
const mintRes = await fetch(`${API}/sessions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.POESIUS_ORG_API_KEY, // poe_org_...
},
body: JSON.stringify({
presentation_id,
external_user_id: 'alice', // your stable user id in your system
capabilities: ['read', 'write', 'enhance', 'generate', 'export', 'ingest', 'chat'],
ttl_minutes: 120,
}),
});
const { session_token, expires_at, capabilities } = await mintRes.json();
// Hand ONLY session_token to the browser
res.json({ sessionToken: session_token, expiresAt: expires_at, capabilities });Rules:
- Org key minting requires
external_user_id. - The presentation must already be scoped to that org +
external_user_id. - Effective capabilities = what you request ∩ what your org allowlist permits.
readis always required to open the editor.
3. Mount the embed (client-side)
Pass sessionToken and apiBase. The SDK loads https://editor.poesius.com/embed, waits for the iframe ready signal, then sends the session over postMessage (the token is not put in the iframe URL).
Give the container a real height (default React wrapper uses minHeight: 480 and height: 100%).
4. (Optional) Attach documents
If the session has ingest:
POST /api/v1/sessions/{session_token}/artifacts
Content-Type: multipart/form-data
file=<pdf>
artifact_type=documentArtifacts bind to the session’s conversation so generate/enhance can use them.
5. (Optional) Headless generate from your backend
If you use PoesiusCanvas (own chat) or want server-driven deck builds:
POST /api/v1/sessions/{session_token}/generate
Content-Type: multipart/form-data
instruction=Build an exec summary deck from the attached brief
artifact_ids=<optional comma-separated ids>Requires the generate capability. You can also inline a doc with document_base64 + filename when ingest is granted.
Session capabilities
Grant only what the embed (and your server calls) need:
| Capability | What it unlocks |
|------------|-----------------|
| read | Open and render the bound deck (required) |
| write | Manual canvas edits (move, reorder, structure) |
| chat | Poesius agent chat on this deck (mainly for full mode) |
| enhance | AI redesign / elevate / edit-slide style ops |
| refine | Lighter AI polish |
| generate | Build / expand slides from content or instructions |
| export | PPTX / PDF download |
| ingest | Upload documents/images into this session |
| templates.read | List / use templates |
| templates.create | Create custom templates (also plan-gated) |
Typical partner defaults: read, enhance, export, ingest (add chat for PoesiusEditor; add generate / write as needed).
Sessions do not grant org admin, billing, listing other users’ decks, or cross-account template admin. Those stay on your Poesius org credentials / first-party auth.
API reference (SDK)
PoesiusEditor / PoesiusCanvas props
| Prop | Type | Description |
|------|------|-------------|
| sessionToken | string | Short-lived token from POST /sessions |
| apiBase | string | e.g. https://poe.poesius.com/api/v1 |
| editorOrigin | string? | Default https://editor.poesius.com. Local: http://localhost:5174 |
| mode | 'full' \| 'canvas'? | Only on PoesiusEditor / createPoesiusEditor. Canvas wrapper forces canvas. |
| theme | 'light' \| 'dark'? | Initial theme; update later via handle setTheme |
| capabilities | string[]? | Optional hint to the embed; server session is authoritative |
| className / style | React | Container styling |
| onReady | () => void | Iframe loaded and ready for init |
| onInitialized | (info) => void | Session accepted; includes presentationId, conversationId, capabilities |
| onExport | (info) => void | User/export finished; blobUrl / url, format: 'pptx' \| 'pdf' |
| onCreditExhausted | () => void | Credits / quota exhausted — show your upgrade UI |
| onAuthRequired | (info?) => void | Action needs auth or a missing capability |
| onError | (message) => void | Embed error string |
| onNavigate | (path) => void | Host should navigate (e.g. back to your dashboard). Embed cannot own your router. |
createPoesiusEditor(options) → handle
const handle = createPoesiusEditor({ el, sessionToken, apiBase, mode: 'full' });
handle.setTheme('dark');
handle.setSessionToken(newToken); // refresh before expiry
handle.requestExport('pptx');
handle.destroy(); // unmount iframe + listenersSecurity checklist
- Org API key (
poe_org_…) stays on the server. Onlysession_tokengoes to the browser. - Scope every deck with
external_user_idso one end user cannot open another’s presentation under your org. - Mint with the minimum capabilities you need; rotate sessions with TTL (
ttl_minutes, 5–1440). - Refresh the session before
expires_atand callsetSessionToken(or remount with a new token). - Do not put long-lived secrets in the iframe URL or frontend env.
Full React example (own chat + canvas)
import { useEffect, useState } from 'react';
import { PoesiusCanvas } from '@poesius/editor';
export function DeckWorkspace({ presentationId }: { presentationId: string }) {
const [sessionToken, setSessionToken] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
(async () => {
// Your backend mints with poe_org_* and returns only the session token
const res = await fetch('/api/poesius/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ presentationId }),
});
const data = await res.json();
if (!cancelled) setSessionToken(data.sessionToken);
})();
return () => {
cancelled = true;
};
}, [presentationId]);
if (!sessionToken) return <div>Loading editor…</div>;
return (
<div style={{ display: 'grid', gridTemplateColumns: '360px 1fr', height: '100vh' }}>
<YourChat
// Your chat calls your backend → Poesius session APIs with the same token
sessionToken={sessionToken}
/>
<PoesiusCanvas
sessionToken={sessionToken}
apiBase="https://poe.poesius.com/api/v1"
theme="light"
onExport={({ blobUrl, format }) => {
const a = document.createElement('a');
a.href = blobUrl!;
a.download = `deck.${format}`;
a.click();
}}
onCreditExhausted={() => alert('Credits exhausted')}
onError={(message) => console.error(message)}
/>
</div>
);
}Vanilla example (full editor)
import { createPoesiusEditor } from '@poesius/editor';
const handle = createPoesiusEditor({
el: document.getElementById('editor'),
sessionToken,
apiBase: 'https://poe.poesius.com/api/v1',
mode: 'full',
theme: 'light',
onInitialized: ({ presentationId, capabilities }) => {
console.log('ready', presentationId, capabilities);
},
onExport: ({ blobUrl, format }) => {
// trigger download
},
});Local development
Point the iframe and API at your local stacks:
createPoesiusEditor({
el,
sessionToken,
apiBase: 'http://localhost:8000/api/v1',
editorOrigin: 'http://localhost:5174',
});CDN
The same loader can be published as https://js.poesius.com/editor/v1/editor.js (Stripe.js-style). The npm package exposes the same API for bundlers.
Support
- API host:
https://poe.poesius.com - Editor host:
https://editor.poesius.com - Package:
@poesius/editor
If minting fails with 403, verify the presentation is tied to your org and the same external_user_id you send at mint time. If the iframe stays blank, check that apiBase is https://poe.poesius.com/api/v1 and that the session has not expired.
