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.
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.
2 · Smoke test #
curl https://myaise.com/v1/ping \\
-H "Authorization: Bearer myaise_live_…"
3 · List your projects #
curl https://myaise.com/v1/projects \\
-H "Authorization: Bearer myaise_live_…"
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.
Authorization: Bearer myaise_live_<prefix>_<random>
Scopes #
| Scope | What it grants |
|---|---|
workspace:read | Workspace metadata, and reading Assist settings and the customer-app connection over MCP. |
workspace:manage | Change workspace configuration over MCP: Assist settings and the customer-app connection. Implies workspace:read. Never accepts or returns a secret. |
content:read | Read projects, demos, rules, and assets. |
content:write | Create or change ordinary content. |
sessions:read | Read viewer sessions. |
sessions:write | Create and manage viewer sessions. |
sessions:credentials:submit | Submit only client-encrypted viewer credential payloads. |
webhooks:read | Read webhook configuration and delivery status. |
webhooks:manage | Owner-only webhook management and redrive. |
whitelabel_only | Isolated 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.
| Preset | Scopes |
|---|---|
standard | workspace:read content:read content:write sessions:read sessions:write sessions:credentials:submit |
full | Everything in standard, plus webhooks:read webhooks:manage workspace:manage |
read_only | workspace:read content:read sessions:read |
sessions_only | workspace:read sessions:read sessions:write sessions:credentials:submit |
whitelabel_only | whitelabel_only |
Studio (free) #
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.Concepts #
Six resources, in nesting order. Everything else is a child or a side-effect.
steps[].
ID prefixes #
Every ID is prefixed so you can spot the resource by eye — no need to remember which is which.
ws_team workspacews_user_personal workspaceproj_projectdemo_demostep_stepov_overlayact_actionsess_viewer sessionwh_webhookdel_delivery
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.
// 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);
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— asessions_onlykey is exactly that set. - A webhook subscribed to
session.ended(and optionallysession.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.
# 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.
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:
{
"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").)
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
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"),
};
}
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.
{
"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:
{
"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_secondsis clamped to the workspace maximum, so readexpires_atandexpires_in_secondsback from the response instead of assuming. When the clock runs out the session ends withend_reason: "ttl_expired". - Credit budget. The AI SE's answers, walkthrough generation and agent turns each cost credits (see AI credits). Without
credit_budgetthe session takes the workspace default, or 5000 credits when the workspace sets none (lowered to the workspace maximum if that is smaller); readcredit_budgetback from the response. The turn that reaches the budget is the last one, and the session ends withend_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:
{
"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:
{
"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:
| Reason | What happened |
|---|---|
ttl_expired | The session hit its time limit. |
credit_budget_exhausted | The AI credit budget ran out. A session.budget_exhausted event fires just before session.ended. |
runtime_incompatible | The session never started: the viewer runtime could not apply its settings (such as its navigation allowlist) or stopped before it was ready — rare. |
revoked | You deleted the session. |
billing_hold | The 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_completed | The prospect reached the end of the demo. |
On receipt:
- Verify the signature (see Signature verification) before trusting anything in the body.
- Be idempotent on the event
id— retries and replays are expected. - Revoke the MCP grant.
- 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_urlis 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.metadatato 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.
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.
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
/mcpin Claude Code and choose it to sign in.
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.
/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:
{
"mcpServers": {
"myaise": {
"transport": "http",
"url": "https://myaise.com/mcp",
"headers": {
"Authorization": "Bearer myaise_live_..."
}
}
}
}
claude mcp add --transport http myaise https://myaise.com/mcp --header "Authorization: Bearer myaise_live_..."
2 · Ask the model
design_demo prompt with my pricing URL, then create the demo."3 · What it does, step-by-step
tools/list— sees the full tool surface.- The
design_demoprompt — gets a structured prompt template covering URL flow + overlay copy guidelines. projects.create, thendemos.createwith nested steps + overlays.- For anchored overlays, attaches
target_context(+ an optional baselinescreenshot) so the demo can self-heal — see “Drift protection” below. sessions.createto actually launch the demo.
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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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
| Tool | Scope | What 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. |
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:
- Set
auto_healing_enabled: trueon the demo (demos.create/demos.update). - Give every anchored overlay (one with a
target_selector) atarget_contextonoverlays.create— capture it from the live page you authored against:
"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.
sessions.create), so it never needs the agent's cookies.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.
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.
<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.
<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:
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:
| Claim | Meaning |
|---|---|
credit_allowance | Whole 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_key | Your 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.
// 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.
{
"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:
{
"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,cosmicand 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:
{
"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:
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:
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:
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):
{
"event_type": "overlay_click",
"ts": 1717108800123,
"step_index": 2,
"overlay_id": "ov_abc...",
"button_id": "1",
"action": "next_step"
}
url_pattern resolves to a different origin fires onError({ code: "CROSS_ORIGIN_STEP" }). Multi-domain flows stay in viewer mode.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:
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):
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")
);
}
Retry & auto-disable #
Failed deliveries back off exponentially. After 8 consecutive non-2xx responses, the webhook auto-disables and we email the workspace owner.
Redrive a specific failed delivery via POST /v1/webhook_deliveries/:id/redrive. Full event taxonomy at GET /v1/event_types.
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.
| Endpoint | What 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. |
external_id upserts, so replaying a batch is safe.
{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.
{ok: true, upserted: N, cursor: {...}}.
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 }
}'
| Status | Meaning |
|---|---|
409ingest_paused | The 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. |
| 502 | Attachment storage blip. Replay the same batch; upserts dedupe. |
| 422 | Validation 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.
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.
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:
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.
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.
{
"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.
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.
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/workspaces \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "ws_3f1c8e2a-...",
"type": "team",
"name": "Acme Inc",
"slug": "acme",
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-20T09:30:00Z"
}
]
}
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/workspaces/ws_3f1c8e2a-... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "ws_3f1c8e2a-...",
"type": "team",
"name": "Acme Inc",
"slug": "acme",
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-20T09:30:00Z"
}
}
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/workspaces/ws_3f1c.../api_keys \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "key_a1b2c3d4-...",
"name": "CI key",
"prefix": "cap_live_",
"last4": "9f3a",
"scopes": ["workspace:read", "content:read"],
"rate_limit_per_minute": 120,
"last_used_at": "2026-05-26T11:00:00Z",
"revoked_at": null,
"created_at": "2026-04-10T08:00:00Z"
}
]
}
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/projects \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "proj_e72b1a4c-...",
"name": "Acme Demo",
"description": "",
"base_url": "https://app.acme.com",
"version": 7,
"rule_count": 4,
"demo_count": 2,
"created_at": "2026-05-01T12:00:00Z",
"updated_at": "2026-05-20T09:30:00Z"
}
]
}
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/projects/proj_e72b... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "proj_e72b...", "name": "Acme Demo", "base_url": "https://app.acme.com",
"version": 7, "rule_count": 4, "demo_count": 2,
"rules": [{ "id": "8c1f...", "match": "/api/users", "type": "json", "index": 0 }],
"demos": [{ "id": "demo_b3d2...", "name": "Onboarding", "steps": [] }]
}
}
https://app.acme.com.
"".
[].
[].
curl -X POST https://myaise.com/v1/projects \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Acme Demo","base_url":"https://app.acme.com"}'
{
"data": {
"id": "proj_e72b...", "name": "Acme Demo", "base_url": "https://app.acme.com",
"version": 1, "rule_count": 0, "demo_count": 0, "rules": [], "demos": []
}
}
curl -X PATCH https://myaise.com/v1/projects/proj_e72b... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"version":7,"name":"Acme Demo 2026"}'
{ "data": { "id": "proj_e72b...", "name": "Acme Demo 2026", "version": 8 } }
curl -X DELETE https://myaise.com/v1/projects/proj_e72b... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"version":8}'
204 No Content
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/projects/proj_e72b.../rules \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{ "id": "8c1f2a...", "match": "/api/users", "type": "json",
"rewrite": "$.data[*].plan", "enabled": true, "index": 0 }
]
}
json, html, block, etc.
curl -X POST https://myaise.com/v1/projects/proj_e72b.../rules \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"match":"/api/users","type":"json","rewrite":"$.plan","body":"enterprise"}'
{
"data": {
"rule": { "id": "8c1f2a...", "match": "/api/users", "type": "json" },
"rules": [{ "id": "8c1f2a...", "index": 0 }]
}
}
curl -X PATCH https://myaise.com/v1/projects/proj_e72b.../rules/8c1f2a... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
{ "data": { "rule": { "id": "8c1f2a...", "enabled": false }, "rules": [ ... ] } }
curl -X DELETE https://myaise.com/v1/projects/proj_e72b.../rules/8c1f2a... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/projects/proj_e72b.../demos \
-H "Authorization: Bearer $CAP_KEY"
{ "data": [ { "id": "demo_b3d2...", "name": "Onboarding", "theme_id": "glass", "steps": [] } ] }
"glass".
http:// or https:// URL where the demo sends the prospect when finished.
false.
curl -X POST https://myaise.com/v1/projects/proj_e72b.../demos \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Onboarding","theme_id":"glass"}'
{
"data": {
"id": "demo_b3d2...", "name": "Onboarding", "theme_id": "glass",
"exit_url": null, "auto_healing_enabled": false, "steps": [], "pre_demo_actions": []
}
}
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/demos/demo_b3d2... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "demo_b3d2...", "name": "Onboarding", "theme_id": "glass",
"color_scheme": { "primary": "#7c3aed", "accent": "#a855f7" },
"steps": [{ "id": "step_91fe...", "name": "Dashboard", "url_pattern": "/dashboard*",
"overlays": [{ "id": "ov_4c8a...", "type": "tooltip" }] }],
"pre_demo_actions": []
}
}
curl -X PATCH https://myaise.com/v1/demos/demo_b3d2... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"exit_url":"https://acme.com/thanks"}'
{ "data": { "id": "demo_b3d2...", "exit_url": "https://acme.com/thanks" } }
curl -X DELETE https://myaise.com/v1/demos/demo_b3d2... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: content:write
"<name> (copy)".
curl -X POST https://myaise.com/v1/demos/demo_b3d2.../clone \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Onboarding — EU"}'
{ "data": { "id": "demo_9a77...", "name": "Onboarding — EU", "steps": [ ... ] } }
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/demos/demo_b3d2.../steps \
-H "Authorization: Bearer $CAP_KEY"
{ "data": [ { "id": "step_91fe...", "name": "Dashboard", "url_pattern": "/dashboard*", "overlays": [] } ] }
/dashboard*.
{}.
curl -X POST https://myaise.com/v1/demos/demo_b3d2.../steps \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Dashboard","url_pattern":"/dashboard*"}'
{ "data": { "id": "step_91fe...", "name": "Dashboard", "url_pattern": "/dashboard*", "overlays": [], "entry_actions": [] } }
curl -X PATCH https://myaise.com/v1/demos/demo_b3d2.../steps/step_91fe... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"url_pattern":"/reports*"}'
{ "data": { "id": "step_91fe...", "url_pattern": "/reports*" } }
curl -X DELETE https://myaise.com/v1/demos/demo_b3d2.../steps/step_91fe... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/steps/step_91fe.../overlays \
-H "Authorization: Bearer $CAP_KEY"
{ "data": [ { "id": "ov_4c8a...", "type": "tooltip", "title": "Click here", "target_selector": "#new-report" } ] }
tooltip, modal, banner, spotlight, outline, etc.
top/bottom/left/right.
[{label, action}].
slide-up.
video_bubble, a safe absolute http:// or https:// embed URL.
selectorCandidates plus role / accessibleName / textContent / nearbyText / rect. Lets MyAISE re-locate this element after the customer's site changes. See “Drift protection” below.
curl -X POST https://myaise.com/v1/steps/step_91fe.../overlays \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"tooltip","title":"Click here","target_selector":"#new-report"}'
{ "data": { "id": "ov_4c8a...", "type": "tooltip", "title": "Click here", "target_selector": "#new-report" } }
curl -X PATCH https://myaise.com/v1/steps/step_91fe.../overlays/ov_4c8a... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"animation":"slide-up"}'
{ "data": { "id": "ov_4c8a...", "animation": "slide-up" } }
curl -X DELETE https://myaise.com/v1/steps/step_91fe.../overlays/ov_4c8a... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/demos/demo_b3d2.../pre_demo_actions \
-H "Authorization: Bearer $CAP_KEY"
{ "data": [ { "id": "act_2f9b...", "type": "fill", "target": "#email", "value": "jane@acme.com" } ] }
click, fill, wait, navigate, etc.
curl -X POST https://myaise.com/v1/demos/demo_b3d2.../pre_demo_actions \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"fill","target":"#email","value":"jane@acme.com"}'
{ "data": { "id": "act_2f9b...", "type": "fill", "target": "#email", "value": "jane@acme.com" } }
curl -X PATCH https://myaise.com/v1/demos/demo_b3d2.../pre_demo_actions/act_2f9b... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"delay_ms":500}'
{ "data": { "id": "act_2f9b...", "delay_ms": 500 } }
curl -X DELETE https://myaise.com/v1/demos/demo_b3d2.../pre_demo_actions/act_2f9b... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/steps/step_91fe.../entry_actions \
-H "Authorization: Bearer $CAP_KEY"
{ "data": [ { "id": "act_7c1a...", "type": "click", "target": ".accept-cookies" } ] }
click, fill, wait, etc.
curl -X POST https://myaise.com/v1/steps/step_91fe.../entry_actions \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"click","target":".accept-cookies"}'
{ "data": { "id": "act_7c1a...", "type": "click", "target": ".accept-cookies" } }
curl -X PATCH https://myaise.com/v1/steps/step_91fe.../entry_actions/act_7c1a... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"delay_ms":300}'
{ "data": { "id": "act_7c1a...", "delay_ms": 300 } }
curl -X DELETE https://myaise.com/v1/steps/step_91fe.../entry_actions/act_7c1a... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/demos/demo_b3d2.../assets \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "asset_5d1c...", "demo_id": "demo_b3d2...", "project_id": "proj_e72b...",
"content_hash": "sha256:abc123...", "kind": "baseline_screenshot",
"storage_url": "https://cdn.myaise.com/assets/abc123.png",
"width": 1280, "height": 800, "byte_size": 48213, "mime_type": "image/png",
"created_at": "2026-05-20T09:30:00Z"
}
]
}
curl https://myaise.com/v1/demos/demo_b3d2.../assets/abc123... \
-H "Authorization: Bearer $CAP_KEY"
{ "data": { "id": "asset_5d1c...", "content_hash": "sha256:abc123...", "storage_url": "https://cdn.myaise.com/assets/abc123.png" } }
Authorization: Bearer <api_key>
scope: sessions:read / sessions:write
curl https://myaise.com/v1/viewer/public_key \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0B...\n-----END PUBLIC KEY-----\n",
"key_type": "RSA",
"algorithm": {
"key_wrap": "RSA-OAEP", "hash": "SHA-256",
"mgf1_hash": "SHA-256", "content": "AES-256-GCM"
},
"envelope_fields": ["encrypted_key", "iv", "ciphertext", "tag"],
"iv_bytes": 12,
"tag_bytes": 16,
"encoding": "base64",
"plaintext_schema": { "base_url": "https://…", "cookies": [], "headers": {} }
}
}
Authorization: Bearer <api_key>
scope: sessions:write + sessions:credentials:submit
{ name, email, company, metadata } — shown in the dashboard.
{ encrypted_key, iv, ciphertext, tag }. Plaintext credentials are rejected.
expires_in), effective range 60..604800 — smaller values are raised to 60, larger ones clamped to the workspace maximum, so read expires_at / expires_in_seconds back from the response. Omitted → the workspace default. Not a positive integer → 400 invalid_expires_in.
credit_budget_exceeds_policy; not a positive integer → 400 invalid_credit_budget.
*.example.com matches subdomains; no IP literals). Omitted → the workspace's allowed-hosts policy (off, or the project base_url host + its subdomains + any extras; a project whose base_url is * uses its demos' hosts instead). Invalid → 422 invalid_allowed_hosts; no host to allow → 422 navigation_base_required.
curl -X POST https://myaise.com/v1/demos/demo_b3d2.../sessions \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"prospect": { "name": "Jane", "email": "jane@acme.com" },
"encrypted_payload": { "encrypted_key": "...", "iv": "...", "ciphertext": "...", "tag": "..." },
"expires_in_seconds": 1200,
"credit_budget": 100,
"allowed_hosts": ["app.example.com", "*.app.example.com"]
}'
{
"data": {
"id": "sess_7a2e...",
"token": "v1_AbCd...",
"status": "pending",
"viewer_url": "https://joinlivedemo.com/view/v1_AbCd...",
"expires_at": "2026-05-28T14:00: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..." }
}
}
Authorization: Bearer <api_key>
scope: sessions:write + sessions:credentials:submit
{ name, email, company, metadata } — shown in the dashboard.
{ encrypted_key, iv, ciphertext, tag }.
curl -X POST https://myaise.com/v1/projects/proj_e72b.../assistant_sessions \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"prospect":{"name":"Jane"},"encrypted_payload":{"encrypted_key":"...","iv":"...","ciphertext":"...","tag":"..."}}'
{
"data": {
"id": "sess_7a2e...", "token": "v1_AbCd...", "status": "pending",
"viewer_url": "https://joinlivedemo.com/view/v1_AbCd...",
"expires_at": "2026-05-28T14:00:00Z", "project_id": "proj_e72b...",
"workspace": { "type": "team", "id": "ws_3f1c..." }
}
}
Authorization: Bearer <api_key>
read · any key
curl "https://myaise.com/v1/workspaces/ws_3f1c.../sessions?status=active" \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "sess_7a2e...", "status": "active", "demo_id": "demo_b3d2...",
"token_prefix": "v1_AbCd", "share_link_available": false,
"workspace": { "type": "team", "id": "ws_3f1c..." },
"prospect": { "name": "Jane", "email": "jane@acme.com" },
"demo_status": "in_progress", "demo_step_index": 2,
"machine_id": "d8...", "warming_state": "ready",
"expires_at": "2026-05-28T14:00:00Z", "created_at": "2026-05-28T13:00:00Z"
}
]
}
Authorization: Bearer <api_key>
scope: sessions:read / sessions:write
curl https://myaise.com/v1/demo_sessions/sess_7a2e... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "sess_7a2e...", "status": "active",
"token_prefix": "v1_AbCd", "share_link_available": false,
"workspace": { "type": "team", "id": "ws_3f1c..." },
"project_id": "proj_e72b...", "demo_id": "demo_b3d2...",
"prospect": { "name": "Jane", "email": "jane@acme.com", "company": "Acme", "metadata": {} },
"current_url": "https://app.acme.com/dashboard",
"demo_status": "in_progress", "demo_step_index": 2,
"demo_started_at": "2026-05-28T13:01:00Z", "demo_completed_at": null,
"credit_budget": 100, "credits_spent": 34,
"allowed_hosts": ["app.example.com", "*.app.example.com"],
"end_reason": null, "ended_at": null,
"machine_id": "d8...", "warming_state": "ready",
"warming_completed": 4, "warming_total": 4,
"expires_at": "2026-05-28T14:00:00Z"
}
}
curl -X DELETE https://myaise.com/v1/demo_sessions/sess_7a2e... \
-H "Authorization: Bearer $CAP_KEY"
{ "ok": true }
Authorization: Bearer <api_key>
scope: content:read
curl -X POST https://myaise.com/v1/assist/messages \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Which object backs the account settings form?",
"origin": "customer-app-mcp-planner"
}'
{
"data": {
"conversation_id": "9f4d2c1b-...",
"conversation_created": true,
"message": {
"id": "3e533c0a-...",
"role": "assistant",
"content": "Open Campaigns and verify the routing target is active."
},
"answer_gap": false,
"suggested_walkthroughs": [],
"wants_walkthrough_generation": false,
"retrieval_refs": {
"solution_ids": ["8c1a..."],
"chunk_ids": [],
"groundtruth_ids": []
}
}
}
Authorization: Bearer <api_key>
scope: content:read
curl -X POST https://myaise.com/v1/assist/conversations \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"origin":"customer-app-mcp-planner"}'
{
"data": {
"conversation_id": "9f4d2c1b-...",
"status": "active",
"origin": "customer-app-mcp-planner"
}
}
Authorization: Bearer <api_key>
scope: content:read
curl -X POST https://myaise.com/v1/assist/conversations/9f4d2c1b-.../messages \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"What should I check first?"}'
{
"data": {
"conversation_id": "9f4d2c1b-...",
"conversation_created": false,
"message": {
"id": "3e533c0a-...",
"role": "assistant",
"content": "Open Campaigns and verify the routing target is active."
},
"answer_gap": false,
"suggested_walkthroughs": [],
"wants_walkthrough_generation": false,
"retrieval_refs": {
"solution_ids": ["8c1a..."],
"chunk_ids": [],
"groundtruth_ids": []
}
}
}
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/embed/end_users/user_8842/credits \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"end_user_ref": "user_8842",
"credit_allowance": 300,
"allowance_source": "claim",
"credit_period": "monthly",
"period_key": "2026-09",
"credits_spent": 90,
"credits_remaining": 210,
"resets_at": "2026-10-01T00:00:00Z",
"out_of_credits": false,
"last_seen_at": "2026-09-04T18:20:11Z",
"out_of_credits_message": "You've used this month's AI credits.",
"out_of_credits_url": "https://app.example.com/billing/credits"
}
}
Authorization: Bearer <api_key>
scope: write
curl -X PUT https://myaise.com/v1/embed/end_users/user_8842/credits \
-H "Authorization: Bearer $CAP_KEY" \
-H 'Content-Type: application/json' \
-d '{"credit_allowance": 500, "credit_period": "monthly", "reset_spent": true}'
# a purchase: send the payment id as the Idempotency-Key so a retry never grants twice
curl -X PUT https://myaise.com/v1/embed/end_users/user_8842/credits \
-H "Authorization: Bearer $CAP_KEY" \
-H 'Idempotency-Key: pay_01J9X…' -H 'Content-Type: application/json' \
-d '{"add_credits": 50}'
{
"data": {
"end_user_ref": "user_8842",
"credit_allowance": 500,
"allowance_source": "api",
"credit_period": "monthly",
"period_key": "2026-09",
"credits_spent": 0,
"extra_credits": 0,
"credits_remaining": 500,
"resets_at": "2026-10-01T00:00:00Z",
"out_of_credits": false,
"last_seen_at": "2026-09-04T18:20:11Z",
"out_of_credits_message": "You've used this month's AI credits.",
"out_of_credits_url": "https://app.example.com/billing/credits"
}
}
Authorization: Bearer <api_key>
scope: write
curl -X POST https://myaise.com/v1/embed/end_users/user_8842/revoke \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"end_user_ref": "user_8842",
"revoked_at": "2026-10-16T14:02:11.418220Z",
"sessions_ended": 2,
"connections_removed": 1
}
}
Authorization: Bearer <api_key>
scope: content:write, or any read scope for read-only catalogs
sess_… id of a live viewer session created by this workspace. Binds the grant to that prospect's session so the AI SE acts as that demo user — see the website integration guide. 404 session_not_found if it is not this workspace's session; 409 session_not_live if it has ended or expired. With it, sessions:write is sufficient (a sessions_only key works); without it content:write is required.
curl -X POST https://myaise.com/v1/customer_app/mcp_handoffs \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"endpoint_url": "https://mcp.customer-app.example/rpc",
"access_token": "customer_app_mcp_token_...",
"expires_at": "2026-06-26T16:00:00Z",
"app_ref": "supportdesk",
"account_id": "acct_123",
"scopes": ["read_only"],
"tool_catalog": [{"name":"records.get","readOnlyHint":true}],
"myaise_conversation_id": "9f4d2c1b-...",
"viewer_session_id": "sess_7a2e..."
}'
{
"data": {
"handoff_ref": "customer_app_handoff_abc123",
"expires_at": "2026-06-26T16:00:00Z",
"viewer_session_id": "sess_7a2e...",
"connected": true,
"credential_handling": "server-side only",
"customer_app": {
"app_ref": "supportdesk",
"account_id": "acct_123",
"tool_count": 1,
"read_only": true
}
}
}
Authorization: Bearer <api_key>
scope: whitelabel_only
curl -X POST https://myaise.com/v1/whitelabel/theme_verifications \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"css": ":root { --brand: #4f46e5; }",
"preview_html": "<!doctype html><html><body>Preview</body></html>",
"external_ref": "theme_123"
}'
{
"data": {
"id": "6d8d4f2a-...",
"external_ref": "theme_123",
"status": "queued",
"findings": [],
"cleared_css": null
}
}
curl https://myaise.com/v1/whitelabel/theme_verifications/6d8d4f2a-... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "6d8d4f2a-...",
"external_ref": "theme_123",
"status": "approved",
"findings": [],
"cleared_css": ":root { --brand: #4f46e5; }"
}
}
Authorization: Bearer <api_key>
scope: whitelabel_only
curl -X POST https://myaise.com/v1/whitelabel/theme_verifications \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"css": ":root { --brand: #4f46e5; }",
"preview_html": "<!doctype html><html><body>Preview</body></html>",
"external_ref": "theme_123"
}'
{
"data": {
"id": "6d8d4f2a-...",
"external_ref": "theme_123",
"status": "queued",
"findings": [],
"cleared_css": null
}
}
curl https://myaise.com/v1/whitelabel/theme_verifications/6d8d4f2a-... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "6d8d4f2a-...",
"external_ref": "theme_123",
"status": "approved",
"findings": [],
"cleared_css": ":root { --brand: #4f46e5; }"
}
}
Authorization: Bearer <api_key>
scope: content:write
curl https://myaise.com/v1/workspaces/ws_3f1c.../blocked_paths \
-H "Authorization: Bearer $CAP_KEY"
{ "data": [ { "id": "bp_77aa...", "pattern": "/admin/*", "status_code": 403, "enabled": true, "locked": false } ] }
/admin/*.
curl -X POST https://myaise.com/v1/workspaces/ws_3f1c.../blocked_paths \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"pattern":"/admin/*","status_code":403}'
{ "data": { "id": "bp_77aa...", "pattern": "/admin/*", "status_code": 403, "enabled": true, "locked": false } }
curl -X PATCH https://myaise.com/v1/workspaces/ws_3f1c.../blocked_paths/bp_77aa... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'
{ "data": { "id": "bp_77aa...", "enabled": false } }
curl -X DELETE https://myaise.com/v1/workspaces/ws_3f1c.../blocked_paths/bp_77aa... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: webhooks:manage
curl https://myaise.com/v1/workspaces/ws_3f1c.../webhooks \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "wh_9c3d...", "url": "https://acme.com/hooks", "active": true,
"events": ["session.created", "session.demo_completed"],
"project_ids": ["proj_e72b..."], "demo_ids": [],
"signing_secret_prefix": "whsec_ab12cd", "consecutive_failures": 0,
"last_success_at": "2026-05-27T10:00:00Z", "last_failure_at": null
}
]
}
curl -X POST https://myaise.com/v1/workspaces/ws_3f1c.../webhooks \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://acme.com/hooks","events":["session.created"]}'
{
"data": {
"id": "wh_9c3d...", "url": "https://acme.com/hooks", "active": true,
"events": ["session.created"],
"signing_secret": "whsec_ab12cd...FULL_SECRET_SHOWN_ONCE",
"signing_secret_prefix": "whsec_ab12cd"
}
}
curl -X PATCH https://myaise.com/v1/workspaces/ws_3f1c.../webhooks/wh_9c3d... \
-H "Authorization: Bearer $CAP_KEY" \
-H "Content-Type: application/json" \
-d '{"active":false}'
{ "data": { "id": "wh_9c3d...", "active": false } }
curl -X DELETE https://myaise.com/v1/workspaces/ws_3f1c.../webhooks/wh_9c3d... \
-H "Authorization: Bearer $CAP_KEY"
204 No Content
Authorization: Bearer <api_key>
scope: webhooks:manage
curl -X POST https://myaise.com/v1/workspaces/ws_3f1c.../webhooks/wh_9c3d.../test \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "del_b81a...", "webhook_id": "wh_9c3d...", "event_type": "session.created",
"status": "delivered", "attempt": 1, "response_status": 200, "created_at": "..."
}
}
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/workspaces/ws_3f1c.../webhook_deliveries \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{
"id": "del_b81a...", "webhook_id": "wh_9c3d...", "event_type": "session.demo_completed",
"status": "delivered", "attempt": 1, "response_status": 200,
"delivered_at": "2026-05-27T10:00:01Z", "next_attempt_at": null
}
]
}
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/webhook_deliveries/del_b81a... \
-H "Authorization: Bearer $CAP_KEY"
{
"data": {
"id": "del_b81a...", "webhook_id": "wh_9c3d...", "event_type": "session.demo_completed",
"status": "delivered", "attempt": 1, "response_status": 200,
"payload": { "type": "session.demo_completed", "data": { "session_id": "sess_7a2e..." } },
"response_body": null, "error_message": null,
"signature": "sha256=9f86d0818..."
}
}
Authorization: Bearer <api_key>
scope: webhooks:manage
curl -X POST https://myaise.com/v1/webhook_deliveries/del_b81a.../redrive \
-H "Authorization: Bearer $CAP_KEY"
{ "data": { "id": "del_b81a...", "status": "pending", "attempt": 2, "next_attempt_at": "2026-05-28T13:05:00Z" } }
Authorization: Bearer <api_key>
read · any key
curl https://myaise.com/v1/event_types \
-H "Authorization: Bearer $CAP_KEY"
{
"data": [
{ "type": "session.created", "category": "session",
"description": "A new viewer session was created via the API." },
{ "type": "session.demo_completed", "category": "session",
"description": "The prospect reached the end of the demo." },
{ "type": "demo.drift_detected", "category": "demo_authoring",
"description": "The validator found drift in this demo." }
]
}
initializepingtools/listtools/callresources/listresources/readprompts/listprompts/get
Every tools/call name, grouped the same way as the full reference table below (name, scope, annotations, description).
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
Errors #
Error responses use a Stripe-shape envelope so existing client libraries can be reused.
{
"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 #
| Status | When you'll see it |
|---|---|
| 400 | Malformed request. |
| 401 | Missing / invalid / revoked API key. |
| 402 | Studio (free) — upgrade to Solo or AI SE. |
| 403 | Workspace mismatch or insufficient scope. |
| 404 | Resource not in this workspace (we don't distinguish from "doesn't exist anywhere"). |
| 409 | Concurrent-session limit hit, or a locked resource. |
| 422 | Validation failure — see details[]. |
| 429 | Rate limit. See Retry-After + X-RateLimit-* headers. |
Found a typo? Drop us a note at docs@myaise.com · API status: status.myaise.com