Truke KF exposes a small REST/JSON API under /api. With it you can read an
item, run a full-text search, walk the relations between items, and create or
update items and their related objects (events, actions, components, and so on) —
everything the web interface does with the item store, from a script or an
integration.
The API carries exactly your own access: a request made with your token sees the same items you would see in the browser, no more. See authentication & access for how identity and access labels work.
The full specification is browsable and testable in your browser:
| Address | What it is |
|---|---|
/api/docs | Swagger UI — try every endpoint interactively |
/api/openapi.yaml | the raw OpenAPI 3.0 document |
Open /api/docs, click Authorize, paste a token (below), and you can call any
endpoint directly from the page.
Every data endpoint requires a personal access token sent as a bearer credential:
Authorization: Bearer
Only /api/health, /api/docs and /api/openapi.yaml are public — everything
else returns 401 without a valid token.
Create a token (you must be logged in). The plaintext is shown once — store it immediately, it cannot be retrieved later:
curl -X POST https://kf.example.com/auth/tokens \
-d name=my-script -d ttl=720h
# {"id":"a1b2c3d4","token":"f0e1d2…"}
name is a label for your own reference; ttl is an optional lifetime (for
example 720h for 30 days) — omit it for a token that never expires. Revoke a
token with POST /auth/tokens/{id}/revoke. See
access tokens for the full details.
How long is a token valid? As long as you decide. Unlike the browser
session (which expires after a fixed period, 24 hours by default), an API
token has no default and no maximum lifetime — it lives for exactly the ttl
you set at creation, or forever if you omit it. Once issued, the only way to end
it early is to revoke it.
ttl must use Go duration units — h (hours), m (minutes), s (seconds); for
example 720h, not 30d. A value KF cannot parse is treated as no expiry, so
a typo like 30d silently produces a token that never expires.
Content-Type: application/json
on POST and PUT.id (or uid). Items also have an optional
human code (an SKU or x0 number) and a space-separated list of tags.m (module), i (item), e (event), a (action),
t (type). The class controls how an item behaves and how it is shown.{ "error": "…" } with an appropriate HTTP status
(400 invalid input, 401 no/invalid token, 404 not found).| Method & path | Purpose |
|---|---|
GET /api/health | Liveness check (public, no token) |
GET /api/search?q=… | Full-text (BM25) search |
POST /api/items | Create an item |
GET /api/items/{id} | Get an item by id |
PUT /api/items/{id} | Update an item |
GET /api/items/by-code/{code} | Get an item by its code (SKU / x0) |
GET /api/items/by-tag/{tag} | List items carrying a tag |
GET /api/items/{id}/path | Breadcrumb path of an item |
GET /api/items/{id}/{relation} | List related items |
POST /api/items/{id}/{relation} | Create and link a related item |
GET /api/search?q=pump+failure
Full-text search over all items, in BM25 rank order (best match first). The
query uses FTS5 syntax: plain keywords, "exact phrases", prefix* wildcards, and
the boolean operators AND / OR / NOT.
curl -G https://kf.example.com/api/search \ --data-urlencode "q=pump AND (seal OR bearing)" \ -H "Authorization: Bearer $TOKEN"
Each result is a compact reference:
[
{ "id": "3k9", "title": "Pump seal leak", "type": "e", "link": "/3k9" }
]
GET /api/items/{id} returns the full item:
{
"id": "3k9",
"uid": "…",
"title": "Centrifugal pump",
"class": "i",
"code": "PMP-100",
"tags": "critical rotating",
"parent": "1a2",
"doc": "Markdown body of the item…"
}
You can also fetch by code (GET /api/items/by-code/PMP-100) or list every item
carrying a tag (GET /api/items/by-tag/critical). GET /api/items/{id}/path
returns the breadcrumb trail:
{ "id": "3k9", "path": "Home / Machine / Pump" }
GET /api/items/{id}/{relation} lists the items linked to an item by a given
relation. {relation} is one of:
| Relation | Direction | What it lists |
|---|---|---|
events | down | failure modes / problems annotated on the item |
components | down | sub-parts (bill of materials) |
actions | down | corrective / preventive actions |
outputs | down | expected outcomes |
instances | down | items that draw from this one as a type |
sidebar | down | sidebar navigation elements |
causes | up | causes leading to this event |
types | up | types this item learns from |
curl https://kf.example.com/api/items/3k9/events \ -H "Authorization: Bearer $TOKEN"
Create an item with POST /api/items. Only title is required; class,
code, tags, doc and parent are optional:
curl -X POST https://kf.example.com/api/items \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Centrifugal pump","class":"i","code":"PMP-100","tags":"critical"}'
The response is the created item (HTTP 201), including its new id.
Update with PUT /api/items/{id}. Only the fields you send (title, doc,
code, tags) are changed; anything omitted is left as it was.
POST /api/items/{id}/{relation} creates a new item of the class implied by the
relation and links it to {id} in one step. For example, to annotate a failure
event on a component:
curl -X POST https://kf.example.com/api/items/3k9/events \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Seal leak under load"}'
The down relations (events, components, actions, outputs, instances,
sidebar) attach the new item below {id}; the up relations (causes,
types) attach it above. The body is the same NewItem shape as
POST /api/items, but class and parent are ignored — the relation decides both.
export TOKEN=f0e1d2…
# find an item
curl -G https://kf.example.com/api/search --data-urlencode "q=pump" \
-H "Authorization: Bearer $TOKEN"
# read it
curl https://kf.example.com/api/items/3k9 -H "Authorization: Bearer $TOKEN"
# annotate a failure event on it
curl -X POST https://kf.example.com/api/items/3k9/events \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"title":"Bearing seizure"}'