For developers · ZoeBot by WISHGOODS

An assistant that understands your product — and can operate it

ZoeBot is not a widget that answers questions. You declare your application’s tools, Zoe pulls them, reasons over your data and takes actions — with an approval layer deciding what runs on its own and what waits.

  • A tool contract you declare
  • Your own persona
  • Approval before action
  • Registered domains only

How this differs from a chatbot

A chatbot is given documents and answers about them. ZoeBot is given your capabilities and works inside your product.

You declare tools, you do not upload documents

Each tool is a name, a description, an input schema and an effect: READ, WRITE or DESTRUCTIVE. What you did not declare does not exist, and cannot be talked into existing.

Your data stays yours

We do not import your database or hold a copy that goes stale. When a fact is needed, Zoe calls your endpoint and it arrives live.

Every action passes a policy layer

Reads run on their own. Anything with a side effect is classified and routed for approval. A tool we could not classify counts as an action — the default is always the strict one.

Your persona, our identity

You set the name, the tone, what it helps with and what to refuse. What you cannot set: making it deny being an AI, or claim to be a person.

Six steps

All of it is HTTP. You can walk the whole path in curl before writing a line inside your product.

1. Developer account

ZoeBot uses your existing Zoe account as the developer account. Sign in and take the access token — it is needed only to manage applications, never to run conversations.

2. Create an application — this is your domain

The application is the tenant: isolation is cut there, not at the developer or the organisation. The domainKey must be lowercase letters and digits, because it becomes part of every tool name.

POST /api/zoebot/applications
curl -X POST https://jarvis-935d.onrender.com/api/zoebot/applications \
  -H "Authorization: Bearer $ZOE_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Delivery",
    "domainKey": "acme",
    "execution": { "mode": "https", "endpointUrl": "https://acme.example.com/zoe/tools" },
    "allowedOrigins": ["https://app.acme.example.com"],
    "manifest": {
      "key": "acme",
      "label": "Acme delivery workspace",
      "entities": [{ "key": "task", "label": "Task" }],
      "tools": [
        {
          "key": "list_tasks",
          "label": "List tasks",
          "description": "Lists tasks, optionally filtered by project or status.",
          "effect": "READ",
          "contextSource": true,
          "inputSchema": {
            "type": "object",
            "properties": { "status": { "type": "string", "enum": ["open", "done"] } }
          }
        },
        {
          "key": "close_task",
          "label": "Close a task",
          "description": "Marks a task done. Cannot be undone.",
          "effect": "DESTRUCTIVE",
          "requiresActorRole": "owner",
          "inputSchema": {
            "type": "object",
            "properties": { "taskId": { "type": "string" } },
            "required": ["taskId"]
          }
        }
      ]
    }
  }'

Tip: start with "execution": { "mode": "sandbox" } and get real answers before writing a line of your own endpoint.

3. The keys — shown once

The response carries three values and will not carry them again. That is not an interface limitation: we do not store them in a form that can produce them. Lose one and you rotate.

The response
{
  "keys": {
    "publishableKey": "zk_pk_live_...",   // identifies, carries no authority
    "secretKey":      "zk_sk_live_...",   // your server only. never a browser
    "signingSecret":  "zk_whsec_..."      // proves a call came from us
  }
}

4. Your endpoint

A tool arrives as one POST. Verify the signature, do the work, answer. A refusal is a legitimate answer — Zoe will explain it to the user rather than insisting.

What we send, and what you return
POST https://acme.example.com/zoe/tools
x-zoe-domain: acme
x-zoe-signature: t=1772000000,v1=<hmac_sha256>

{ "domain": "acme", "capability": "close_task", "effect": "DESTRUCTIVE",
  "args": { "taskId": "t-1" },
  "actor": { "role": "owner", "appUserId": "user-42" },
  "requestedAt": "2026-03-11T09:00:00.000Z" }

// Verify (Node):
const [t, v1] = header.split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', SIGNING_SECRET)
  .update(`${t}.${rawBody}`).digest('hex');
// compare with timingSafeEqual, and reject if t is older than 5 minutes

// Respond:
{ "success": true, "data": { "task": { "id": "t-1", "status": "done" } } }
// or:
{ "success": false, "error": "Task t-1 is locked by an open invoice." }

5. A token for your user — from your server

Your server already knows who is signed in. It asks for a short-lived token for them. The browser never names itself — otherwise any user could read any other’s conversations by editing one string.

POST /api/zoebot/sessions
curl -X POST https://jarvis-935d.onrender.com/api/zoebot/sessions \
  -H "Authorization: Bearer $ZK_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "appUserId": "user-42", "role": "owner" }'

# => { "token": "<short-lived, hand this to the browser>" }

6. The conversation

Now ask. Zoe picks tools from your contract, crosses them, and answers from inside the domain.

POST /api/zoebot/turns
curl -X POST https://jarvis-935d.onrender.com/api/zoebot/turns \
  -H "Authorization: Bearer $END_USER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "text": "What is stuck?" }'

# => { "response": "...", "domainKey": "acme", "sessionId": "..." }

What keeps customers apart

These are not promises. They are properties of the structure.

Identity is derived, never handed over

The principal is a function of (application, user). No caller names it, so no application can name another application’s user. The same person in two applications is two principals.

Registered applications only

A request that cannot be traced to an active registered application is refused before a contract is read. Every authentication failure returns the same answer, so a guessed key learns nothing.

The secret never reaches a browser

zk_sk stays on your server. The browser holds only a short-lived token your server asked for, signed with a separate key — it cannot act as a Zoe session, and a Zoe session cannot act as it.

The address is checked on every call

Your endpoint is re-resolved before each request and internal addresses are refused. A hostname that resolves publicly today can point at a metadata service tomorrow, so a check at registration is not enough.

Text from your site is material, not instruction

Terms, FAQs and copy you supply are read and quoted, and never obeyed. A sentence planted inside them does not become a system instruction.

Your rules are enforced by you

We enforce authority and risk level. "No closing a project with an open invoice" can only be enforced by the system holding the invoices — your endpoint refuses, and Zoe explains why.

Where this stands — honestly

The core is built and measured. What is not is written here rather than hidden, because you would find it on day one anyway.

  • Working: registration, keys, tool contract, persona, approvals, per-application isolation, signed calls to your endpoint.
  • Not yet: a drop-in widget. Today it is HTTP — the visual side is yours.
  • Not yet: usage quotas and billing. There is a per-application rate limit, not an invoice.
  • Not yet: Zoe raising things on her own inside your domain. She answers and acts; she does not yet notice.
  • Early access: talk to us before you build something large on it.

Questions

Do you store my data?

Not your records. We store the tool contract you declared and the conversations produced, isolated per application and per user. The records stay with you and arrive on a live call.

What happens when Zoe wants to do something dangerous?

It depends on the effect you declared. READ runs on its own. WRITE and DESTRUCTIVE go for approval. A tool we could not classify counts as an action — the default is always the strict one.

Can I try it without building an endpoint?

Yes. Register with mode: "sandbox" and you get a demo domain with projects, tasks, blockers and invoices — including cases that need two sources crossed. It is how you see whether the reasoning holds before investing.

How does it know when to use my domain?

When an application is bound to a conversation, the domain is its subject rather than a side channel. A question like "what is stuck?" reaches your tools even though it names none of them.

What does it cost?

No public pricing during early access. We will talk based on scope.

Is this the same as Zoe for Business?

No. Zoe for Business is Zoe running inside an organisation’s perimeter. ZoeBot is for putting an assistant inside a product you are building, for your users.

Want a key?

ZoeBot is in early access. Tell us about your product and what you would want the assistant to do in it.