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:
export TESRIA_URL=https://your-server
export TESRIA_TOKEN=cct_your_token
export TESRIA_SPACE=SANDBOXIn PowerShell on Windows:
$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.
// 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.
"""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
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, thecodethat says why, such asread_only_token. See Requests and responses for what each status means.Listing and searching.
GET /api/spacesandGET /api/searchanswer with only what the token’s owner may see.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.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 answers409instead of overwriting them, and the script reads it again and retries.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. |