Public reference · REST + MCP

the api, in plain english

Drop new signups into a personalised demo, or let an agent author demos through MCP. Both surfaces share one implementation — everything here applies to the REST API and the hosted MCP server. Building an MCP server for the MyAISE assistant instead? See the MCP integration guide.

RESTmyaise.com/v1 MCPmyaise.com/mcp Streamjoinlivedemo.com
You'll need a Solo or AI SE workspace to make requests.

This page is the read-only mirror. The authenticated version at /dashboard/docs/api cross-references your live keys + webhooks.

See pricing

Drop new signups into a personalised demo, or let an AI agent author demos through MCP. Both surfaces share one implementation — everything here applies to the REST API and the hosted MCP server. Building an MCP server for the MyAISE assistant instead? See the MCP integration guide. Want to configure your whole MyAISE workspace by chatting with Claude instead of reading API docs? See Set up MyAISE with Claude Code.

RESTmyaise.com/v1 MCPmyaise.com/mcp Streamjoinlivedemo.com
Section 01

Quick start #

Three commands and you have a working integration. Get a key, ping, list projects — that's it.

1 · Get an API key #

Visit /dashboard/api-keys → Create key. The secret is shown once — copy it into your secret store immediately.

Show-once secret. MyAISE never stores the plaintext. Lose it and you rotate.

2 · Smoke test #

bash · curl
curl https://myaise.com/v1/ping \\
  -H "Authorization: Bearer myaise_live_…"

3 · List your projects #

bash · curl
curl https://myaise.com/v1/projects \\
  -H "Authorization: Bearer myaise_live_…"
Section 02

Authentication #

Every request carries a workspace-scoped Bearer token. Keys are workspace-scoped — a team key can never see a personal workspace. MCP clients such as Claude can sign in with OAuth instead of using a key; see Connect Claude.

http · header
Authorization: Bearer myaise_live_<prefix>_<random>

Scopes #

ScopeWhat it grants
workspace:readWorkspace metadata, and reading Assist settings and the customer-app connection over MCP.
workspace:manageChange workspace configuration over MCP: Assist settings and the customer-app connection. Implies workspace:read. Never accepts or returns a secret.
content:readRead projects, demos, rules, and assets.
content:writeCreate or change ordinary content.
sessions:readRead viewer sessions.
sessions:writeCreate and manage viewer sessions.
sessions:credentials:submitSubmit only client-encrypted viewer credential payloads.
webhooks:readRead webhook configuration and delivery status.
webhooks:manageOwner-only webhook management and redrive.
whitelabel_onlyIsolated partner scope for theme verification; it cannot be combined with workspace scopes.

Dashboard presets #

The API keys page mints keys from presets. A preset name is never stored; the key carries the scopes it expands to.

PresetScopes
standardworkspace:read content:read content:write sessions:read sessions:write sessions:credentials:submit
fullEverything in standard, plus webhooks:read webhooks:manage workspace:manage
read_onlyworkspace:read content:read sessions:read
sessions_onlyworkspace:read sessions:read sessions:write sessions:credentials:submit
whitelabel_onlywhitelabel_only

Studio (free) #

Free workspaces get 402 Payment Required on every /v1 and /mcp request (error.type: "tier_required"). Upgrade to Solo or AI SE to enable API access. A paid workspace gets the same 402 while billing holds it back, with the reason as error.type: billing_hold (a payment on the account was disputed), plan_paused (its plan payment was refunded or not collected), workspace_paused (the account that pays for the workspace is either) or key_owner_billing (a workspace key whose creator's account must resolve billing first: ask your workspace owner), and error.message says what to do; error.billing_url is the billing page. Upgrading doesn't help: the paying account's owner resolves it on that page.
Section 03

Concepts #

Six resources, in nesting order. Everything else is a child or a side-effect.

ws_ · ws_user_ workspace Personal or team. One key = one workspace.
proj_ project A site + its rewriting rules. Owns demos.
demo_ demo A multi-step product tour. Embeds steps[].
step_ step One page of the tour. Embeds overlays + entry actions.
ov_ overlay A tooltip / modal / card layered on the page.
sess_ session A live, isolated browser running the demo for one prospect.

ID prefixes #

Every ID is prefixed so you can spot the resource by eye — no need to remember which is which.

  • ws_team workspace
  • ws_user_personal workspace
  • proj_project
  • demo_demo
  • step_step
  • ov_overlay
  • act_action
  • sess_viewer session
  • wh_webhook
  • del_delivery
Worked example

Embed MyAISE in a signup flow #

A new prospect signs up on your site. Your backend POSTs to MyAISE to mint a session URL, then redirects the prospect there instead of an empty account.

Use a sessions_only key for this integration.

Flow

Signup → live demo, in one round-trip

The Idempotency-Key header makes retries safe — the cloud replays the original response without spinning a second viewer machine.

  1. Prospect submits your signup form.
  2. Your backend POSTs /sessions with their identity + encrypted cookies.
  3. MyAISE returns a one-shot viewer_url.
  4. Redirect — they land in a fully personalised, live demo.
javascript · node 18+
// mint a session, redirect prospect
const res = await fetch(
  "https://myaise.com/v1/demos/demo_abc.../sessions",
  {
    method:  "POST",
    headers: {
      "Authorization":   `Bearer ${process.env.MYAISE_API_KEY}`,
      "Content-Type":    "application/json",
      "Idempotency-Key": prospect.id   // safe to retry
    },
    body: JSON.stringify({
      prospect: {
        name:    prospect.name,
        email:   prospect.email,
        company: prospect.company
      },
      encrypted_payload:  encryptCookies(cookies, myaisePubkey),
      expires_in_seconds: 3600
    })
  }
);

const { data } = await res.json();
res.redirect(data.viewer_url);
Worked example

A live demo before signup (website integration) #

A prospect on your marketing site fills in name, email and company. Your backend provisions a throwaway demo account inside your own app, encrypts a one-time login URL for it, creates a MyAISE session with a time limit, an AI-credit budget and a host allowlist, binds an MCP grant so the AI SE can act as that demo user, redirects the prospect to the viewer, and cleans up when the session.ended webhook arrives.

Before you start

  • An AI SE workspace with MyAISE (the assistant) enabled.
  • An API key with sessions:write + sessions:credentials:submit — a sessions_only key is exactly that set.
  • A webhook subscribed to session.ended (and optionally session.budget_exhausted).
  • The id of the demo to launch (demo_…).
  • Optionally, the workspace Website integration policy in the dashboard — default / maximum TTL, default / maximum credit budget, allowed-hosts mode. Values you send are clamped to it.
Flow

Marketing form → live demo → cleanup

Eight steps, four of them HTTP calls. Everything the prospect touches is disposable: a demo account you own, a session that expires, and a grant that dies with it.

  1. The prospect submits your form. Protect it — CAPTCHA, a honeypot field, per-IP and per-email rate limits, and a cap on concurrent live demos.
  2. Provision a demo account in your own app and mint a single-use, short-lived login URL for it.
  3. GET /viewer/public_key — the RSA key you wrap this session's AES key with.
  4. Encrypt { base_url, cookies, headers } into the sealed envelope.
  5. POST /demos/:id/sessions with the envelope, the time limit, the credit budget and the host allowlist.
  6. POST /customer_app/mcp_handoffs with viewer_session_id, so the AI SE acts as the demo user.
  7. Redirect the prospect (303) to the returned viewer_url.
  8. On session.ended, revoke the grant and purge the demo account.
http · the calls, in order
# your form handler
POST https://you.example/demo-requests

# provision the demo account, then:
GET  https://myaise.com/v1/viewer/public_key
POST https://myaise.com/v1/demos/demo_b3d2.../sessions
POST https://myaise.com/v1/customer_app/mcp_handoffs

# hand the prospect over
303  Location: <viewer_url>

# later, from MyAISE to you
POST https://you.example/webhooks/myaise   # session.ended

Fetch the public key #

Every workspace has an RSA public key (PEM) held by its viewer key provider. You wrap the per-session AES key with it; the matching private key never leaves the key provider, and the cloud only ever holds the sealed envelope.

bash · curl
curl https://myaise.com/v1/viewer/public_key \
  -H "Authorization: Bearer $CAP_KEY"

Fetch it per session and never persist it — the key is regenerated in development and rotated in production, and a stale key produces a session whose credentials can never be unsealed. The response spells out the whole envelope contract (padding, hashes, IV and tag sizes, the plaintext schema) so you never have to guess; the full shape is in the endpoint reference. If the key provider is unreachable you get 503 key_unavailable — retry. There is no plaintext fallback: plaintext credentials are rejected.

Encrypt the login #

The envelope is fixed:

  • A random 32-byte AES key and a random 12-byte IV, both fresh for every session.
  • AES-256-GCM over the UTF-8 JSON plaintext with empty additional data (AAD = ""), producing a 16-byte tag.
  • The AES key wrapped with RSA-OAEP — SHA-256 hash, MGF1-SHA-256, no label.
  • Every field base64 (standard alphabet, padded), sent as encrypted_key, iv, ciphertext, tag.

The plaintext is one small JSON object:

json · plaintext
{
  "base_url": "https://app.example.com/demo/enter/<one-time-token>",
  "cookies": [],
  "headers": {}
}

Prefer a one-time login URL over real cookies — it is the smallest thing you can hand over and it dies on first use. When you do need cookies, each entry is { name, value, domain, path, secure, httpOnly }; headers are sent on every request to the base_url host. The viewer machine consumes the credentials exactly once, and the cloud stores only the sealed envelope — never the plaintext, never the AES key.

Both examples are stdlib only — Ruby 3.x on OpenSSL 3, and Node 18+ on node:crypto. (On Ruby 3.4+ base64 is a bundled gem: add it to your Gemfile, or swap those four calls for [bytes].pack("m0").)

ruby · seal.rb
require "openssl"
require "json"
require "base64"

def seal_for_myaise(plaintext_hash, public_key_pem)
  cipher = OpenSSL::Cipher.new("aes-256-gcm").encrypt
  key = cipher.random_key          # 32 bytes
  iv  = cipher.random_iv           # 12 bytes
  cipher.auth_data = ""
  ciphertext = cipher.update(JSON.generate(plaintext_hash)) + cipher.final
  tag = cipher.auth_tag            # 16 bytes

  rsa = OpenSSL::PKey.read(public_key_pem)
  wrapped = rsa.encrypt(key,
    rsa_padding_mode: "oaep",
    rsa_oaep_md: "sha256",
    rsa_mgf1_md: "sha256")         # never public_encrypt: that is SHA-1 OAEP

  {
    encrypted_key: Base64.strict_encode64(wrapped),
    iv:            Base64.strict_encode64(iv),
    ciphertext:    Base64.strict_encode64(ciphertext),
    tag:           Base64.strict_encode64(tag)
  }
end
javascript · seal.mjs
import { randomBytes, createCipheriv, publicEncrypt, constants } from "node:crypto";

export function sealForMyaise(plaintext, publicKeyPem) {
  const key = randomBytes(32);
  const iv = randomBytes(12);
  const cipher = createCipheriv("aes-256-gcm", key, iv);
  cipher.setAAD(Buffer.alloc(0));
  const ciphertext = Buffer.concat([cipher.update(JSON.stringify(plaintext), "utf8"), cipher.final()]);
  const tag = cipher.getAuthTag();
  const wrapped = publicEncrypt(
    { key: publicKeyPem, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" },
    key
  );
  return {
    encrypted_key: wrapped.toString("base64"),
    iv: iv.toString("base64"),
    ciphertext: ciphertext.toString("base64"),
    tag: tag.toString("base64"),
  };
}
Ruby's rsa.public_encrypt cannot build this envelope — it is PKCS#1 v1.5 by default, and OAEP-SHA-1 with the OAEP padding constant. The cloud rejects both. Use rsa.encrypt with rsa_padding_mode: "oaep", rsa_oaep_md: "sha256" and rsa_mgf1_md: "sha256", as above.

Create the session #

POST /demos/:demo_id/sessions, with an Idempotency-Key header so a retried POST replays the original response instead of spinning a second viewer machine.

json · request body
{
  "prospect": {
    "name": "Sarah Chen",
    "email": "sarah@acme.com",
    "company": "Acme",
    "metadata": { "lead_id": "ld_91" }
  },
  "encrypted_payload": { "encrypted_key": "…", "iv": "…", "ciphertext": "…", "tag": "…" },
  "expires_in_seconds": 1200,
  "credit_budget": 100,
  "allowed_hosts": ["app.example.com", "*.app.example.com"]
}

A 201 comes back with the one-time viewer URL and the limits as they were actually applied:

json · 201 Created
{
  "data": {
    "id": "sess_7a2e...",
    "token": "v1_AbCd...",
    "status": "pending",
    "viewer_url": "https://joinlivedemo.com/view/v1_AbCd...",
    "expires_at": "2026-09-06T14:30:00Z",
    "expires_in_seconds": 1200,
    "credit_budget": 100,
    "credits_spent": 0,
    "allowed_hosts": ["app.example.com", "*.app.example.com"],
    "demo_id": "demo_b3d2...",
    "project_id": "proj_e72b...",
    "workspace": { "type": "team", "id": "ws_3f1c..." }
  }
}

The three limits:

  • Time limit. expires_in_seconds is clamped to the workspace maximum, so read expires_at and expires_in_seconds back from the response instead of assuming. When the clock runs out the session ends with end_reason: "ttl_expired".
  • Credit budget. The AI SE's answers, walkthrough generation and agent turns each cost credits (see AI credits). Without credit_budget the session takes the workspace default, or 5000 credits when the workspace sets none (lowered to the workspace maximum if that is smaller); read credit_budget back from the response. The turn that reaches the budget is the last one, and the session ends with end_reason: "credit_budget_exhausted". Live-viewer session hours are metered separately and are not part of this budget.
  • Host allowlist. The streamed browser refuses top-level navigations to any other host and bounces back to the demo's base URL — links to your marketing site, your docs or an external site cannot carry the prospect away. Page subresources from other hosts still load.

Errors worth handling: 400 invalid_expires_in and 400 invalid_credit_budget for values that are not positive integers; 422 credit_budget_exceeds_policy when the budget is above the workspace maximum; 422 invalid_allowed_hosts when the list is not an array, holds more than 20 entries, or contains an entry that is not a hostname (the offending entry is named in the error message); 422 navigation_base_required when the workspace limits sessions to the project's hosts but the project has no base URL (or *) and none of its demos names a site (an absolute step URL or an exit URL), so there is no host to allow and nowhere to bounce back to. Set the project's base URL or give a demo step an absolute URL.

viewer_url is a bearer secret. Never log it, never email it, never put it in a query string you keep. Redirect once, straight from the response.

Bind the MCP grant #

If your app exposes an MCP server, the AI SE can answer "how many campaigns do I have?" by calling it as the demo user. Mint a short-lived grant scoped to the demo account you just provisioned — expiring no later than the session, with a tool-call cap — and push it server-side with viewer_session_id from the previous response:

json · POST /customer_app/mcp_handoffs
{
  "endpoint_url": "https://mcp.example.com/rpc",
  "access_token": "demo_user_grant_...",
  "expires_at": "2026-09-06T14:30:00Z",
  "app_ref": "example-app",
  "account_id": "demo_acct_91",
  "scopes": ["read_only"],
  "viewer_session_id": "sess_7a2e...",
  "tool_catalog": { "tools": [ /* your MCP tools/list result for this grant */ ] }
}

tool_catalog is what the AI SE can call: pass the tools/list result your server returns for this grant (MCP shape — name, description, inputSchema, annotations). MyAISE never calls tools/list itself, so a handoff without a catalog gives the AI SE zero tools.

The grant is bound to that prospect's viewer session, so it dies with the session. Re-pushing with the same viewer_session_id rotates the grant. Because the binding is authorised by the same scope that created the session, sessions:write is enough here — a sessions_only key can push a session-bound handoff, while an unbound one still needs content:write. Building the MCP server itself is covered in the MCP integration guide.

Handle session.ended #

Every terminal session fires the specific event plus the meta session.ended:

json · session.ended
{
  "id": "evt_9b1f...",
  "type": "session.ended",
  "created_at": "2026-09-06T14:21:03Z",
  "workspace_id": "ws_3f1c...",
  "api_version": "2026-06-01",
  "data": {
    "session_id": "sess_7a2e...",
    "status": "expired",
    "completion_type": "budget_exhausted",
    "end_reason": "credit_budget_exhausted",
    "ended_at": "2026-09-06T14:21:02Z",
    "seconds": 742,
    "credits_spent": 100,
    "credit_budget": 100,
    "allowed_hosts": ["app.example.com", "*.app.example.com"],
    "expires_at": "2026-09-06T14:30:00Z",
    "prospect": {
      "name": "Sarah Chen",
      "email": "sarah@acme.com",
      "company": "Acme",
      "metadata": { "lead_id": "ld_91" }
    },
    "demo_id": "demo_b3d2...",
    "project_id": "proj_e72b..."
  }
}

end_reason is one of:

ReasonWhat happened
ttl_expiredThe session hit its time limit.
credit_budget_exhaustedThe AI credit budget ran out. A session.budget_exhausted event fires just before session.ended.
runtime_incompatibleThe session never started: the viewer runtime could not apply its settings (such as its navigation allowlist) or stopped before it was ready — rare.
revokedYou deleted the session.
billing_holdThe account paying for the session lost its plan: a payment on it was disputed (a billing hold), or its plan payment was refunded or not collected.
demo_completedThe prospect reached the end of the demo.

On receipt:

  1. Verify the signature (see Signature verification) before trusting anything in the body.
  2. Be idempotent on the event id — retries and replays are expected.
  3. Revoke the MCP grant.
  4. Disable or soft-delete the demo account immediately; hard-purge it after a grace period.

Run a sweeper too, for sessions whose webhook you never received: the session show endpoint reports status, end_reason and ended_at, so a periodic reconcile over your own open demo accounts closes the gap.

What the prospect sees #

A branded viewer page — your logo, colours and splash from the white-label theme — with a countdown pill showing the time left that turns amber under five minutes, and the AI SE chat alongside the live app. When the session ends they get an end screen keyed on end_reason: "Your demo time is up" for the time limit, an "AI budget has been used up" message for the credit budget, "Thanks for watching" when the demo completed, and your demo's exit URL if it defines one. Nothing about credits, hosts or policy is exposed beyond the countdown and that end screen.

Teams with a custom end page in their white-label theme keep it. Add <ca-slot name="end_headline">, <ca-slot name="end_subtitle"> or <ca-slot name="end_reason"> to that page and the viewer fills in why the session ended; slots the page does not use cost nothing.

Security checklist #

  • Login URLs are one-time and short-lived (five minutes or less), stored on your side as digests, never in plaintext.
  • Demo accounts are least-privilege: no billing, no team management, no API keys, no purchasing.
  • Ship only the cookies the demo needs, and only for the demo account.
  • viewer_url is a bearer secret — redirect once, never log or email it.
  • Allow only the hosts the demo needs; every extra host is somewhere a prospect can wander.
  • Rate-limit and CAPTCHA the form, and cap concurrent sessions per IP, per email and per workspace.
  • Keep prospect.metadata to what you would put in a CRM — no PII beyond that.
  • Verify webhook signatures, tolerate replays, and expect the same event id more than once.
  • Purge demo data on a schedule, not only on the webhook.
  • Never log the API key, the encrypted payload, or the login token.
Worked example

AI-design a demo via MCP #

Connect Claude (or any MCP client) to https://myaise.com/mcp. You sign in to MyAISE, pick a workspace and what the client may do, and there is no key to copy. The model then authors demos the same way your backend would.

Setting up a whole integration — environments, your app's MCP connection, embed keys, webhooks — rather than authoring demos? Follow Set up MyAISE with Claude Code instead; it walks the full configuration flow step by step.

1 · Connect the client #

  • Claude.ai and Claude Desktop: open Settings → Connectors, choose Add custom connector and enter https://myaise.com/mcp.
  • Claude Code: add the server, then run /mcp in Claude Code and choose it to sign in.
shell · claude code
claude mcp add --transport http myaise https://myaise.com/mcp

The client opens a MyAISE page in your browser. Sign in with GitHub or Google, pick the workspace (only workspaces on Solo or AI SE can connect), check the scopes it may use, and approve. The client receives a one-hour access token and a refresh token; it renews them itself for up to 90 days. The connection is listed under API keys → Connected apps, where you, or the owner of its team, can revoke it at any time. Leaving the team ends it too.

MyAISE follows the MCP authorization spec: an unauthenticated request to /mcp gets a 401 naming /.well-known/oauth-protected-resource/mcp, which points to the authorization server metadata at /.well-known/oauth-authorization-server. Clients register themselves (dynamic client registration), use PKCE with S256, and send resource=https://myaise.com/mcp. Access tokens work only on /mcp, not on the REST API.

Use an API key instead

Scripts and clients without OAuth support can send an API key from API keys as a Bearer header:

json · claude-desktop.json
{
  "mcpServers": {
    "myaise": {
      "transport": "http",
      "url":       "https://myaise.com/mcp",
      "headers": {
        "Authorization": "Bearer myaise_live_..."
      }
    }
  }
}
shell · claude code
claude mcp add --transport http myaise https://myaise.com/mcp --header "Authorization: Bearer myaise_live_..."

2 · Ask the model

"I'm building a product tour for our pricing page. Use the design_demo prompt with my pricing URL, then create the demo."

3 · What it does, step-by-step

  1. tools/list — sees the full tool surface.
  2. The design_demo prompt — gets a structured prompt template covering URL flow + overlay copy guidelines.
  3. projects.create, then demos.create with nested steps + overlays.
  4. For anchored overlays, attaches target_context (+ an optional baseline screenshot) so the demo can self-heal — see “Drift protection” below.
  5. sessions.create to actually launch the demo.
Every mutating MCP tool routes through the same context function the REST controller would — so the AI can't produce an invalid demo any more easily than your backend can. On initialize, the server returns an instructions block describing this whole flow, so MCP clients prime the model automatically.

4 · Configure the workspace from chat #

With a team key minted from the full preset, the model can also configure the whole workspace — Assist, your app's MCP connection, the visual asset Library, whitelabel branding, billing, and usage — the same way the dashboard does. The same rules as the dashboard apply: the key's creator must still be a member of the team, on the plan the dashboard action itself requires. Every tool below routes through the same context function its dashboard action calls, with the same validation.

Projects

ToolScopeWhat it does
launch_defaults.get workspace:read read-only Read a project's launch defaults: the start URL, optional sign-in URL (set to enable a prep session), allowed-sites list (at most 20 hosts, `*.host` wildcards allowed) and AI-credit budget the web launch form pre-fills. Every field is null when nothing has been saved.
launch_defaults.set workspace:manage Change a project's launch defaults. Partial update: pass only the fields to change; a null clears an optional field. Validated like the dashboard's Advanced launch form (start/sign-in URLs must be public http(s), at most 20 allowed-site entries, credit_budget > 0). Returns the same shape as launch_defaults.get.
projects.create content:write Create a new project (ruleset) in the workspace.
projects.delete content:write destructive confirm Delete a project and all its demos.
projects.get content:read read-only Fetch a project (ruleset) by id, including embedded rules + demos.
projects.list content:read read-only List all projects in the authenticated key's workspace.
projects.update content:write Update a project's name / description / base_url.
projects.versions.list content:read read-only List a project's saved version history (each snapshot from the desktop app, the REST/MCP rules API, or a restore), newest first: version number, description, author, rule count, and when it was saved. Use projects.versions.restore to roll back to one.
projects.versions.restore content:write destructive confirm Restore a project's rules to an earlier saved version. Confirm step: the first call summarizes the version's date and how many rules would change compared to now, and returns a confirm_token; call again with that token to apply it. The version restored FROM is not lost — it stays in history and the project's version number still advances, so this can always be undone by restoring again.
rules.create content:write Add a new URL-rewriting rule to a project. Same fields as POST /v1/projects/:project_id/rules (match, rewrite, type, enabled, description, headers, status_code, body, methods); none are required, matching that endpoint. Returns the new rule and the project's full rule list.
rules.delete content:write destructive Remove a rule from a project. No confirmation step: every push is kept in the project's version history (projects.versions.list / .restore), so a deleted rule is recoverable by restoring the last version that had it.
rules.list content:read read-only List a project's URL-rewriting rules in order, each with its position index. Mirrors GET /v1/projects/:project_id/rules.
rules.reorder content:write Reorder a project's rules. Takes the complete new order as `rule_ids`: every current rule id, listed exactly once (call rules.list first — a stale or partial list is refused rather than guessed at). Applied as one atomic write, not the dashboard's one-step-at-a-time up/down swap, so a stale list can't silently reorder the wrong rule.
rules.update content:write Change a project rule (matched by id). Partial update: pass only the fields to change, using the same field names as the REST rules API; a null clears an optional field. Returns the updated rule and the project's full rule list.

Demos

ToolScopeWhat it does
demos.clone content:write Duplicate a demo. All steps, overlays, and actions get fresh ids.
demos.create content:write Create a demo. Accepts nested steps[] + overlays[] for whole-demo authoring in one call.
demos.delete content:write destructive confirm Delete a demo with all its steps and overlays. It cannot be restored: a project's version history keeps only its rules. Confirm step: the first call changes nothing and returns a summary (the demo's name and its step and overlay counts) and a confirm_token; call again with the token to delete.
demos.get content:read read-only Fetch a demo by id with all steps + overlays + actions.
demos.list content:read read-only List all demos in a project.
demos.scaffold content:write Generate a starter project + multi-step demo from a product description and signup URL. Returns the persisted project with full nested demos/steps/overlays, ready to edit: demos.update (pass `steps` to rewrite step order), steps.create to append a step, overlays.create / overlays.update for overlays. Pro tier; metered by the LLM bridge.
demos.update content:write Patch a demo's top-level fields. Passing `steps` rewrites ALL of the demo's steps: the list given replaces them, overlays and actions included, and a step left out is deleted, so send every step to keep, in play order.
overlays.create content:write Add an overlay to a step. For drift protection on anchored overlays (those with target_selector), also pass target_context and optionally a baseline screenshot — see the input schema.
overlays.suggest_content content:write Propose new title + body copy for an existing overlay. Returns the suggestion WITHOUT persisting — call overlays.update once the user / agent is happy. Solo plan or higher; metered.
overlays.update content:write Patch an existing overlay. Use this to attach or re-capture drift-protection context (target_context + baseline screenshot) after a site change, or to edit any overlay field.
steps.create content:write Append a step to a demo. Returns the new step with its assigned id.
templates.list content:read read-only List the reusable card/screen content templates (Assets library kind card_template) visible to this workspace: public_id, target (card|screen), name, description, and fillable slots. Reference one from an overlay via template_id + template_vars (slot values) instead of hand-building card/screen content.

Sessions

ToolScopeWhat it does
recordings.get sessions:read read-only Fetch one recording take's metadata and the dashboard page where it can be watched. Needs the AI SE plan, as on the dashboard. Never returns a video URL.
recordings.list sessions:read read-only List a session's recording takes, metadata only: take number, status, duration, size, times, and the dashboard page where they can be watched. Needs the AI SE plan, as on the dashboard. Never returns video URLs.
sessions.create sessions:write Create a viewer session for a prospect and return the URL to share. Credentials must be encrypted client-side with the published viewer key.
sessions.get sessions:read read-only Fetch one viewer session: status, stage, vm_state, prospect, start and sign-in URLs, expires_at, ready_at, prep age, recording flag, and prep_url while it waits for the rep to sign in. The prospect link is not returned: it is stored only as a digest and shown once when issued, so use sessions.share_link.rotate to issue a new one.
sessions.launch sessions:write Launch a viewer session for a project as the dashboard's launch form does: optional demo or assistant, duration, prospect details, recording, and Advanced settings (start_url, sign_in_url, allowed_hosts, credit_budget) that default to the project's launch defaults when omitted and are cleared for this launch by null. Without a sign-in URL it returns the prospect link and emails it to the prospect email, if given. With one it returns stage "prep" and prep_url: the rep must open prep_url in their browser and sign in to the site with their own credentials (a human step; never ask for or send a password here), then click Ready or call sessions.ready, which emails the prospect.
sessions.list sessions:read read-only List the workspace's viewer sessions, newest first: status, stage, vm_state, prospect, expiry, ready time, prep age and recording flag. Paginated with cursor and limit. Prospect links are never included (see sessions.share_link.rotate).
sessions.navigate sessions:write Send a live session's browser to a URL, as the dashboard's Push does (bare hosts and http are upgraded to https). Refused when the URL is outside the session's navigation allowlist, or while the browser is not running (starting, or paused because nobody is watching).
sessions.ready sessions:write Hand a prep session to the prospect once the rep has signed in on its prep page: the browser goes to the start page with its history cleared, the time limit starts, the prospect is emailed, and the VM pauses until they join. Returns the prospect link on the first Ready (shown once). Call it only after the rep says they have signed in; the session's browser must be running (the prep page open).
sessions.reprep sessions:write Put a session launched with a sign-in URL back into prep so the rep can sign in again, for example after the site's login expired. The prospect's link stays valid and shows "being prepared" until the next Ready. Returns prep_url for the rep.
sessions.revoke sessions:write destructive confirm End a viewer session now: its prospect link stops working, anyone watching is disconnected, its VM is released, and subscribed webhooks get session.revoked. Irreversible: the first call only returns a summary and a confirm_token; call again with the token to revoke.
sessions.share_link.rotate sessions:write destructive confirm Issue a new prospect link for a live session and return it once; the current link stops working immediately and anyone watching through it is disconnected. Use it when the link is needed again, since links cannot be read back. Irreversible: the first call only returns a summary and a confirm_token; call again with the token.

Customer App

ToolScopeWhat it does
customer_app.action.cancel content:write Cancel a pending customer-app action confirmation token for an API-key-owned assistant conversation.
customer_app.action.confirm content:write Confirm a previously prepared customer-app action after trusted MyAISE-recorded consent. Requires the unchanged confirmation token, consent id, digest, tool name, and arguments.
customer_app.action.prepare content:write Prepare a customer-app mutating action and return a permission overview, confirmation token, and digest. Ask the user for explicit permission before calling confirm.
customer_app.catalog.get content:read read-only Return the redacted customer-app MCP catalog for an API-key-owned assistant conversation. Checks the durable snapshot against the app's live tools/list and returns the live catalog when they differ or no snapshot exists; the snapshot is used unchecked only when the live list can't be fetched.
customer_app.connection.get workspace:read read-only Read the team's customer-app MCP connection (the dashboard's Connected app card) or null: name, endpoint URL (without query string), how MyAISE authenticates, labels, operator switch, OAuth status, and timestamps, for one workspace environment (default production). Never returns tokens or client secrets. Team API keys only.
customer_app.connection.oauth.disconnect workspace:manage destructive confirm Disconnect the team's OAuth customer-app connection: revokes MyAISE's grant at the customer's authorization server and deletes the stored tokens; the connection and its settings stay. environment picks which environment's connection (default production). The first call returns a summary and a confirm_token.
customer_app.connection.oauth.start workspace:manage Start connecting the team's OAuth customer-app connection: returns a one-time link the user opens in a browser signed in to MyAISE as themselves (it works once, for 15 minutes); they continue to the customer's app, sign in and approve access. Then call customer_app.connection.get until oauth_status is connected. The connection must use credential_source oauth. environment picks which environment's connection (default production).
customer_app.connection.set workspace:manage Create or update the team's customer-app MCP connection for one workspace environment (default production; it must exist); omitted fields keep their values (endpoint_url is required to create) and the probe safety settings are left as they are. credential_source static_token saves the other fields and returns a one-time secure link where the user enters the token (never in chat; the connection switches to it once entered), with token_expires_at defaulting to no expiry; for oauth, call customer_app.connection.oauth.start next. A stored static token is never carried to a new endpoint: changing endpoint_url on a static-token connection drops the token, saves the connection as runner_handoff and returns a secure link to enter the token again. Returns the connection as customer_app.connection.get does, plus the link when a token is to be entered.
customer_app.connection.static_token.status workspace:read read-only Report whether the one-time secure link from customer_app.connection.set was used to enter the static token: status pending, used or expired, and whether the connection now authenticates with a static token. Never returns the token.
customer_app.connections.list workspace:read read-only List the team's customer-app MCP connection in every workspace environment, production first: one row per environment with its endpoint host, credential source, app_ref and OAuth status (null fields when the environment has no connection). Never returns tokens. Team API keys only.
customer_app.context.summary content:read read-only Return the safe customer-app snapshot summary and refs for an API-key-owned assistant conversation without refreshing live MCP state.
customer_app.findings.review workspace:manage Review a connected-app behavior finding (a corpus entry): set its review_status, confidence_label and/or allowed_downstream_uses, as the dashboard's review panel does. Archiving keeps the evidence. Returns the finding's review fields.
customer_app.probes.get workspace:read read-only Read one synthetic behavior probe run as the dashboard's probe page shows it: status, the redacted plan, guard and result, and each step's action, status and redacted evidence. Never returns tokens. The run must belong to the environment passed (default production).
customer_app.probes.preview workspace:manage Refresh the customer app's catalog from its live MCP server and build the synthetic behavior probe plan, as the dashboard's Index & preview does. Returns the writes the probe would make into the customer's app, the full plan, and plan_fingerprint for customer_app.probes.run. Writes nothing to the customer's app. environment picks which environment's connection (default production).
customer_app.probes.run workspace:manage destructive confirm Queue the previewed synthetic behavior probe, which makes real writes into the customer's app (its test account) once the Learner's network guard is up, then cleans up as the connection's cleanup contract says. Pass plan_fingerprint from customer_app.probes.preview; the call is refused if the plan changed since. The first call returns a summary listing every write, cleanup included, and a confirm_token. Pass the same environment as the preview (default production).
customer_app.schema.read content:read read-only Fetch the full field list for a customer-app object (the fields a lean write-tool schema omits — caps, timeouts, enums, nested *_attributes). Read-only; use it to build complete write arguments in one call.
customer_app.tool.read content:read read-only Call a read-only customer-app MCP tool for an API-key-owned assistant conversation. Mutating tools are refused before any live MCP request is sent.

Webhooks

ToolScopeWhat it does
event_types.list content:read read-only List every webhook event type the API can fire, with descriptions.
webhooks.create webhooks:manage Subscribe an https:// URL to webhook events in this workspace (team owners, or your own personal workspace). Returns the webhook and a one-time secure link (secret_link) that reveals its signing secret once; the secret is never in the chat.
webhooks.delete webhooks:manage destructive confirm Delete a webhook and its delivery history; pending deliveries are never sent. Confirm step: the first call changes nothing and returns a summary and a confirm_token; call again with the token to delete.
webhooks.deliveries.list webhooks:read read-only List the workspace's webhook deliveries, newest first: event, status (pending, succeeded, failed), attempt, response status and times. Paginated with cursor/limit.
webhooks.list webhooks:read read-only List webhook subscriptions in the workspace, each with the environment whose events it receives.
webhooks.redrive webhooks:manage Send a delivery again: it is re-queued now as pending with its attempt count reset. Returns the delivery.
webhooks.secret_link.status webhooks:read read-only Report whether the signing-secret link from webhooks.create was used: pending, used (the secret was revealed), or expired.
webhooks.test webhooks:manage Send a synthetic session.created event (data.test true) to the webhook's URL. It is queued like a real delivery, signed and retried; returns the delivery, whose outcome webhooks.deliveries.list shows.
webhooks.update webhooks:manage destructive Change a webhook's URL, its subscribed events (replaces the list), its environment (moves it to another existing environment), or turn it on or off; active true also re-enables a webhook auto-disabled after repeated failures. Omitted fields keep their values.

Solutions

ToolScopeWhat it does
solutions.archive content:write Archive a solution (removes it from retrieval and the review backlog).
solutions.get content:read read-only Fetch one solution in full: content, steps, phrasings, hints, sanitization report, embedding status.
solutions.list content:read read-only List the team's assistant knowledge-base solutions (defaults to the DRAFT review backlog). Returns per-status counts so you can see the backlog size. Team API keys only.
solutions.publish content:write Publish a solution so the assistant can retrieve and cite it. REFUSED unless the stored sanitization report passed (the privacy gate is code, not UI). A rated answer (source rated_answer) also needs verified_no_customer_data: true, which you may send only after reading its full text and finding no customer names, numbers, ids or account values.
solutions.update content:write Edit a solution after validating it against the real product/codebase. Content edits (title, problem_statement, solution_markdown, steps, phrasings) reset the embedding for re-embed and re-run the sanitization audit on mined solutions; hint edits (answer_hint, walkthrough_hint, workflow_hint) do not. Pass "" to clear a hint.

Assist

ToolScopeWhat it does
assist.emergency_stop workspace:manage destructive confirm Immediately pause the learner, cancel every active job, and destroy any running learner session for this team. Irreversible for in-flight work, so this asks for confirmation first; the summary counts the jobs that will be cancelled. Team API keys with workspace:manage only.
assist.jobs.cancel workspace:manage Cancel a job by id. A queued job cancels immediately; a claimed/running job is flagged and the runner acknowledges at its next check-in. Team API keys with workspace:manage only.
assist.jobs.enqueue workspace:manage Manually enqueue learner/crawler work. kind is one of: groundtruth_learn, kb_crawl, or walkthrough_prebuild (dedup-guarded — a kind already queued or running returns already_queued); capture_gaps (queue a pass over pages observed but never captured); or suggest_url (queue a direct crawl of one path/URL under the site root — requires the url argument). Team API keys with workspace:manage only.
assist.jobs.list workspace:read read-only List learner/crawler jobs for the team, newest first: kind, status, origin, and timestamps. Paginated. Team API keys only.
assist.learner.credentials.set workspace:manage Set the demo-account login the learner uses: login_url and username are stored immediately (they are not secret); the response is a one-time secure link where the password is entered in a browser. The password never passes through chat. Team API keys with workspace:manage only.
assist.learner.get workspace:read read-only Read the team's learner configuration: site/KB crawl roots, crawl intervals, the daily agent-turn cap, domain context, and the mining window (days). Also reports whether demo-account login credentials are set — login_url and username are returned (they are not secret), the password never is. Team API keys only.
assist.learner.update workspace:manage Change the team's learner targets: site/KB crawl roots, crawl intervals (minutes), the daily agent-turn cap (clamped to the platform maximum), and domain context for the AI prompts. Partial update: pass only the fields to change; null clears an optional value. Does not touch demo-account credentials (assist.learner.credentials.set) or the mining window (assist.mining_window.set). Team API keys with workspace:manage only.
assist.learner_session.stop workspace:manage Stop the team's currently running learner viewer session, if any. In-flight jobs requeue unless cancelled or the learner is paused first — use assist.pause.set or assist.emergency_stop to halt everything. Team API keys with workspace:manage only.
assist.mcp_hints.get content:read read-only Read the team's free-text "how our MCP server works" guide that is injected into the assistant's prompt.
assist.mcp_hints.set content:write Replace the team's "how our MCP server works" guide (max 4000 chars; empty string clears it). Injected into every assistant answer for this team — describe tool naming, common workflows, and quirks of YOUR MCP server.
assist.mining_window.set workspace:manage Set how many days back ticket/chat mining reaches (minimum 30). Widening the window resets the team's Zendesk sync cursors, which triggers a re-pull of the newly-included older tickets on the next sync (within ~5 minutes). Team API keys with workspace:manage only.
assist.pause.set workspace:manage Pause or resume the learner (blocks claiming/scheduling/provisioning; jobs already running are unaffected — cancel them or use assist.emergency_stop) or the ingest collector (rejects new batches; cursors are safe, nothing is lost). Team API keys with workspace:manage only.
assist.refresh_token.rotate workspace:manage destructive confirm Rotate the team's inbound deploy-webhook refresh token. The current token stops working immediately, so this asks for confirmation first. On confirmation, returns a one-time secure link to reveal the new token — it is shown once and never appears in chat. Team API keys with workspace:manage only.
assist.settings.get workspace:read read-only Read the team's Assist settings as the dashboard shows them: the assistant (enabled, name, instructions, theme, animation/transition pools), website-integration limits (session time limits in minutes, AI-credit budgets, navigation allowlist), per-end-user embed credits, and whether support mining is on. Never includes credentials or tokens. Team API keys only.
assist.settings.update workspace:manage Change the team's Assist settings. Partial update: pass only the fields to change; omitted fields keep their values and null clears an optional value. Validated like the dashboard forms (a default can't exceed its maximum, hosts must be valid hostnames, the out-of-credits link must be https). Returns the same shape as assist.settings.get. Team API keys with workspace:manage only.

Keys

ToolScopeWhat it does
api_keys.create workspace:manage Create an API key in this workspace from a dashboard preset (standard, full, read_only, sessions_only, whitelabel_only); only the owner can create team keys. The secret is never returned: the result has the key's metadata and a one-time secure link (secret_link) the user opens in their browser to reveal it once within 15 minutes; api_keys.secret_link.get reports whether it was opened.
api_keys.list workspace:read read-only List this workspace's API keys as the dashboard's API keys page shows them: id, name, prefix, last4, scopes, preset, created_at, last_used_at and creator; never a secret. Team keys are listed for the team owner only. Connected apps (OAuth sign-ins) are not keys; use connected_apps.list.
api_keys.revoke workspace:manage destructive confirm Revoke an API key of this workspace; anything using it stops working at once. A personal key's user or, for team keys, the team owner may revoke. Takes the confirm step: the first call changes nothing and returns a summary that also says when the key is the one making this call.
api_keys.secret_link.get workspace:read read-only Report whether the one-time secure link returned by api_keys.create was used: pending, used or expired. Only the user the link was made for can check it, in the workspace it was made in.
connected_apps.list workspace:read read-only List the apps connected to this workspace by signing in to MyAISE (OAuth), such as Claude: id, client_name (chosen by the app itself, unverified), redirect_hosts, workspace, scopes, connected_by, created_at and last_used_at. A member sees the apps they connected; the team owner sees every app in the team.
connected_apps.revoke workspace:manage destructive confirm Disconnect an app: revoke its OAuth grant and every token issued under it. The user who connected it or the team owner may revoke. Takes the confirm step: the first call changes nothing and returns a summary that also says when the app is the one making this call.
ingest_keys.create workspace:manage Create a team ingest key bound to one data source (a lowercase slug such as telegram, slack, whatsapp, zendesk or other); it can only feed channels of that source. The secret is never returned: the result has a one-time secure link (secret_link) the user opens in their browser to reveal it once within 15 minutes. Team API keys only.
ingest_keys.list workspace:read read-only List the team's ingest keys (source-bound keys for support-data collectors): id, name, source, prefix, last4, created_at, last_used_at and creator; never a secret. Team API keys only.
ingest_keys.revoke workspace:manage destructive confirm Revoke a team ingest key; the collector using it stops working at once. Takes the confirm step: the first call changes nothing and returns a summary. Team API keys only.
ingest_keys.secret_link.get workspace:read read-only Report whether the one-time secure link returned by ingest_keys.create was used: pending, used or expired. Only the user the link was made for can check it. Team API keys only.

Workspace

ToolScopeWhat it does
blocked_paths.create workspace:manage Create a blocked URL pattern for the team. status_code is 200 (silently allow through with an empty response), 403 (block) or 404 (hide). Owner only.
blocked_paths.delete workspace:manage destructive confirm Delete a blocked path. Owner only, irreversible.
blocked_paths.list workspace:read read-only List the team's blocked URL patterns (dashboard: Team > Blocked paths). Any member can call this; team-tier plan required.
blocked_paths.lock workspace:manage Lock or unlock a blocked path. A locked path can only be changed or deleted by the owner, even though a member could otherwise toggle it. Owner only.
blocked_paths.update workspace:manage Turn a blocked path on or off. Any member can call this, UNLESS the path is locked — then only the owner can change it.
invitations.list workspace:read read-only List the team's pending invitations: email, when sent, and when they expire. Owner only, as on the dashboard's Team page (members never see pending invitations there either). Team-tier plan required.
invitations.resend workspace:manage Resend a pending invitation email with a fresh link (the old link stops working). Owner only, team-tier plan required, rate-limited to once every 30 seconds per invitation, exactly as the dashboard.
invitations.revoke workspace:manage destructive confirm Revoke a pending invitation so its link stops working. Owner only, team-tier plan required. Irreversible: the invitee would need a brand new invitation to join.
members.invite workspace:manage Invite someone to the team by email, within the workspace's seat limit. Sends the invitation email; the invitee accepts it themselves — this never adds them directly. Owner only, team-tier plan required.
members.list workspace:read read-only List the team's members: user id, name, email, role, and when they joined. Any member can call this; the workspace must be on a team-tier plan, as on the dashboard's Team page.
members.remove workspace:manage destructive confirm Remove a member from the team. Revokes every API key they created in this team and moves their team projects and live viewer sessions to the workspace owner. Owner only, team-tier plan required, irreversible.
workspace.get workspace:read read-only Read the API key's own workspace: id, name, slug, kind (team or personal), the acting user's role, a plan/tier summary, and seat usage (used and total). Works for personal keys too.
workspace.leave workspace:manage destructive confirm Leave this team workspace yourself (self-service; the owner can't use this — they'd transfer ownership or delete the team from the dashboard instead). Revokes every API key you created in this team, including the key used for this call, and moves your team projects and live viewer sessions to the owner. Irreversible.
workspace.rename workspace:manage Rename the team workspace (dashboard: Team page "Rename"). The slug is regenerated from the new name, exactly as the dashboard does. Owner only.

Integrations

ToolScopeWhat it does
integrations.zendesk.create workspace:manage Start connecting a new Zendesk subdomain. subdomain and email are stored immediately (they are not secret); a Zendesk connection requires an API token to exist at all, so the response is a one-time secure link where the token is entered in a browser — it never passes through chat. Team API keys with workspace:manage only.
integrations.zendesk.delete workspace:manage destructive confirm Delete a Zendesk connection and every item harvested from it. Irreversible, so this asks for confirmation first; the summary counts the harvested items that will be deleted. Team API keys with workspace:manage only.
integrations.zendesk.list workspace:read read-only List the team's Zendesk connections: subdomain, auth email, enabled state, last sync time, and the number of items harvested from each. Never returns the API token. Paginated. Team API keys only.
integrations.zendesk.toggle workspace:manage Enable or resume, or pause, a Zendesk connection's sync — flips its current state. Team API keys with workspace:manage only.
integrations.zendesk.update_credentials workspace:manage Reconnect an existing Zendesk connection with a fresh API token — for a token that was rotated or purged on Zendesk's side. Optionally updates the auth email (omit to keep the current one). Returns a one-time secure link where the token is entered in a browser; it never passes through chat. Team API keys with workspace:manage only.

Knowledge

ToolScopeWhat it does
answers.curate content:write Create or replace a human-authored curated answer for a question: stored as a published solution so the assistant reuses it verbatim next time. Pass solution_id to edit an existing curated answer in place.
clusters.dismiss content:write destructive Discard a still-forming issue group: it stops collecting and will never draft. Its raw examples are kept.
clusters.list content:read read-only List the team's still-forming issue groups (collecting examples toward a draft), biggest first, each annotated with its nearest already-drafted group (a candidate to fold into instead of writing up fresh) — the dashboard's collecting-groups panel.
clusters.merge content:write destructive Fold a forming near-duplicate issue group into the already-drafted group it orbits: its items become phrasing evidence for the existing solution, and the source group is removed.
clusters.promote content:write Manually promote a still-forming issue group to ready, so it writes up without waiting for critical mass — the human override for rare-but-valid questions.
corpus.archive content:write destructive Remove one corpus entry (a KB document, learned page, workflow trace, or solution) from retrieval and the review backlog. Soft delete: the crawler/learner will not bring it back on the next pass. No confirm step — decide before calling.
corpus.decoder.set content:write Turn the LLM conversation decoder on or off for the team — the revert path for a re-decode.
corpus.get content:read read-only Fetch one corpus entry in full: a KB document's markdown, a learned page's elements and traces, or a solution's full content. kind + id come from corpus.list.
corpus.list content:read read-only List one section of the team's assistant knowledge corpus, exactly as the dashboard's corpus browser shows it: crawled KB documents, learned pages, verified workflow traces, or solutions (published, or the draft/approved review backlog). Paginated. Team API keys with an API-access plan only.
corpus.pin content:write Pin or unpin a KB document as always-relevant: a pinned active document's content is always included in the assistant's context, regardless of the question.
corpus.recluster content:write Re-cluster the team's already-decoded issues without re-decoding (no LLM cost): dismisses the current decoded-source groups and archives their now-stale mined drafts, then kicks the miner to regroup from the existing title embeddings. Use after tuning the clustering threshold, or to clear stale groups.
corpus.redecode.full content:write destructive confirm Reset the WHOLE support corpus for the LLM conversation decoder: dismisses every legacy per-line issue group, archives their draft (unverified) mined solutions, switches the team to the decoder, and kicks the miner to rebuild from scratch. Raw messages are never deleted, but this spends AI budget re-mining the entire corpus — a bulk, expensive change. Requires confirmation; the summary reports live counts.
corpus.redecode.trial content:write destructive confirm Bounded trial of the LLM conversation decoder: resets the newest 200 raw support messages to the pipeline's clean starting state, deletes the team's existing decoded issues and dismisses the issue groups built from them (derived data that cannot be restored), switches the team to the decoder, and kicks the miner. The legacy per-line issue groups and draft solutions are left untouched so the trial is directly comparable. Spends AI budget re-mining the sample. Requires confirmation; the summary reports live counts.
messages.rate content:write destructive confirm Operator rating that publishes, on one assistant answer — the rating flywheel: Good curates the answer (or an edited correction) as a retrievable solution, optionally a gold exemplar, and boosts the sources it cited; Bad with a correction curates the correction as the override and demotes them. Either way the end user's question is published with it as curated content, so this takes the confirm step: the first call changes nothing and returns a summary quoting the question and the text to be published, plus a confirm_token. A Bad rating without a correction publishes nothing: use messages.rate_bad, which needs no confirmation.
messages.rate_bad content:write Operator Bad on one assistant answer, without a correction: records the reason and note, demotes the quality of every solution the answer cited, and lists the exchange as an answer gap. Publishes nothing, so there is no confirm step. To publish a correction, use messages.rate with rating bad and a correction.
solutions.exemplar content:write Mark or unmark a curated solution as a gold few-shot exemplar injected into the answer prompt.
solutions.pin content:write Pin or unpin a solution as always-relevant: a pinned published solution is always fed to the assistant regardless of the question.
solutions.redraft content:write destructive Rewrite a mined solution from its issue group's CURRENT examples. Returns the solution to draft for review (unpublishing it first if it was live) and overwrites its existing content. Spends AI budget. Manual (non-mined) solutions have no issue group and are refused.
solutions.verify content:write Audit a solution's structured workflow against the team's live customer-app snapshot (the same connection-scoped catalog the dashboard's "Audit against current catalog" button uses). Records a verification attempt and stamps verified_at on a pass. Defaults to the newest active snapshot for the solution's app; pass snapshot_id to target a specific one.
solutions.verify_live content:write Queue the learner to walk this solution's steps in the live demo account. A full, end-to-end pass stamps the solution UI-verified when it completes (asynchronous — this only enqueues the job). Refused when the solution has no steps.
walkthroughs.promote content:write Promote a Good generated walkthrough into the reusable library (published, no expiry), so the assistant reuses it instead of regenerating.
walkthroughs.rate content:write Operator Good/Bad + reason on one generated (assistant-source) walkthrough.

Embed

ToolScopeWhat it does
embed.drift.get content:read read-only Read a demo's drift-check settings (project, interval, auto-apply threshold, enabled), or null when none are set.
embed.drift.run content:write Queue a drift check of a demo now; the runner logs in with the project's test credentials and the check is billed. Returns the job id and status; when a check is already queued for the demo, that job is returned instead of a second one.
embed.drift.set content:write destructive Create or change a demo's drift-check settings. Partial update: omitted fields keep their values; new settings default to on-demand only, threshold 0.9, enabled, and the demo's own project. Returns the saved settings.
embed.end_users.revoke content:write destructive Revoke an embedded end user (they were banned or removed): ends their open assistant sessions, refuses every embed JWT and context session issued until now, and disconnects their app connections. A JWT signed afterwards works again.
embed.secret_link.status content:read read-only Report whether a secure link from embed_keys.create or embed.test_credentials.set was used: pending, used, or expired. A used test-credentials link means the credentials were saved.
embed.test_credentials.delete content:write destructive confirm Delete a project's stored test-account credentials. Confirm step: the first call changes nothing and returns a summary and a confirm_token; call again with the token to delete.
embed.test_credentials.set content:write destructive Set a project's test-account credentials, which drift checks use to log in: method cookies, headers, or jwt_callback. Returns a one-time secure link where the user pastes the cookies or headers JSON (see format) or the callback URL, never in the chat; saving replaces the project's current credentials. embed.secret_link.status reports when they were saved.
embed.user_credits.add content:write destructive Add purchased or granted credits to an embedded end user (creating the user if they haven't visited yet): spent after their allowance and carried over until used; re-arms the exhausted webhook. NOT idempotent — a call whose response was lost must not be repeated blindly. Returns the same shape as embed.user_credits.get.
embed.user_credits.get content:read read-only Read an embedded end user's credit allowance and spend for the current period, by the end_user_ref your embed JWT carries.
embed.user_credits.set content:write destructive Set an embedded end user's credit allowance, creating the user if they haven't visited yet. An allowance set here is pinned (clamped to the workspace maximum): it beats the user's JWT claims until clear_override, while the period follows the JWT. Omitted fields keep their values; reset_spent zeroes the period's spend and alone pins nothing; top-ups go through embed.user_credits.add. Returns the same shape as embed.user_credits.get.
embed_keys.create content:write Create an embed key for the website widget and JS embed library. Returns the key (its id, the embed key id, is the JWT kid) and embed_key, the key itself, which is public and also shown on the dashboard's API keys page. signing_mode none needs no JWT; ed25519 takes your public key; hs256 generates a signing secret that is revealed once through a one-time secure link (secret_link), never in the chat.
embed_keys.list content:read read-only List the workspace's active embed keys: id (the embed key id, the JWT kid), name, allowed domains and demo ids, signing mode, Ed25519 public key, token prefix and last 4, environment, and timestamps. Never returns tokens or signing secrets. Paginated with cursor/limit.
embed_keys.revoke content:write destructive confirm Revoke an embed key: pages using it stop loading demos, its JWTs stop verifying, and its open embed sessions end. Confirm step: the first call changes nothing and returns a summary and a confirm_token; call again with the token to revoke.
embed_keys.update content:write destructive Change an embed key in place: allowed domains, allowed demo ids, (ed25519 keys) the public key, or its environment (moves it to another existing environment; sessions it already started keep theirs); the id (kid) and token stay the same. A new public key makes JWTs signed with the old private key fail at once, until your backend signs with the new one. The signing mode can't change in place: create a new key and revoke the old one.

Library

ToolScopeWhat it does
assets.archive content:write destructive Archive an asset in this key's workspace (removes it from the Library; it stops being usable in new demos). Owner-only, as assets.rename. There is no confirm step — the dashboard button archives immediately too.
assets.edit content:write Regenerate a PERSONAL asset's code from an edit instruction, in place (keeps its public_id, bumps its version, re-verifies). Async: returns immediately with status "generating". Owner-only, with a personal API key; requires a Solo or AI SE plan, as assets.generate.
assets.fork content:write "Add to my library" — deep-copy an approved marketplace asset into your own personal library as an editable copy (a personal API key). Premium assets require an existing entitlement (buy first via the dashboard); free/bundled assets fork immediately.
assets.generate content:write Generate a new Library asset (theme, overlay_animation, transition, or card_template) from a text prompt with hosted AI. Async: returns immediately with the new asset's id and status "generating" — poll assets.get until status is approved / needs_changes. The asset lands in your personal library, so this needs a personal API key. Requires a Solo or AI SE plan (design-kit tier); the dashboard's free-tier "bring your own key" option is not available over MCP.
assets.get content:read read-only Fetch one Library asset by public_id, including its CSS/JS code when the caller is entitled to it. Visible scopes are the same as assets.list.
assets.list content:read read-only List the Library assets in this key's workspace: approved marketplace assets plus, for a personal key, your own personal assets, or, for a team key, that team's approved library assets (paid teams). No code — use assets.get for one asset's CSS/JS. Paginated.
assets.propose content:write Propose one of your own approved personal assets to this key's team's shared library: a team API key, and team_id must be that team. The team owner must approve it (publications.approve) before it goes live for the team. Requires the team to be on a paid (AI SE) plan.
assets.rename content:write Edit an asset's display name and/or description, in this key's workspace. Owner-only (with a personal key, your own personal asset; with a team key, one of that team's assets if you own the team). Does not re-verify or touch code.
design_kit.import content:write Import a customized overlay design kit HTML file (downloaded from the Library, restyled by hand or in an AI): base64 in kit_b64, up to 8 MB decoded. Parses the delimited MYAISE:*:START/END blocks (the HTML is never rendered or executed) into a theme, any overlay animations and any transitions, then imports the ones named in selected (default: all). Pass replace to overwrite one of your own personal assets of the same kind in place (keeps its public_id) instead of creating a new one. Every imported asset re-verifies before it can be shared. The assets land in your personal library, so this needs a personal API key. Requires a Solo or AI SE plan.
publications.approve content:write Approve a pending proposal to this key's team's library (assets.propose), moving that asset into the team's shared library. A team API key; team owner only; requires the team to be on a paid plan.
publications.reject content:write Reject a pending proposal to this key's team's library (assets.propose). A team API key; team owner only.

Branding

ToolScopeWhat it does
branding.get workspace:read read-only Read the team's whitelabel viewer branding: enabled state, verification status, screen HTML/meta fields (splash, toolbar, session-ended, chat, invite email), page title/favicon/background, and version. email_screen reports the invite email's verification (absent, pending, not_verified, needs_changes, verified) with its findings, and sending_branded says whether invites use it right now (it also needs the team's email sender domain to be active). null when the team has never uploaded a branding kit. Any team member; requires the AI SE plan.
branding.import workspace:manage Upload a customized viewer branding kit HTML file (downloaded from the team dashboard, restyled by hand or in an AI): base64 in kit_b64, up to 8 MB decoded. Parses the delimited MYAISE:BRAND_*:START/END blocks (the HTML is never rendered or executed) and queues it for verification (status becomes "verifying"); it does not go live until branding.toggle after it is approved. The invite email is the MYAISE:BRAND_EMAIL (HTML), MYAISE:BRAND_EMAIL_SUBJECT and optional MYAISE:BRAND_EMAIL_TEXT blocks; slots are <ca-slot name="prospect_name|sender_name|message|expires|team_name|view_url">fallback</ca-slot>, and the button is <a data-ca-href="view_url">, whose href MyAISE writes. Team owner only.
branding.send_test_email workspace:manage Send the team's whitelabel invite email, rendered with sample values, to your own account email (it takes no address and never mails anyone else). It sends only what prospects would get: branding approved and enabled, the invite email verified, and the team's email sender domain active; otherwise it says which is missing. 5 per team per hour. Team owner only.
branding.toggle workspace:manage Enable or disable the team's whitelabel branding. Enabling requires the uploaded kit to already be verified (status "approved") — an unverified or needs-changes kit cannot go live. Team owner only.

Domains

ToolScopeWhat it does
custom_domain.check workspace:manage Re-check the team's custom viewer domain now instead of waiting for the next scheduled check (for example right after adding the DNS records). A domain that needs attention resumes setup. Returns the domain and what DNS shows right now for each record. Team owner only; requires the AI SE plan.
custom_domain.get workspace:read read-only Read the team's custom viewer domain (the hostname prospects open demo links on, instead of the default viewer domain): hostname, status, whether it is serving, the last problem found and the exact DNS records to add (a TXT ownership record and a CNAME). custom_domain is null when none is set. Also says whether the feature is available here and whether the team's plan includes it. Any team member.
custom_domain.remove workspace:manage destructive confirm Remove the team's custom viewer domain: new demo links go back to the default viewer domain at once, links on the custom hostname stop working, and its setup is deleted. Cannot be undone. Takes the confirm step: the first call changes nothing and returns a summary. Team owner only (on any plan).
custom_domain.set workspace:manage Connect a hostname you control (a subdomain such as demo.example.com) as the team's custom viewer domain. Returns the DNS records to add: a TXT record proving ownership and a CNAME routing the hostname; setup (ownership check, then an HTTPS certificate) continues automatically and can take from minutes to a few hours. Setting the current hostname again re-checks it. When the team already has a different domain, pass replace: true to remove that one and use the new one. Team owner only; requires the AI SE plan.
email_domain.check workspace:manage Re-check the team's sender domain now instead of waiting for the next scheduled check. A domain whose DKIM check failed starts again with new DKIM records. Returns the domain and whether the ownership TXT record shows in DNS right now. Team owner only; requires the AI SE plan.
email_domain.get workspace:read read-only Read the team's custom email sender domain (demo invitations are sent from an address on it, such as demos@example.com, instead of from MyAISE): domain, sender name and address, status, whether it is in use, the last problem found and the exact DNS records to add (a TXT ownership record, three DKIM CNAMEs once issued, and MX + TXT for a custom MAIL FROM domain). email_domain is null when none is set. Any team member.
email_domain.remove workspace:manage destructive confirm Stop sending demo invitations from the team's domain: they go out from MyAISE again at once and the domain's sending setup is deleted. Cannot be undone. Takes the confirm step: the first call changes nothing and returns a summary. Team owner only (on any plan).
email_domain.set workspace:manage Send the team's demo invitations from a domain you control (the domain itself, such as example.com, or a subdomain). Returns the DNS records to add: first a TXT record proving ownership; once it is found, three DKIM CNAMEs appear (call email_domain.get or email_domain.check). Invitations switch to the new sender when the domain is verified, which can take up to 72 hours. For the current domain, only the given sender fields change. When the team already has a different domain, pass replace: true. Team owner only; requires the AI SE plan.

Billing

ToolScopeWhat it does
billing.checkout_link workspace:manage Create a Stripe Checkout link to subscribe to (or change to) a plan. Returns a checkout_url the user opens in their browser to complete payment; it is not usable from inside this chat. Refused if the account already has a live subscription (changes to an existing subscription go through billing.portal_link) or has a payment past due.
billing.credit_pack_link workspace:manage Create a Stripe Checkout link for a one-time AI-credit top-up pack. Returns a checkout_url the user opens in their browser to complete payment. Requires a Solo or AI SE plan (Studio is BYOK-only and cannot buy hosted-AI credits).
billing.get workspace:read read-only Read the caller's own billing state: plan, seat count, billing interval, AI credit balance, next credit renewal, and whether a payment method is on file. Never returns card details or Stripe ids.
billing.portal_link workspace:manage Create a Stripe Customer Portal link for the caller to change plan/seats, update their card, view invoices, or cancel. Returns a portal_url the user opens in their browser; it is not usable from inside this chat. Refused (with a payment_method_update link instead) while a payment is past due — pay it first.

Usage

ToolScopeWhat it does
usage.export_link workspace:read read-only Build a URL to the dashboard's usage.csv export for the caller's workspace, with the same filters as usage.ledger. The link needs an existing signed-in browser session (it is not a bearer-token API endpoint) — open it in a browser where you are already signed in to MyAISE.
usage.ledger workspace:read read-only Paginated raw ledger rows for the caller's workspace, newest first, with the same filters as the dashboard's Usage page (period, kind, action, source_type, source_id, end_user_ref, status, environment). Each row names its environment.
usage.summary workspace:read read-only Credit-ledger totals for the caller's workspace and period: overall credits used / recorded (shadow) / refunded / net, broken down by action ("by kind"), by causing-entity source_type ("by source") and by environment, plus the current credit balance. Defaults to the current calendar month and every environment; environment narrows the totals to one.

Environments

ToolScopeWhat it does
environments.create workspace:manage Add an environment to the workspace, e.g. development for testing against a dev copy of the customer's app. It starts empty: give it its own customer-app connection (customer_app.connection.set), embed keys (embed_keys.create) and webhooks (webhooks.create), each with environment set. Knowledge, assistant settings, demos, credits and end users are shared. At most 5 environments, production included. Team API keys only.
environments.delete workspace:manage destructive confirm Delete an environment. Refused for production, and while the environment still has live embed keys, webhooks, or a customer-app connection holding a live credential: the error names what to move or revoke first. Conversations and ledger rows keep the environment's name as history. The first call returns a summary of what is in the environment and a confirm_token.
environments.list workspace:read read-only List the workspace's environments, production first: name, default (true for production), created_at, and counts of what each holds (live embed keys, active customer-app connections, webhooks). A workspace has at most 5, production included. Team API keys only.
Secrets never go through chat. A tool that needs a secret, or that mints one shown once, returns a secure link to a one-time myaise.com page instead — it works once, for the signed-in user who asked for it, for 15 minutes. A destructive tool deletes, revokes, removes, or overwrites something; when it's also irreversible enough to need a second look, it carries confirm — the first call changes nothing and returns a human-readable summary plus a confirm_token, and only a second call with that token executes. Every successful write records an audit event naming the changed field NAMES only, never their values.

5 · Drift protection (self-healing) #

Customer sites change. When an overlay's target element moves or its selector breaks, MyAISE re-locates it using the overlay's target_context — a snapshot of the element's anchors. To make an agent-authored demo durable:

  1. Set auto_healing_enabled: true on the demo (demos.create / demos.update).
  2. Give every anchored overlay (one with a target_selector) a target_context on overlays.create — capture it from the live page you authored against:
json · target_context
"target_context": {
  "selectorCandidates": [
    { "kind": "id", "selector": "#new-report", "rank": 0 },
    { "kind": "role-name", "selector": "button[name=\"New report\"]", "rank": 2 }
  ],
  "role": "button",
  "accessibleName": "New report",
  "textContent": "New report",
  "nearbyText": ["Reports", "Create"],
  "rect": { "x": 120, "y": 340, "width": 96, "height": 32 }
}

Optionally pass a base64 screenshot alongside it; MyAISE stores it as a baseline demo asset and references it from target_context.screenshotRef for visual diffing. Re-baseline after a site change with overlays.update (a screenshot-only update keeps the existing anchors). Floating overlays (modal/banner with no target_selector) don't need this.

No credentials needed at authoring time. The baseline is credential-free — you supply it from the page you already loaded. Drift detection runs later inside the prospect's authenticated viewer session (the credentials from sessions.create), so it never needs the agent's cookies.
Section 06

Embed library #

Drop a <script> tag in your own product and run MyAISE-authored demos on your customer's REAL accounts. Overlays + transitions render on your DOM — no proxy hop, no PII upload, no screen recording. Team-tier only.

Embed mode never dispatches synthetic events on your customer's DOM. Demos that depend on pre-demo / driven actions render as informational overlays — your prospect advances with the library's Next button.

Install #

One bundle — myaise.min.js — does everything: overlay rendering, CSS step transitions, and the full Three.js transition set. Each demo's custom themes / animations / transitions are fetched at launch as a per-demo asset pack (one request, SRI-verified), so the bundle stays the same size no matter how customized your demos are. Strict-CSP-safe — no unsafe-eval, no new Function. Load from our CDN (versioned) or self-host with SRI:

Asset code (custom and marketplace transitions, scripted overlay animations) never runs in your page: each run gets a sandboxed frame (sandbox="allow-scripts") with no access to your page, cookies or storage. The frame runs inline script only and its policy blocks requests. A Content-Security-Policy cannot switch WebRTC off, so the frame's first script, before any asset code, removes WebRTC and media capture from the frame, makes shadow DOM unavailable in it and discards any frame nested inside it. The frame still receives the before/after snapshots of your page that it animates, and a browser cannot stop a frame from navigating itself, so treat a transition's snapshots as readable by the asset's author. Marketplace code is published only after an admin reviews it and our automated review passes it; the automated review rejects any code that uses WebRTC or media capture. An asset's stylesheet is not script and does not run in the frame: a transition's code_css is added to your page's <head> (asset verification rejects a stylesheet that loads anything but an inline image). Under a strict CSP, keep your API host in connect-src (the frame's Three.js build loads from it) and, with a nonce-based script-src, pass cspNonce.

The SDK is served from MyAISE and exposes window.MyAISE; it is not published to npm, so install it only from these script tags. These examples give it the local name MyAISE and connect it to the MyAISE API.

html · CDN
<script src="https://myaise.com/embed/v1/myaise.min.js"></script>
<button id="show-me-tour">Show me a tour</button>
<script>
  const MyAISE = window.MyAISE;
  MyAISE.init({
    apiKey: "myaise_pub_xxx",
    apiBase: "https://myaise.com",
  });

  document.querySelector("#show-me-tour").addEventListener("click", () => {
    MyAISE.launch("your-demo-uuid");
  });
</script>

Or self-host (recommended for strict CSP): replace the CDN script tag with this one and keep the initialization and launch code below it.

html · self-host + SRI
<script
  src="/static/myaise.min.js"
  integrity="sha384-wlZ/BbgH0VR5ZSzjMDRPivBRHSoylx0XjedoLTDm+RkfaO5o1ZZ5cRFPtXMrgsj1"
  crossorigin="anonymous"
></script>

Embed keys #

Embed mode uses embed keys (myaise_pub_*), different from the secret myaise_live_* keys you use server-side. Create one on the API keys page, where each key, its embed key id (the JWT kid) and your team id stay visible:

  • Domain allowlist: customer.com, *.customer.com. A leaked key can't be used from any other origin.
  • Demo allowlist: pin a key to one or more demo UUIDs.
  • Team-scoped: embed keys belong to teams (AI SE plan required).

Embed keys are designed to live in your HTML — they're usage scoping, not protection. Treat the demo spec as public content (your customer's visitors can see it anyway).

For tighter setups, mint short-lived launch JWTs from your backend:

javascript · jwt mode
MyAISE.init({
  apiBase: "https://myaise.com",
  jwt: await fetch("/api/myaise-token").then(r => r.text()),
  jwtRefresh: async () => fetch("/api/myaise-token").then(r => r.text()),
});

JWTs are HS256-signed with a per-key secret OR Ed25519-signed against a public key you register with us. Required claims: iss, aud=myaise.embed, typ=embed_access, sub, kid (header), origin, team_id, jti, exp at most 5 minutes out. When the key has a demo allowlist, sub must be one of those demo ids. Optional: end_user_ref, context, and the credit claims.

Identify the end user with end_user_ref, never sub. The embedded assistant scopes chats and credit allowances to the explicit end_user_ref claim — your stable id for the signed-in user, e.g. user.id. sub is never used as the end-user identity; without end_user_ref the context exchange refuses with 403 end_user_required, and an end_user_ref that names one of your demos is refused with 400 invalid_end_user.

Session tokens. The session_api_key an assistant conversation or demo launch returns stops working when the session ends, when its key is revoked (revoking a key ends every open session it minted), 24 hours after it was issued, or after 2 hours unused. Every assistant turn also re-checks the key, the plan, the assistant switch and the domain allowlist, so turning any of them off stops turns on tokens already issued.

Per-user credits #

When the assistant is embedded in your product, every answer draws on your workspace balance. Per-user allowances let you decide how much of it each of your users may spend — sell credits monthly, grant a fixed pool, or block a user — without giving anyone unbridled access.

Allowances need JWT mode: the user is identified by the end_user_ref claim (required — sub is not an identity). Once any allowance is configured (a workspace default or maximum, or any user with an allowance), conversations without a signed end_user_ref are refused with 403 end_user_required, so a capped or blocked user can't fall back to an anonymous chat. Three optional claims set the allowance for that user:

ClaimMeaning
credit_allowanceWhole number ≥ 0. Credits the user may spend per period. 0 blocks the user; omit it to use the workspace default (no cap unless you set one).
credit_period"monthly" (default — resets on the first of each UTC month) or "total" (never resets; top up with add_credits).
credit_period_keyYour own billing-cycle key, e.g. "2026-09-15". When it changes, the user's spend resets to zero. Implies a custom period.

An allowance set through the API or the dashboard is a pin that wins over the claim, which wins over the workspace default; a JWT never overwrites a pin, with or without claims, until you clear it. Every claim and pin is clamped to the maximum allowance in the dashboard (Assist → Website integration → Embedded assistant), and invalid claims refuse the exchange with 400 invalid_credit_claims rather than silently granting unlimited credits.

javascript · jwt claims with an allowance
// Your backend, when rendering the page for a signed-in user:
const claims = {
  iss: "app.example.com", aud: "myaise.embed", kid: PUBLISHABLE_KEY_ID,
  origin: "https://app.example.com", team_id: TEAM_ID,
  sub: user.id,
  end_user_ref: user.id,             // required: the end-user identity
  jti: crypto.randomUUID(), exp: now + 300,
  context: { key: account.id, label: account.name },
  credit_allowance: user.plan.aiCredits,   // e.g. 300 per month
  credit_period: "monthly",                // or "total"
  // credit_period_key: user.billingCycleStart  // your own cycle instead of calendar months
};

What a turn costs. An embedded answer costs 30 credits (a live-MCP agent turn), a confirmed customer-app action's verification the same, and a generated walkthrough 8. The turn that reaches the allowance is the last one — spend can exceed the allowance by less than one turn, and remaining is reported as 0. A user whose allowance cannot cover a walkthrough still gets the answer. Your workspace balance is charged as usual after the user's reservation; if the workspace itself is out of credits the reservation is returned.

What the widget shows. The exchange, the chat list and every turn response carry a credits object (null when the user has no cap): allowance, extra_credits (top-ups not yet spent; they carry over), spent, remaining (what is left of the allowance, plus the pot), period, period_key, resets_at and your out_of_credits message + link. The widget renders a "N credits left" pill in the header (amber under 10 %) and, once the user is out, locks the composer and shows your message with a "Get more credits" button pointing at your top-up page — both set in the dashboard. A turn refused for this reason returns 402 with code: "user_allowance_exhausted" and the same credits object; the workspace-level out_of_credits 402 is unchanged and unrelated.

json · 402 user_allowance_exhausted
{
  "error": {
    "type": "user_allowance_exhausted",
    "code": "user_allowance_exhausted",
    "message": "You've used this month's AI credits.",
    "url": "https://app.example.com/billing/credits",
    "credits": {
      "allowance": 300, "extra_credits": 0, "spent": 300, "remaining": 0,
      "period": "monthly", "period_key": "2026-09",
      "resets_at": "2026-10-01T00:00:00Z",
      "out_of_credits": { "message": "You've used this month's AI credits.", "url": "https://app.example.com/billing/credits" }
    }
  }
}

Managing allowances from your backend. GET /v1/embed/end_users/:end_user_ref/credits reads a user's standing; PUT sets credit_allowance, credit_period / credit_period_key, and reset_spent: true zeroes the counter (a refund or a new plan), and add_credits: N adds to a top-up pool that is spent after the period allowance and carries over until used. That last one is the self-serve flow: point your out-of-credits link at your billing page and, once the purchase clears, PUT add_credits from your backend with an Idempotency-Key (your payment id, so a retried request never grants the credits twice) — the widget re-reads the standing when the user comes back to its tab (or taps "I've added credits") and unlocks without a reload; unused top-ups survive the period reset, and the exhausted webhook re-arms. An allowance you set is pinned — it beats the JWT's claims on every later visit until clear_override: true, while the period follows the JWT — and the same controls are on the dashboard under Assist → End users, so your support team can bump one user without code. Both use your secret key. When a user runs out you receive the embed.end_user_credits_exhausted webhook once per period with end_user_ref, the standing and resets_at — the hook for an upsell email or an in-app nudge. See the endpoint reference.

Step transitions #

Per-step transitions are stored as an object on the demo's step:

json · step.transition
{
  "id": "step-2",
  "url_pattern": "/dashboard/billing*",
  "transition": {
    "wipe": "cube",
    "bg_color": "#0c0a20"
  },
  "overlays": [ ... ]
}

The catalog has three kinds, all rendered by the one bundle:

  • Simple — pure CSS. Five ids: none, blur, zoom_css, slide, circle_css.
  • 2D — Three.js fragment shaders on a flat quad. dissolve, glitch, liquid, and dozens more.
  • 3D — true depth + multi-mesh scenes. cube, flip, fold, cosmic and others.

The runtime bundles a handful of scenes (dissolve, cube, flip, glitch); every other Three.js/html-doc transition arrives in the demo's asset pack and is registered at launch — still no eval. Model-backed transitions such as koi ship their html_doc plus a CORS model_url in the pack.

Overlay animations #

Each overlay can pick from 13 enter animations (matching the desktop studio's OVERLAY_ANIMATIONS list). The renderer applies a per-overlay-type default; set animation on the overlay to override:

json · overlay.animation
{
  "id": "hero-modal",
  "type": "modal",
  "title": "Welcome to Acme",
  "animation": "elastic",
  "body": "Three minutes to your first dashboard."
}

The full list of wire ids:

  • fade — quick opacity fade. Default for spotlights, outlines, hotspots, number badges, banners.
  • scale — opacity + scale-in from 0.5 with a tiny overshoot.
  • slide-up / slide-down / slide-left / slide-right — translate from off-axis.
  • rise — translateY + perspective + a blur dropoff. Default for tooltips, callouts, slideouts.
  • fly-in-3d — Z-axis fly-in with rotation + blur. Default for driven_action overlays.
  • portal — perspective scale + rotate-X. Default for modals.
  • elastic — scale-in with a rubber-band overshoot.
  • glitch — clip-path stutter + RGB color shift.
  • zoom-spin — scale from 0 + 180° rotation.
  • parallax — minimal translateY for content-heavy slideouts.

Unknown ids fall back to the per-type default (no error). Class names are .ca-enter-<id> exactly — easy to inspect in devtools.

Demo-level config #

Every demo carries a target_runtime field that the studio's picker filter consults and that gates which assets are embed-eligible (all embed targets load the same myaise.min.js):

  • viewer (default) — the desktop / cloud viewer pool. All transitions available.
  • embed_lite — customer-site embed; simple CSS transitions + overlays.
  • embed_pro — customer-site embed; adds Three.js scenes (bundled + pack-delivered) on top.

Set it on create or patch:

http · POST /v1/projects/:id/demos
POST /v1/projects/proj_<uuid>/demos
Content-Type: application/json

{
  "name": "Acme onboarding",
  "target_runtime": "embed_pro",
  "theme_id": "ocean",
  "steps": [ ... ]
}

MCP authoring tools (demos.create, demos.update) accept the same field. The cloud schema enum-validates the value, so a typo bubbles up as a changeset error instead of silently coercing to viewer.

Drift checks #

Demos break when your UI changes. To keep them healed without ever touching real user data, configure a test account per project in Studio. We run a headless Playwright check against that account every N minutes (max 30) and AI-heal selector drift when confidence ≥ your threshold. Two credential modes:

  • Encrypted at rest — cookies / headers arrive over TLS and are immediately protected with a KMS-backed AES-256-GCM data-key envelope. Cleartext is recovered only for the claimed drift check.
  • Customer-backend JWT callback — we POST your webhook at check time; you return a 5-minute signed token. We never persist credentials.

A callback response JWT must carry aud: "myaise.embed.callback_response". Saving the callback binds it to one active HS256 embed key and returns callback_api_key_id; pass a replacement id explicitly when you rotate that callback key. Ed25519-only keys cannot authenticate MyAISE's request and are therefore not eligible for credential callbacks.

The drift envelope ships inline with every demo fetch — the library exposes it via onDriftDetected:

javascript · drift
MyAISE.launch("demo-uuid", {
  onDriftDetected: (info) => {
    // info.status: ok | unverified | stale | broken | healed
    // info.findings_count, info.checked_at, info.next_check_at
  },
});

Timeline capture & PII #

The library captures overlay clicks only — clicks on the Next/Back/dismiss buttons we render, never anything on your customer's page. There is no document-level listener and we don't read text content, attribute values, or input values from any element. URL paths aren't captured either.

Capture is opt-in:

javascript · init opts
MyAISE.init({
  apiKey: "myaise_pub_xxx",
  apiBase: "https://myaise.com",
  captureTimeline: true,             // default false
  navigationMode: "auto",          // "spa" | "mpa" | "auto"
  variables: { firstName: "Alice" },
  cspNonce: "<your-csp-nonce>",  // for strict CSP
});

Event shape (overlay-scoped):

json · event
{
  "event_type": "overlay_click",
  "ts": 1717108800123,
  "step_index": 2,
  "overlay_id": "ov_abc...",
  "button_id": "1",
  "action": "next_step"
}
Cross-site demos are not supported in embed mode. The library is single-origin only — a step whose url_pattern resolves to a different origin fires onError({ code: "CROSS_ORIGIN_STEP" }). Multi-domain flows stay in viewer mode.
Section 07

Webhooks #

Subscribe to lifecycle events at /dashboard/webhooks or via POST /v1/workspaces/:ws/webhooks. Signing secret is shown once.

Signature verification #

Each delivery carries a header you must verify before trusting the payload:

http · header
X-MyAISE-Signature: t=<unix-seconds>,v1=<hmac-sha256>

Where hmac = HMAC-SHA256(signing_secret, "<t>.<raw_body>"). Every delivery also carries X-MyAISE-Event-Id (the payload's id, use it to de-duplicate retries) and X-MyAISE-Event-Type. Header names are case-insensitive. Verification (Node):

javascript · verify.mjs
import crypto from "node:crypto";

function verifyMyAISESignature(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map(p => p.split("=", 2))
  );

  const t  = parseInt(parts.t, 10);
  const v1 = parts.v1;

  // ±5min clock-skew guard
  if (Math.abs(Date.now() / 1000 - t) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(v1,       "hex")
  );
}
±5 minute clock skew tolerance is on you (the example does it). Reject anything outside that window.

Retry & auto-disable #

Failed deliveries back off exponentially. After 8 consecutive non-2xx responses, the webhook auto-disables and we email the workspace owner.

try 1
1 s
try 2
5 s
try 3
30 s
try 4
5 m
try 5
30 m
try 6
2 h
try 7
8 h
try 8 → off
24 h

Redrive a specific failed delivery via POST /v1/webhook_deliveries/:id/redrive. Full event taxonomy at GET /v1/event_types.

Section 08

Ingest API #

The ingest API lets you push support conversations into MyAISE from anywhere — helpdesk exports, chat platforms, internal tools, whatever you run. Zendesk has a hosted sync; for other sources, use a collector that maps your conversations to this API. Everything you push lands in the team's quarantined mining pipeline — raw conversations never reach your end users; only genericized, human-approved solutions do.

Keys & auth #

Requests authenticate with source-bound myaise_ing_* keys minted on /dashboard/api-keys. Send the secret as Authorization: Bearer. Each key is bound to one team and one source slug you choose at mint time. The slug must be 2-32 chars, use lowercase letters, digits, dash, or underscore, and start with a letter or digit; common ones are telegram | slack | whatsapp | zendesk | other, or anything that names your source. The channel kind always derives from the key's ingest_source, never from the request — a key minted for internal-crm can only feed internal-crm channels. The secret is shown once at mint; revoke it and anything still using it stops immediately.

Authorization: Bearer myaise_ing_<prefix>_<secret> team + source bound

Pushing items #

Base URL: https://myaise.com/v1/ingest. Register each external chat/channel once, maintain its roster, then replay message batches whenever needed.

EndpointWhat it does
GET/channels List the key's channels for its source. Each row includes {id, kind, external_key, display_name, enabled, cursor, participants, last_synced_at}.
POST/channels Register a channel. external_key is required and should be the collector's stable id for the chat/channel. Duplicate registration is idempotent: it returns the existing channel with 200 instead of erroring.
PUT/channels/:id/participants Replace the roster with a map of sender_key => "staff" | "customer" (up to 2000 entries). Server-derived roles win: roster staff becomes agent, roster customer stays customer, out: true is agent, and everything else is kept as unknown for classification. Roster edits retroactively re-stamp not-yet-mined history.
POST/channels/:id/items Idempotent message batch, up to 500 items. Each item needs external_id and body; optional fields are sender, sender_key, parent_external_id, sent_at, out, and attachments. Top-level cursor is persisted only after a successful batch.
Request body
itemsrequired array Up to 500 messages. Re-sending the same external_id upserts, so replaying a batch is safe.
items[].attachments array Up to 5 images per item as {mime, data_b64, width?, height?}. JPEG, PNG, or WebP only; decoded bytes must be ≤5MB. Pixels go to object storage and only refs land in item metadata.
cursor object Persisted on the channel only after the batch succeeds. Response shape: {ok: true, upserted: N, cursor: {...}}.
Example request
curl
curl -X POST https://myaise.com/v1/ingest/channels/ch_123/items \
  -H "Authorization: Bearer myaise_ing_..." \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "external_id": "msg-1001",
        "sender": "Ava",
        "sender_key": "user:42",
        "body": "The billing page is blank",
        "sent_at": "2026-06-12T09:18:00Z"
      },
      {
        "external_id": "msg-1002",
        "sender": "Support",
        "sender_key": "agent:7",
        "out": true,
        "body": "Can you send a screenshot?",
        "attachments": [
          {
            "mime": "image/png",
            "data_b64": "iVBORw0KGgoAAAANSUhEUg...",
            "width": 1280,
            "height": 720
          }
        ]
      }
    ],
    "cursor": { "chat_id": "42", "offset": 1002 }
  }'
Errors worth handling
StatusMeaning
409ingest_pausedThe team paused ingest from the Assist dashboard. Rejected before side effects and the cursor is untouched, so you can replay the same window after resuming. Treat repeated 409s as a stop signal, not a retry.
502Attachment storage blip. Replay the same batch; upserts dedupe.
422Validation failure. The human-readable reason is in error.message.

The successful batch also emits an ingest_batch activity event into the Assist live feed.

Build your own collector #

Zendesk is the one hosted sync we ship. Chat platforms like Telegram don't allow a hosted service to read your conversations, so a collector for them runs on your side — a small always-on program that reads the chats you have access to and replays them into this API. An AI coding agent can build one in a sitting; the prompt below describes the collector we run ourselves (a Telegram tray app), and adapts to Slack, WhatsApp exports, or an internal tool by swapping the source.

prompt · paste into your AI coding agent
Build me an always-on desktop collector (a menu-bar/tray app) that harvests
our Telegram support chats into MyAISE's ingest API.

Requirements:
- I sign into Telegram Web once inside a persistent session partition; the
  session survives restarts and never leaves my machine. MyAISE's servers
  must never contact Telegram directly.
- A small manager window lets me pick which chats to harvest.
- Every 30 minutes, and on a "Harvest now" click, extract new messages from
  the Telegram Web DOM and send them as idempotent batches to
  POST https://myaise.com/v1/ingest/channels/:id/items, after
  registering each chat once via POST /v1/ingest/channels with a stable
  external_key. Each message carries a stable external_id so replays upsert
  instead of duplicating.
- Authenticate with my source-bound ingest key (myaise_ing_..., minted at
  /dashboard/api-keys) sent as Authorization: Bearer. Store the key encrypted
  with the OS keychain; keep other config in a local config file.
- Track every sender seen; let me mark each one Staff or Customer in the
  manager, and sync the roster via PUT /v1/ingest/channels/:id/participants
  as a map of sender_key to "staff" or "customer". Send sender_key on each
  item and out: true for our own outbound messages.
- Persist a per-chat cursor in the batch payload so harvests resume where
  they left off.
- Read the API contract at https://myaise.com/docs/api#ingest-api first.
  Treat 409 ingest_paused as a stop signal (not a retry); replay the same
  batch on 502.
Section 09

AI credits #

Every hosted AI feature — MyAISE answers, walkthrough generation, rule generation, the design kit, self-healing, knowledge mining from your support channels — draws from one per-workspace credit balance. Monthly grants reset each billing period; purchased top-ups never expire. Check the balance before batch work:

http · balance
GET /v1/credits/balance

{
  "data": {
    "balance": { "granted": 640, "purchased": 500, "total": 1140 },
    "grant": { "amount": 1000, "period": "2026-08", "last_grant_period": "2026-08", "next_grant_at": "2026-09-01" },
    "low_balance": false,
    "topup_url": "https://myaise.com/dashboard/billing"
  }
}

Live-session hours #

Live viewer sessions draw on a separate monthly allowance of runtime hours: 10 hours on Solo, 20 hours per seat, pooled, on AI SE. A session is metered from the moment its runtime goes live until it ends. Time past the allowance is charged to the same credit balance at 50 credits per hour, prorated per started minute, when the session ends. Every session object carries the workspace's current-month usage as workspace.session_hours (used, included, period); once the allowance is spent and the balance cannot cover an hour, session creation returns 402 out_of_credits.

Usage audit #

Every charge is attributed to the workspace it was for and the entity that caused it — a viewer session, an assistant conversation, a learner job, a mining cluster, an API key. GET /v1/credits/usage?period=YYYY-MM returns the period's totals, the breakdown by action, a per-day series, and the session-hour meter; GET /v1/credits/transactions returns the ledger itself, newest first, with period, kind, action, source_type, source_id, end_user_ref, status (shadow, pending, settled, refunded), page and per_page (max 100) filters. A workspace key reads its workspace; a personal key reads the user's own account. The same view is on the dashboard at Usage, with CSV export.

json · usage + transactions
GET /v1/credits/usage?period=2026-09

{
  "data": {
    "period": "2026-09",
    "scope": { "type": "team", "id": "…" },
    "credits_used": 1240, "refunded": 16, "net": 1224, "recorded": 0,
    "by_action": [ { "action": "agent_turn", "count": 120, "credits": 960, "refunded": 16, "net": 944 } ],
    "by_day": [ { "date": "2026-09-01", "credits": 0, "refunded": 0 }, … ],
    "session_hours": { "used_seconds": 5400, "included_seconds": 5400, "overage_seconds": 0, "overage_credits": 0, "allowance_seconds": 216000, "remaining_seconds": 210600 }
  }
}

GET /v1/credits/transactions?period=2026-09&source_type=viewer_session&source_id=…

{
  "data": [
    { "id": "…", "inserted_at": "2026-09-07T10:12:03Z", "kind": "deduction", "action": "agent_turn",
      "amount": -8, "balance_after": 2492, "team_id": "…",
      "source": { "type": "viewer_session", "id": "…" },
      "status": "settled", "end_user_ref": null, "acting_user": "se@example.com",
      "trace": { "model": "claude-sonnet-4-6", "prompt_tokens": 40612, "completion_tokens": 88 } }
  ],
  "page": { "page": 1, "per_page": 50, "total": 1, "total_pages": 1 }
}

Out of credits #

When the balance is exhausted, AI endpoints return 402 Payment Required with code: "out_of_credits", the current balance, the next grant date, and a top-up URL. Demos keep playing — serving a demo never spends credits — and non-AI endpoints are unaffected. Handle it by pausing AI calls until the next grant or topping up from the billing dashboard.

json · 402 out_of_credits
{
  "code":  "out_of_credits",
  "error": "Out of AI credits",
  "balance": { "granted": 0, "purchased": 0, "total": 0 },
  "grant": { "amount": 1000, "period": "2026-08", "next_grant_at": "2026-09-01" },
  "topup_url": "https://myaise.com/dashboard/billing"
}

The embedded assistant has a second, per-user 402 — user_allowance_exhausted — when one of your users has spent the allowance you gave them while your workspace still has credits. See per-user credits.

Section 10

Endpoint reference #

Grouped by resource. Click any row to expand auth, request body, an example request, and a sample response. All requests carry Authorization: Bearer <api_key>. Use the narrow content:*, sessions:*, workspace:read, and owner-only webhooks:* scopes shown above; catch-all and plaintext-credential scopes do not exist.

Workspaces 2 endpoints
API keys 1 endpoint
Projects & rules 6 endpoints
Demos · steps · overlays · actions 9 endpoints
Viewer sessions 6 endpoints · the headline integration
MyAISE Q&A 3 endpoints · agent Q&A
Embed · per-user credits 3 endpoints · sell credits to your users, revoke a user
Customer-app MCP 1 endpoint · server-side handoff
Whitelabel verification 2 endpoints
Blocked paths 1 endpoint family
Webhooks 6 endpoints
MCP — JSON-RPC 2.0 1 endpoint · 8 methods · 181 tools across 18 domains
Methods
initializepingtools/listtools/callresources/listresources/readprompts/listprompts/get
Customer-app tools

These tools require a MyAISE conversation with an attached customer-app handoff. Keys with content:read see catalog/context/read/schema; action prepare/confirm/cancel require the content:write scope (in the standard and full presets) and use the permission overview plus confirmation-token flow.

customer_app.action.cancelcustomer_app.action.confirmcustomer_app.action.preparecustomer_app.catalog.getcustomer_app.connection.getcustomer_app.connection.oauth.disconnectcustomer_app.connection.oauth.startcustomer_app.connection.setcustomer_app.connection.static_token.statuscustomer_app.connections.listcustomer_app.context.summarycustomer_app.findings.reviewcustomer_app.probes.getcustomer_app.probes.previewcustomer_app.probes.runcustomer_app.schema.readcustomer_app.tool.read
Section 11

Errors #

Error responses use a Stripe-shape envelope so existing client libraries can be reused.

json · error envelope
{
  "error": {
    "type":       "validation_error",
    "message":    "One or more fields are invalid.",
    "details": [
      { "path": "demos.0.steps", "message": "must be a list" }
    ],
    "request_id": "F7xa..."
  }
}

Status codes #

StatusWhen you'll see it
400Malformed request.
401Missing / invalid / revoked API key.
402Studio (free) — upgrade to Solo or AI SE.
403Workspace mismatch or insufficient scope.
404Resource not in this workspace (we don't distinguish from "doesn't exist anywhere").
409Concurrent-session limit hit, or a locked resource.
422Validation failure — see details[].
429Rate limit. See Retry-After + X-RateLimit-* headers.

Found a typo? Drop us a note at docs@myaise.com · API status: status.myaise.com