Skip to main content

Overview

The /startup API lets AI agents (Claude, GPT, Cursor, etc.) drive Ekso’s first-run setup wizard — the same outcome as a human filling out the wizard form, but via two anonymous JSON endpoints. After the wizard succeeds the install has its first tenant, an admin user, the auth config, and (optionally) an AI provider — ready to log into. Self-host means the agent is operating against the customer’s own install, not a hosted ekso.app endpoint. The customer has already:
  1. Acquired a license — ekso.app/get-started emails a Free-tier JWT (3 users, no credit card); paid tiers with unlimited users come from ekso.app/pricing via Stripe checkout. This is its own one-shot flow today; future agent automation for this step is on the roadmap.
  2. Stood up the install — Docker, behind whatever DNS the customer chose (e.g. https://ekso.acme.com). See Install.
What the agent then automates is Step 3: drive the install’s /startup wizard with the operator’s details (name, email, password, organization, public URL) plus the license JWT. The install bootstraps the tenant and the wizard URL closes (410 Gone) on success.

Endpoints

Both endpoints are anonymous (no auth header), live on the customer’s install URL, and are gated by StartupGuard middleware — they return 410 Gone once the install has at least one tenant. There is no email-verification step: the operator running the agent has direct host access to the freshly-bootstrapped install already, so abuse-prevention is solved by network reachability rather than email round-trips.

Step 1 (optional): Validate the public URL

Before posting the wizard, the agent can sanity-check the URL the customer plans to use. Permissive — format check only:
POST https://ekso.acme.com/api/startup/url
Content-Type: application/json

{
  "publicUrl": "https://ekso.acme.com"
}
Success (200):
{
  "message": "OK",
  "url": "https://ekso.acme.com"
}
Failure (400): { "error": "Invalid URL format" } / "URL must be http:// or https://" / "URL must include a host".

Step 2: Run the wizard

POST https://ekso.acme.com/api/startup/go
Content-Type: application/json

{
  "name": "Jane Smith",
  "email": "[email protected]",
  "password": "<at least 8 characters>",
  "organization": "Acme Corp",
  "publicUrl": "https://ekso.acme.com",
  "activation": "<the license JWT from ekso.app/pricing>",
  "aiProvider": "openai",
  "aiModel": "gpt-5.5",
  "aiKey": "sk-proj-..."
}
Success (200):
{
  "tenantId": "a1b2c3d4e5f6",
  "message": "Install configured"
}
After this returns, /api/startup/* flips to 410 Gone — the install is configured. The agent should hand the operator the install URL and the email it was bootstrapped with so they can log in.

Request fields

FieldRequiredNotes
nameYesAdmin user’s full name.
emailYesAdmin user’s email. Receives any password-reset / future-notification mail.
passwordYes8–64 characters. Stored hashed; never recoverable.
organizationYesTenant display name (e.g. “Acme Corp”).
publicUrlYesThe PublicUrl the install is reachable at. Must match the host the agent is POSTing to. Used in outbound emails and OAuth redirect URIs.
activationOptional but recommendedLicense JWT — Free tier from ekso.app/get-started, paid tiers from ekso.app/pricing. Empty = install runs in Free-tier defaults until pasted later via Settings → Account.
aiProviderOptional"" (disabled), "openai", or "anthropic". Sets up ConfigAI so vector embeddings during sample-data onboarding work without a separate Settings step.
aiModelConditionalRequired when aiProvider is set. Suggested defaults: gpt-5.5 (OpenAI), claude-sonnet-4-6 (Anthropic).
aiKeyConditionalRequired when aiProvider is set. Empty key with non-empty provider is rejected (would silently no-op every AI call).

Error responses

All failures return a structured error body:
{ "error": "Missing or invalid fields" }
HTTP StatusCause
400Invalid input — empty name/organization/publicUrl, malformed email, password too short/long, malformed PublicUrl, AI provider/key inconsistency. The error body’s error string identifies the specific failure.
410 GoneInstall already configured. The wizard is closed and /api/startup/* is no longer reachable. The agent should hand the operator the existing login URL instead of trying again.
429Rate limited (the Startup rate-limit policy gates the endpoint). Back off and retry.

Example agent conversation

Agent: I'll bootstrap your Ekso install. I'll need your name, email, a
       password (8–64 chars), your organization name, and your install's
       public URL. Do you have a license JWT from ekso.app/get-started
       (Free) or ekso.app/pricing (paid)?

User:  Jane Smith, [email protected], password "S3cure!Pass", Acme Corp,
       https://ekso.acme.com. Yes — here's the JWT: eyJhbGciOi...

Agent: [POST https://ekso.acme.com/api/startup/url with the publicUrl]
       URL looks good. Want me to enable AI features now (OpenAI or
       Anthropic)? You can also configure this later in Settings.

User:  Yes — OpenAI, here's my key: sk-proj-...

Agent: [POST https://ekso.acme.com/api/startup/go with everything]
       ✓ Install configured. Log in at https://ekso.acme.com with
       [email protected]. The setup wizard is now closed.

What the wizard actually does

POST /api/startup/go delegates to the IInstallSetup workhorse, which atomically:
  1. Creates the install-default DataTenant with the supplied organization name + public URL.
  2. Creates the admin DataUser (super admin, hashed password).
  3. Seeds default groups, permissions, and the forms-auth config.
  4. Stores the license JWT in DataTenant.Activation (verified later by LicenseProvider).
  5. If aiProvider is set, writes ConfigAI so the next request sees AI enabled.
  6. Enqueues a background sample-data onboarding job to populate the tenant with example items, fields, and processes.
On success, IInstallConfigured.MarkConfigured() flips an in-process flag that StartupGuard reads to refuse subsequent wizard calls — making the bootstrap genuinely one-shot.

Discovery

For self-host, agents can discover the install’s onboarding API through the install itself once it’s reachable:
  • /api/startup/url and /api/startup/go — the canonical paths, documented here. Both return 410 once configured, so an agent that pings these can also detect “this install is already set up” before prompting the operator for credentials.
  • OpenAPIhttps://ekso.acme.com/api/openapi/v1.json (the Backend serves the same spec the public docs render).
  • This pagehttps://ekso.dev/guide/agent-onboarding is the public-facing reference.
llms.txt and agent-card.json discovery for the SaaS-era hosted onboarding endpoints (/api/bo/on/agent/*) are gone — those endpoints were removed when the BackOfficeService deleted in Lane G of the self-host pivot. A self-host install can publish its own /.well-known/agent-card.json advertising the /api/startup/* endpoints; this is on the roadmap.