Documentation Index
Fetch the complete documentation index at: https://developers.myhero.so/llms.txt
Use this file to discover all available pages before exploring further.
Practical, copy-pasteable examples.
Each demo assumes you have an API key (hero_ak_*) — see Working with the API if you don’t.
export HERO_API_KEY=hero_ak_your_key_here
1. Fetch your default workspace
The simplest possible call — verifies your key is wired up correctly and returns the workspace your account lands on by default.
curl https://app.myhero.so/api/workspace/default \
-H "Authorization: Bearer $HERO_API_KEY"
If you get a 401, the key is wrong or expired.
If you get a 403 with WorkspaceScopeViolation, the key is workspace-scoped and the default workspace isn’t in its scope — pass an explicit workspaceId to a scoped endpoint instead.
2. Create a document programmatically
Two calls: list your projects, then create a document inside one.
List your projects
curl https://app.myhero.so/api/project/all/$WORKSPACE_ID \
-H "Authorization: Bearer $HERO_API_KEY"
Pick the _id of the project you want to write into.Create the document
curl -X POST https://app.myhero.so/api/document \
-H "Authorization: Bearer $HERO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": "<project-id>",
"name": "My new doc",
"type": "FILE"
}'
Returns { "data": { "_id": "...", "name": "My new doc", ... } }. Open it in the HERO app at app.myhero.so/#/<projectId>/<documentId>/editor.
POST /api/ai-agent/stream runs HERO’s general agent and emits Server-Sent Events as it thinks, calls tools, and edits your workspace. You see the model’s reply stream in token-by-token, get notified every time it invokes a tool (create-document, add-row, search-web, and 50+ others), and at the end receive a change summary of every entity it touched — undoable in one shot if the user changes their mind.
The five event types over text/event-stream:
type | data |
|---|
delta | { content: string } — incremental token output |
tool_call | provider-specific payload — agent is invoking a tool |
tool_result | provider-specific payload — tool returned |
done | { finalContent, usage, sessionId, hasUndo, changes? } |
error | { message: string } |
Quick peek with cURL
-N disables curl’s buffering so you see the chunks land in real time.
curl -N -X POST https://app.myhero.so/api/ai-agent/stream \
-H "Authorization: Bearer $HERO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chatMessages": [
{ "type": "user", "text": "Create a document called Sprint 14 retro in my Engineering project, then add an H1 \"What went well\" and three bullet points." }
],
"context": { "workspaceId": "<workspace-id>", "projectId": "<project-id>" }
}'
You’ll see a flurry of data: {"type":"delta",...} lines as the agent narrates what it’s about to do, then data: {"type":"tool_call",...} as it invokes create-document and write-document-markdown, then a final data: {"type":"done",...} with the change summary.
Full Node.js client — parse, react, capture
const res = await fetch("https://app.myhero.so/api/ai-agent/stream", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HERO_API_KEY}`,
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({
chatMessages: [
{ type: "user", text: "Summarise my contracts table and add the summary as a new doc in the Legal project." },
],
context: { workspaceId: process.env.HERO_WORKSPACE_ID },
}),
});
if (!res.ok || !res.body) throw new Error(`Stream failed: ${res.status}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let sessionId: string | undefined;
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE frames are separated by a blank line.
const frames = buffer.split("\n\n");
buffer = frames.pop() ?? "";
for (const frame of frames) {
const line = frame.split("\n").find((l) => l.startsWith("data: "));
if (!line) continue;
const event = JSON.parse(line.slice(6));
switch (event.type) {
case "delta":
process.stdout.write(event.data.content);
break;
case "tool_call":
console.log(`\n→ tool: ${event.data?.name ?? "(unknown)"}`);
break;
case "tool_result":
// optional: log a short preview of the result
break;
case "done":
sessionId = event.data.sessionId;
console.log(`\n\n✓ run complete (session ${sessionId})`);
if (event.data.hasUndo) {
console.log(
` changed ${event.data.changes?.length ?? 0} entities — undoable via POST /api/ai-agent/undo with { sessionId }`,
);
}
break;
case "error":
console.error(`\n✗ ${event.data.message}`);
break;
}
}
}
Undo a run
If the agent did something unwanted, ship the sessionId from the done event back:
curl -X POST https://app.myhero.so/api/ai-agent/undo \
-H "Authorization: Bearer $HERO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sessionId": "<session-id>" }'
Every entity the agent touched (documents, tables, projects) is rolled back to the snapshot it took before the run.
Lighter alternatives
- For a single-prompt rewrite (no tool use, no streaming), use
POST /ai-agent/transform-text with one of the built-in operations (rephrase, fixGrammar, simplify, translate, shorten, summarize, etc.).
- For an agentic run where you don’t need incremental output, use
POST /ai-agent — same body shape, returns the full chatMessages array in one response.
4. Drive HERO from your AI client
The MCP path. After installing for your client, try prompts like these in Cursor, Claude Desktop, or Claude Code — the AI will route them to the right HERO tools automatically.
"List my workspaces and tell me which has the most projects."
"Create a new document called 'Sprint 14 retro' in my Engineering project,
then add an H1 heading 'What went well' and a bulleted list with three items."
"Search my documents for any contracts that mention NDA, and summarise
the renewal dates."
The MCP server exposes 50+ tools across documents, tables, projects, and web search — browse the full Tool catalog.
5. Receive a webhook
Subscribe to events (Webhooks) and accept POSTs at your endpoint.
import express from "express";
const app = express();
app.post("/hero-webhook", express.json(), (req, res) => {
const { event, data } = req.body;
console.log(`[${event}]`, data);
// ... your handling
res.sendStatus(200);
});
app.listen(3000);
Verify the signature header before trusting the payload — see Webhooks for the signing scheme.