# API quickstart

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

1.  Open **Settings → MCP & API** in your workspace.
2.  Copy the **MCP server** address. It ends with your workspace's name, for example `https://app.thenotemaker.com/api/mcp/your-workspace`.
3.  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:

```bash
export NOTEMAKER_URL="https://app.thenotemaker.com/api/mcp/your-workspace"
export NOTEMAKER_TOKEN="nm_YOUR_TOKEN"
```

> [!warning] ⚠️
> 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.

```bash
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:

```json
{"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

```bash
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):

```text
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.

```bash
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."}}}'
```

```text
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`.

```javascript
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`.

```python
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:

```text
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

-   [📄 Pages and collections: API examples](https://docs.wrightdesk.com/build-with-the-api/pages-and-collections-api-examples)
-   [🧩 Forms, sheets and boards: API examples](https://docs.wrightdesk.com/build-with-the-api/forms-sheets-and-boards-api-examples)
-   [🛟 Agents, trash and errors: API examples](https://docs.wrightdesk.com/build-with-the-api/agents-trash-and-errors-api-examples)

## Try it in Postman

Prefer a point-and-click client? The [NoteMaker API collection](https://www.postman.com/thenotemaker-com/thenotemaker-s-workspace/collection/rmndz56/notemaker-api) 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.
