HTTP API
The programmatic surface behind Jetsyn: the session-authed control-plane REST API a frontend or integration calls, how the app relays your session to the backend, and how a run streams to a shareable dashboard.
The programmatic surface behind Jetsyn is one host-dispatched control plane. Every route under /api self-authenticates on your session token, resolves your cell from that session, and role-scopes what it returns. A frontend never talks to a cell directly: it calls the backend, and the app tier relays your session for you.
Authentication#
Every /api route is reachable only with a verified session. Present it either as an Authorization: Bearer <token> header or as the jetsyn_session cookie; the backend accepts both and derives the same principal. The control plane answers on https://admin.jetsyn.app (also reachable as api.jetsyn.ai).
Authorization is role-scoped into three levels: user (a workspace member), workspace-admin, and super-admin. A member can only ever reach a workspace it belongs to. Asking for a workspace you are not a member of returns a hard 403, never a partial read.
Note
For your own cell, the customer routes (/api/agent/cell/*) never accept a slug from the client at all. The backend resolves your one cell from the session, so there is no slug to tamper with and cross-tenant reads are structurally impossible.
curl https://api.jetsyn.ai/api/me \
-H "Authorization: Bearer $JETSYN_SESSION"{
"email": "you@example.com",
"role": "user",
"super_admin": false,
"user_id": 42,
"workspaces": ["acme"],
"plan": "starter",
"status": "active",
"entitled": true,
"onboarding_completed": true
}Control-plane routes#
These are the routes a builder actually calls. Each request carries your session; where a {slug} appears it is checked against your membership before the route runs.
| Method | Path | Purpose | Role |
|---|---|---|---|
GET | /api/me | Identity, role, plan, entitlement, and your workspace slugs | Member |
GET | /api/agent/company | Read your cell's saved company profile | Member |
POST | /api/agent/company | Persist the company to your cell (whitelisted fields) | Member |
POST | /api/agent/cell/tree | Read your own cell's file tree | Member |
POST | /api/agent/cell/read | Read one file from your own cell | Member |
POST | /api/agent/onboarding/start | Seed a strategist run from a typed idea | Member |
POST | /api/agent/action/run | Run a queued action (402 if unpaid) | Member |
POST | /api/agent/chat/action | Deliver a chat message to your cell's orchestrator | Member |
GET | /api/workspaces | The workspaces you can see | Member |
GET | /api/workspace/{slug}/gateways | The workspace's connected gateways | Member (own) |
GET | /api/workspace/{slug}/seats | Seats in the company brain | Member (own) |
GET | /api/workspace/{slug}/memory | The workspace's memory folder tree | Member (own) |
GET | /api/workspace/{slug}/billing | Plan and usage for the workspace | Member (own) |
GET | /api/workspace/{slug}/members | List the workspace's members | Workspace admin |
POST | /api/workspace/{slug}/members/invite | Invite a member | Workspace admin |
DELETE | /api/workspace/{slug}/members/{user_id} | Remove a member | Workspace admin |
POST | /api/billing/change-plan | Move your subscription to another tier | Member |
POST | /api/billing/cancel | Cancel (at period end) or resume your subscription | Member |
GET | /api/invites/{token} | Look up a pending invite | Member |
POST | /api/invites/{token}/accept | Accept an invite and join a workspace | Member |
Availability
Available now: session-authed identity, cell reads, queued-action runs, chat, workspace reads, member management, and live plan changes. The customer cell surface is read plus queued-action only; direct file writes and shell commands into a cell go through the Pro direct gateway, not this API. Coming soon: a published machine-readable OpenAPI spec. Until then, this table is the reference.
Onboarding, actions, and chat#
Three routes drive the founder loop. POST /api/agent/onboarding/start turns a typed idea into a real seeded strategist run in your cell. It carries only the idea; the run itself writes every artifact.
curl -X POST https://api.jetsyn.ai/api/agent/onboarding/start \
-H "Authorization: Bearer $JETSYN_SESSION" \
-H "Content-Type: application/json" \
-d '{"idea": "A cold-email service for dental clinics"}'
# -> {"slug": "acme", "task_id": "...", "status": "running"}Heads up
POST /api/agent/action/run is entitlement-gated. If the account has no active plan or trial, it returns 402 with {"error": "billing_required"}. Treat that 402 as your signal to open checkout, not as an error to retry.
POST /api/agent/chat/action is pure transport, not a model call: it delivers a founder's message to the cell's durable orchestrator and hands back a correlation id. Poll for the reply by reading the returned outbox_path over the POST /api/agent/cell/read relay.
{
"correlation_id": "c_8f3a2b",
"outbox_path": "runs/chat/outbox/c_8f3a2b.json",
"status": "working"
}The relay pattern#
Marketing and the app run on jetsyn.ai and app.jetsyn.ai; the control plane runs on admin.jetsyn.app. Because those are different origins, the browser's jetsyn_session cookie cannot cross to the backend on its own. So nearly every meaningful app route is a thin relay: it reads your jetsyn_session, forwards it to the backend, and returns the backend's response verbatim. The app holds almost no product logic; the backend is the single authority on identity and entitlement.
Two rules make a relay correct. Forward the session, and never forward the slug: the backend resolves your cell from the session, so a client-supplied slug is both unnecessary and a tenant-isolation risk. Treat a 3xx from the backend as a failed authentication and return 401.
// A backend-proxied route. Forward the session, never the slug.
import { cookies } from "next/headers";
const API_BASE = process.env.JETSYN_API_BASE ?? "https://admin.jetsyn.app";
export async function POST(req: Request) {
const session = cookies().get("jetsyn_session")?.value;
if (!session) return new Response("unauthorized", { status: 401 });
const res = await fetch(`${API_BASE}/api/agent/action/run`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: `jetsyn_session=${session}`,
},
body: await req.text(),
redirect: "manual",
});
// A redirect means the cookie did not authenticate: surface it as 401.
if (res.status >= 300 && res.status < 400) {
return new Response("unauthorized", { status: 401 });
}
return new Response(res.body, { status: res.status });
}Tip
The base URL comes from JETSYN_API_BASE (or NEXT_PUBLIC_JETSYN_API_BASE), defaulting to https://admin.jetsyn.app. Point it at a staging control plane without touching a single route.
Watch a run work#
When your employee runs, it streams progress to a per-task dashboard you can hand straight to a customer. Mint a token server-side for the run, and the backend serves a live page at GET /t/{token} on the workspace host (<slug>.jetsyn.app). The token is a 160-bit random value stored hashed, so the link is unguessable and scoped to that one run.
The page renders from the run's own files. Every run writes runs/<task_id>/status.json (plus progress.md and result.md) onto the cell volume, and the dashboard reads them as they change. There is no per-run UI to build: produce the files, hand over the link.
Where to go next#
The agent runtime
What runs behind a queued action: spawn-per-event employees, durable resume, and the run-file contract the dashboard reads.
Read →The CLI
The Pro direct gateway. Where file writes and shell commands into a cell happen, over the same session that authenticates this API.
Read →Surfaces & tiers
How entitlement and plans work, so you know what a 402 means and how change-plan and cancel behave.
Read →Architecture
The three-origin layout: marketing, the relay app, the control plane, and the per-tenant cell the session resolves to.
Read →