Docs menu
Automatic REST API + OpenAPI + SSE
Turns a createGnl configuration into a durable HTTP API in one line: /agents/:name/run|resume|stream (SSE), /workflows, /runs, /usage, and a generated /openapi.json.
What it's for / when to use it#
If you already have a createGnl configuration (agents + workflows + storage), you don't need to hand-write endpoints to expose it as an HTTP API. createRestApi reads the config and automatically generates run, resume, SSE streaming, run history, and usage endpoints — since it all flows through runDurable, exactly-once and durability guarantees come for free.
Typical scenario: wire it into a Hono app with app.route('/api', createRestApi(config, opts)) and point a client (web, CLI, another service) directly at these endpoints. Auth, multi-org isolation, and budget/quota are optional — if you don't pass them, the endpoints are open and single-org (as shown below).
Setup / import#
The package is @gnldev/server; for journal/storage it uses @gnldev/durable and its sub-export @gnldev/durable/sqlite.
npm install @gnldev/server @gnldev/durable honoimport { Hono } from 'hono';
import { createRestApi } from '@gnldev/server';
import { createGnl } from '@gnldev/durable';
import { SqliteStorage } from '@gnldev/durable/sqlite';
const storage = new SqliteStorage('runs.db');
const config = {
storage,
agents: {
starwars: { model: buildModel(), system: SYSTEM, tools: makeSwapiTools(), maxSteps: 4 },
},
};Step-by-step usage#
Once the config is ready, createRestApi(config, opts) returns a Hono router; mount it at any path you want (e.g. /api).
const app = new Hono();
app.route('/api', createRestApi(config, { title: 'SWAPI Free', auth }));
// POST /api/agents/starwars/run {"runId":"demo-1","prompt":"..."}
// POST /api/agents/starwars/stream -> SSE (text-delta/tool-call/tool-result/interrupt/done)
// GET /api/usage -> token/maliyet raporuA run request requires runId — it's the idempotency key for the exactly-once guarantee; POSTing again with the same runId won't re-run tool calls that already completed.
curl -s -X POST http://localhost:3001/api/agents/starwars/run \
-H 'content-type: application/json' \
-d '{"runId":"demo-1","prompt":"Who is Luke Skywalker?"}'For streaming, POST the same body to /agents/:name/stream — the response is an SSE stream (text-delta, tool-call, tool-result, interrupt, done events). If a tool is suspended pending human approval (interrupt), continue it by POSTing { runId, approvals } to /agents/:name/resume with the same runId — the input (prompt/messages) is read from the journal, so you don't need to pass it again.
For a usage/cost report, use GET /usage:
curl -s http://localhost:3001/api/usage
# {"org":null,"usage":{...},"limit":null,"exceeded":false}The generated OpenAPI 3.1 schema is served at GET /openapi.json — run/resume/stream paths are automatically added for every registered agent and workflow; you can feed this directly into tools like Swagger/Redoc.
API reference#
createRestApiGenerates a Hono router from a createGnl config: /agents/:name/run|resume|stream, /agents, /workflows/:name/run, /workflows, /runs, /runs/:id, /usage, /openapi.json.
RestApiOptionsSecond argument to createRestApi: title (OpenAPI title), auth (optional AuthProvider/ReadWriteAuth), org (opt-in multi-org isolation), budgets (fallback budget/quota limits).
buildOpenApiGenerates an OpenAPI 3.1 schema from the list of agent and workflow names; createRestApi calls this automatically at the /openapi.json endpoint, and it can also be imported directly.
pipeAgentStreamPumps a streamDurable result (AI SDK StreamTextResult) into SSE: text-delta/tool-call/tool-result/error/interrupt/done events.
interruptsFromStepsExtracts suspended tool calls (Interrupt[]) from a list of completed steps — the same logic as the suspend handling in runDurable; pipeAgentStream uses this for the interrupt event at the end of the stream.
streamDurableComes from @gnldev/durable (used indirectly inside createRestApi.gnl.stream()): a durable + streaming run — the same exactly-once/suspend guarantees, with the result streamed as fullStream.
auth via roleAuth (@gnldev/auth) — GET endpoints require read, POST endpoints require write permission.runId — this is the key to the exactly-once guarantee. Retrying with the same runId is NOT a new job — it counts as a continuation/retry of the existing one (completed tools won't re-run; if a job is suspended, the budget gate is skipped too).