pixie-agent
v0.1.0
Published
Embed an ixi pixie process run in your own app — ticket-authed WebSocket client + React hook
Maintainers
Readme
pixie-agent
Embed an ixi pixie process run in your own product. Your server starts a run of a process you built in ixi; your frontend hosts the conversation — the end user watches the pixie work, answers its questions, and receives the results.
your server ──(org API key)──▶ POST https://api.ixi.so/graph/api/runs
◀── { runId, agent, ticket, expiresAt }
your frontend ──(ticket)──▶ WebSocket to the run's pixie (this SDK)
your server ──(org API key)──▶ GET /graph/api/runs/:runId (status + results)New here? Start with the step-by-step Integration Guide — this README is the compact API reference.
Security model: the org API key (ixi_sk_…) lives only on your server — it can
spawn runs org-wide. The browser credential is the ticket: instance-bound,
~15-minute TTL, useless for anything but that one run's conversation.
1. Create an API key
ixi → org settings → API keys → Create. The full key is shown once; store it in your server's secret manager.
2. Server: start a run
POST https://api.ixi.so/graph/api/runs
Authorization: Bearer ixi_sk_…
Content-Type: application/json
{
"process": "ent_…", // the process template canvas id (from ixi)
"inputs": { "topic": "spring collection" }, // the process's exposed inputs
"request": "optional free-form ask",
"name": "optional run name"
}Response:
{
"runId": "ent_…",
"name": "spring collection",
"pixie": { "name": "…", "variant": "…" },
"agent": {
"host": "api.ixi.so",
"agent": "pixie-agent",
"name": "ent_…:pixie-…",
"wsUrl": "wss://api.ixi.so/agents/pixie-agent/ent_…:pixie-…"
},
"ticket": "v1.…",
"expiresAt": 1789000000000
}Hand agent.host, agent.name, and ticket to your frontend.
Other endpoints (same bearer auth):
POST /graph/api/runs/:runId/ticket→{ ticket, expiresAt, agent }— re-mint for reconnects. Expose this to your frontend through your own backend (the SDK'sgetTicketcallback); never ship the API key to a browser.GET /graph/api/runs/:runId→{ runId, name, status, inputs, request, results }.statusisworking(turn in flight) oridle; treatidle+ non-emptyresultsas complete.resultsare what the pixie submitted:[{ id, url, thumb, text, group, createdAt }].
Errors: 401 bad/revoked key · 404 process/run not in your org · 422 spawn
refused (see error) · 429 daily run cap · 503 API not configured.
3. Frontend: the conversation
React
import { usePixieRun } from 'pixie-agent/react';
function RunView({ host, agentName, ticket }: { host: string; agentName: string; ticket: string }) {
const run = usePixieRun({
host, agentName, ticket,
getTicket: () => fetch('/api/pixie-ticket').then((r) => r.json()).then((r) => r.ticket),
autoKickoff: true, // fires the run's opening turn once connected
});
return (
<div>
{run.messages.map((m) => <Message key={m.id} message={m} />)}
{run.pendingInteractions.map((p) => (
<InteractionCard key={p.toolCallId} interaction={p}
onAnswer={(output) => run.submitToolResult(p.toolCallId, output)} />
))}
<Composer onSend={(text) => run.sendMessage(text)} disabled={run.isStreaming} />
</div>
);
}Peer deps for the React entry: react and agents@^0.17.
Vanilla (zero dependencies)
import { PixieSession } from 'pixie-agent';
const session = new PixieSession({ host, agentName, ticket, getTicket });
session.on('messages-changed', render);
session.on('interaction', (p) => showQuestionCard(p));
await session.connect();
await session.kickoff(); // opening turn streams in
await session.send('make the second one warmer');
session.submitToolResult(toolCallId, output);Needs a runtime with native WebSocket and fetch (browsers; Node ≥ 22 — pass
webSocket: to polyfill older Node).
4. Answering the pixie's questions
The pixie pauses the turn with three interaction tools. pendingInteractions
surfaces them; render your own UI and answer with submitToolResult(toolCallId, output).
The turn stays paused until every pending interaction is answered.
clarify_from_user — question cards (single/multi-select + free text)
Input: { message?, questions: [{ question, multiSelect?, options: [{ label, description?, recommended?, nodeId?, ref?, url?, mediaType? }] }] }
(options with nodeId/ref/url are media the user should preview).
Output you submit:
{ "answers": [{ "question": "…", "selected": ["label"], "other": "free text (optional)",
"nodeIds": ["…"], "refs": ["…"], "urls": ["…"] }] }Echo nodeIds/refs/urls for chosen media options; selected may be empty when
the user only typed an "Other" answer.
request_approval — one-click go/no-go
Input: { title, details?, approveLabel?, declineLabel? }
Output: { "approved": true } or
{ "approved": false, "requestedChanges": [{ "step": "…", "comment": "…" }] }.
give_user_options — legacy single select
Input { message, options: [string] } → output { "selectedOption": "…" }.
Notes
- Tickets expire (~15 min). Provide
getTicket— the SDK re-mints on reconnects. Revoking the API key invalidates its outstanding tickets immediately. - Runs bill the org that owns the process (model runs + pixie turns), same as runs started inside ixi.
- The agent instance name contains a raw
:— never URL-encode it; the SDK handles this. - Results also appear on the process node inside ixi, so your team can watch runs from the canvas while end users drive them from your app.
