JJETSYNGet in touch

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.

Who am I?
curl https://api.jetsyn.ai/api/me \
  -H "Authorization: Bearer $JETSYN_SESSION"
GET /api/me
{
  "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.

MethodPathPurposeRole
GET/api/meIdentity, role, plan, entitlement, and your workspace slugsMember
GET/api/agent/companyRead your cell's saved company profileMember
POST/api/agent/companyPersist the company to your cell (whitelisted fields)Member
POST/api/agent/cell/treeRead your own cell's file treeMember
POST/api/agent/cell/readRead one file from your own cellMember
POST/api/agent/onboarding/startSeed a strategist run from a typed ideaMember
POST/api/agent/action/runRun a queued action (402 if unpaid)Member
POST/api/agent/chat/actionDeliver a chat message to your cell's orchestratorMember
GET/api/workspacesThe workspaces you can seeMember
GET/api/workspace/{slug}/gatewaysThe workspace's connected gatewaysMember (own)
GET/api/workspace/{slug}/seatsSeats in the company brainMember (own)
GET/api/workspace/{slug}/memoryThe workspace's memory folder treeMember (own)
GET/api/workspace/{slug}/billingPlan and usage for the workspaceMember (own)
GET/api/workspace/{slug}/membersList the workspace's membersWorkspace admin
POST/api/workspace/{slug}/members/inviteInvite a memberWorkspace admin
DELETE/api/workspace/{slug}/members/{user_id}Remove a memberWorkspace admin
POST/api/billing/change-planMove your subscription to another tierMember
POST/api/billing/cancelCancel (at period end) or resume your subscriptionMember
GET/api/invites/{token}Look up a pending inviteMember
POST/api/invites/{token}/acceptAccept an invite and join a workspaceMember

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.

Kick off onboarding
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.

POST /api/agent/chat/action
{
  "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.

app.jetsyn.ai/app/api/agent/action/run/route.ts
// 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#