Tesria

Code examples

The same small program twice, in JavaScript and in Python, doing the five things most scripts need: list the spaces a token can see, search one of them, write a page, change it without overwriting anyone, and label it. Copy one, change what it writes, and you have the skeleton of a real integration.

Both were run against Tesria as it is described here. Neither needs anything installed beyond the language itself.

They write pages. Point them at a space you can throw away, such as a new private space made for trying things.

Before you run them

Step 1: Make a token

Open your profile (your picture or initials at the top right of any page) and scroll to its API tokens card, and make a token with full access (not read-only), since the scripts write. See Getting started with the API for each step.

Step 2: Tell the script where and as whom

The scripts read three settings from the environment rather than from the code, so a token never ends up in a file you might share. On a Mac or Linux:

Bash / Shell
export TESRIA_URL=https://your-server export TESRIA_TOKEN=cct_your_token export TESRIA_SPACE=SANDBOX

In PowerShell on Windows:

powershell
$env:TESRIA_URL = "https://your-server" $env:TESRIA_TOKEN = "cct_your_token" $env:TESRIA_SPACE = "SANDBOX"

your-server is the address you open Tesria at, and SANDBOX the key of the space to write in.

Step 3: If your Tesria has its own certificate

A Tesria without a domain name uses a certificate it made itself, which a script refuses just as a browser warns about it (see Trusting the local certificate). Download the certificate from http://your-server/ca.crt and point the script at it: NODE_EXTRA_CA_CERTS=/path/to/tesria-ca.crt for JavaScript, SSL_CERT_FILE=/path/to/tesria-ca.crt for Python. Never turn certificate checks off instead: that would send your token to anyone who can intercept the connection.

JavaScript

Needs Node 18 or later. Save it as tesria.mjs and run node tesria.mjs.

JavaScript
// A small Tesria client: list spaces, search, write a page, label it. // Run with Node 18 or later: node tesria.mjs const base = process.env.TESRIA_URL // such as https://wiki.example.com const token = process.env.TESRIA_TOKEN // starts with cct_ const space = process.env.TESRIA_SPACE // a space key, such as TEAM async function tesria(method, path, body) { const res = await fetch(base + path, { method, headers: { Authorization: `Bearer ${token}`, ...(body ? { 'Content-Type': 'application/json' } : {}), }, body: body ? JSON.stringify(body) : undefined, }) const data = res.status === 204 ? null : await res.json().catch(() => null) if (!res.ok) { const error = new Error(`${method} ${path}: ${res.status} ${data?.code ?? data?.title ?? ''}`) error.status = res.status error.data = data throw error } return data } // A page's content is a document: paragraphs, headings, lists and so on. const paragraph = (text) => ({ type: 'paragraph', content: [{ type: 'text', text }] }) const documentOf = (...blocks) => JSON.stringify({ type: 'doc', content: blocks }) // 1. Which spaces can this token see? for (const s of await tesria('GET', '/api/spaces')) console.log(s.key, s.name) // 2. Search, in one space. const { id: spaceId } = await tesria('GET', `/api/spaces/${space}`) const hits = await tesria('GET', `/api/search?q=${encodeURIComponent('release')}&spaceId=${spaceId}`) for (const hit of hits) console.log(hit.title, hit.pageId) // 3. Write a page. const page = await tesria('POST', '/api/pages', { spaceId, title: 'Release 1.4 notes', contentJson: documentOf(paragraph('Written by a script.')), }) console.log('created', page.id, 'version', page.currentVersionNumber) // 4. Change it without overwriting anyone: send the version you read. async function append(pageId, text) { for (let attempt = 0; attempt < 3; attempt++) { const current = await tesria('GET', `/api/pages/${pageId}`) const doc = JSON.parse(current.contentJson) doc.content.push(paragraph(text)) try { return await tesria('PUT', `/api/pages/${pageId}`, { title: current.title, contentJson: JSON.stringify(doc), changeComment: 'Added a line', baseVersion: current.currentVersionNumber, }) } catch (error) { if (error.status !== 409) throw error // someone else saved first: read it again } } throw new Error('The page kept changing; try again later.') } const updated = await append(page.id, 'And a second line.') console.log('updated to version', updated.currentVersionNumber) // 5. Label it. await tesria('POST', `/api/pages/${page.id}/labels`, { name: 'release-notes' }) console.log('labeled')

Python

Needs Python 3.9 or later, and nothing else. Save it as tesria.py and run python3 tesria.py.

Python
"""A small Tesria client: list spaces, search, write a page, label it. Needs Python 3.9 or later and nothing else: python3 tesria.py """ import json import os import urllib.error import urllib.parse import urllib.request BASE = os.environ["TESRIA_URL"] # such as https://wiki.example.com TOKEN = os.environ["TESRIA_TOKEN"] # starts with cct_ SPACE = os.environ["TESRIA_SPACE"] # a space key, such as TEAM class TesriaError(Exception): def __init__(self, method, path, status, data): self.status = status self.data = data code = (data or {}).get("code") or (data or {}).get("title") or "" super().__init__(f"{method} {path}: {status} {code}") def tesria(method, path, body=None, params=None): url = BASE + path + ("?" + urllib.parse.urlencode(params) if params else "") request = urllib.request.Request(url, method=method) request.add_header("Authorization", f"Bearer {TOKEN}") data = None if body is not None: request.add_header("Content-Type", "application/json") data = json.dumps(body).encode() try: with urllib.request.urlopen(request, data) as response: text = response.read() return json.loads(text) if text else None except urllib.error.HTTPError as error: try: details = json.loads(error.read()) except ValueError: details = None raise TesriaError(method, path, error.code, details) from None def paragraph(text): return {"type": "paragraph", "content": [{"type": "text", "text": text}]} def document_of(*blocks): return json.dumps({"type": "doc", "content": list(blocks)}) # 1. Which spaces can this token see? for space in tesria("GET", "/api/spaces"): print(space["key"], space["name"]) # 2. Search, in one space. space_id = tesria("GET", f"/api/spaces/{SPACE}")["id"] for hit in tesria("GET", "/api/search", params={"q": "release", "spaceId": space_id}): print(hit["title"], hit["pageId"]) # 3. Write a page. page = tesria("POST", "/api/pages", { "spaceId": space_id, "title": "Release 1.4 notes", "contentJson": document_of(paragraph("Written by a script.")), }) print("created", page["id"], "version", page["currentVersionNumber"]) # 4. Change it without overwriting anyone: send the version you read. def append(page_id, text): for _ in range(3): current = tesria("GET", f"/api/pages/{page_id}") doc = json.loads(current["contentJson"]) doc["content"].append(paragraph(text)) try: return tesria("PUT", f"/api/pages/{page_id}", { "title": current["title"], "contentJson": json.dumps(doc), "changeComment": "Added a line", "baseVersion": current["currentVersionNumber"], }) except TesriaError as error: if error.status != 409: # 409: someone else saved first, so read it again raise raise RuntimeError("The page kept changing; try again later.") updated = append(page["id"], "And a second line.") print("updated to version", updated["currentVersionNumber"]) # 5. Label it. tesria("POST", f"/api/pages/{page['id']}/labels", {"name": "release-notes"}) print("labeled")

What each part does

  1. A helper that sends requests. Every request carries the token as Authorization: Bearer. A refused request raises an error carrying the status and, when Tesria gives one, the code that says why, such as read_only_token. See Requests and responses for what each status means.

  2. Listing and searching. GET /api/spaces and GET /api/search answer with only what the token’s owner may see.

  3. Writing a page. A page’s content is a document of blocks (paragraphs, headings, lists), sent as JSON text in contentJson. The scripts build the simplest one, a paragraph.

  4. Changing it safely. The script reads the page, changes it, and sends the version number it read as baseVersion. If someone saved in between, Tesria answers 409 instead of overwriting them, and the script reads it again and retries.

  5. Labeling it. One request per label.

For everything else the API can do, see API reference. To be told when something changes rather than asking, see Webhooks, which also has code to check that a message really came from Tesria.


Applies to

Tesria 0.6 and later

Updated

September 24, 2026

Changes

Revised.