This page takes you from nothing to a page created by a script. It uses the workspace address and a token, so you need an account and a workspace first.
1. Get your address and a token
Open Settings β MCP & API in your workspace.
Copy the MCP server address. It ends with your workspace's name, for example
https://app.thenotemaker.com/api/mcp/your-workspace.Under API tokens for this workspace, type a name and click Create token. Copy the token now: it starts with
nm_and is shown only once.
Put both in environment variables so the examples below work as they are:
export NOTEMAKER_URL="https://app.thenotemaker.com/api/mcp/your-workspace"
export NOTEMAKER_TOKEN="nm_YOUR_TOKEN"
Treat a token like a password. Never put a real one in a page, a script you share, or a public repository.
2. Check that it works
A call is a POST of one JSON-RPC message. The Accept header must list both types shown.
curl -s -X POST "$NOTEMAKER_URL" \
-H "Authorization: Bearer $NOTEMAKER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
The answer names the fourteen tools:
{"result":{"tools":[{"name":"workspace", "...": "..."}, {"name":"pages", "...": "..."}, "..."]},"jsonrpc":"2.0","id":1}
Every call is self-contained: there is no session to open and nothing to close.
3. Read your workspace
curl -s -X POST "$NOTEMAKER_URL" \
-H "Authorization: Bearer $NOTEMAKER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"workspace","arguments":{"action":"get"}}}'
The text in result.content[0].text reads like this (shortened):
My workspace
- slug: your-workspace
- your role: OWNER
- pages: 5
## Collections (1)
- Handbook (id: cmvkβ¦, private, 2 pages)
## Forms (1)
- Contact us (id: cmvkβ¦, 0 responses, https://app.thenotemaker.com/f/contact-us-t4948j4u)
4. Create a page
Pass the tool's name and its arguments. content_markdown is ordinary Markdown. The Idempotency-Key header makes the call safe to repeat: send the same key again after a timeout and you get the first answer back instead of a second page.
curl -s -X POST "$NOTEMAKER_URL" \
-H "Authorization: Bearer $NOTEMAKER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"pages_write","arguments":{"action":"create","title":"Hello from the API","content_markdown":"Created with curl."}}}'
Created page "Hello from the API" (id: cmvp00soi0000qe7dmm9pb073, private)
https://app.thenotemaker.com/your-workspace/hello-from-the-api-714e0ca3b5
The page is private to you until you share it. The same answer also comes back as data in result.structuredContent.page (id, title, url, visibility).
5. The same thing in JavaScript
Node 20 or newer, no packages needed. Save it as hello.mjs and run node hello.mjs.
const URL_ = process.env.NOTEMAKER_URL;
const TOKEN = process.env.NOTEMAKER_TOKEN;
let id = 0;
async function tool(name, args) {
const res = await fetch(URL_, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method: "tools/call", params: { name, arguments: args } }),
});
const body = await res.json();
if (body.error) throw new Error(body.error.message); // sign-in problems
if (body.result.isError) throw new Error(body.result.content[0].text); // a refused call
return body.result;
}
const created = await tool("pages_write", { action: "create", title: "Hello from JavaScript", content_markdown: "Created with fetch." });
console.log(created.content[0].text);
const pageId = created.structuredContent.page.id;
const found = await tool("pages", { action: "search", query: "Hello" });
console.log(found.content[0].text);
await tool("pages_write", { action: "delete", page_id: pageId }); // moves it to the trash
6. And in Python
Install the one dependency with pip install requests. Save the script as hello.py and run python hello.py.
import os, requests
URL = os.environ["NOTEMAKER_URL"]
TOKEN = os.environ["NOTEMAKER_TOKEN"]
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
_id = 0
def tool(name, **arguments):
global _id
_id += 1
body = {"jsonrpc": "2.0", "id": _id, "method": "tools/call", "params": {"name": name, "arguments": arguments}}
data = requests.post(URL, headers=HEADERS, json=body, timeout=30).json()
if "error" in data:
raise RuntimeError(data["error"]["message"]) # sign-in problems
result = data["result"]
if result.get("isError"):
raise RuntimeError(result["content"][0]["text"]) # a refused call
return result
created = tool("pages_write", action="create", title="Hello from Python", content_markdown="Created with requests.")
print(created["content"][0]["text"])
page_id = created["structuredContent"]["page"]["id"]
found = tool("pages", action="search", query="Hello")
print(found["content"][0]["text"])
tool("pages_write", action="delete", page_id=page_id) # moves it to the trash
Both scripts print:
Created page "Hello from JavaScript" (id: cmvp01cis0003qe7d7vp21rqq, private)
https://app.thenotemaker.com/your-workspace/hello-from-javascript-b6bcbe814b
- Hello from JavaScript (id: cmvp01cis0003qe7d7vp21rqq)
Created with fetch.
7. If a call fails
A missing or wrong token answers HTTP 401 with a WWW-Authenticate header. A call the tool refuses (a locked page, a value that breaks a field's rule) answers HTTP 200 with result.isError true and the reason in plain words, and nothing is changed. Public form submissions answer errors as application/problem+json. More in the Agents, trash and errors examples.
Next
Try it in Postman
Prefer a point-and-click client? The NoteMaker API collection in our public Postman workspace has every request from these pages, with tests, an example answer for each and an error case per folder. Fork it into your own workspace, set baseUrl, workspace and token in the environment, and run the folders in order.