REST API

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.

Interactive documentation

The full specification is browsable and testable in your browser:

AddressWhat 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.

Authentication

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…"}
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](/products/kf/auth) 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.

## Conventions

- All request and response bodies are **JSON**; send `Content-Type: application/json`
  on `POST` and `PUT`.
- An **item** is identified by its `id` (or `uid`). Items also have an optional
  human **code** (an SKU or `x0` number) and a space-separated list of **tags**.
- Every item has a **class**: `m` (module), `i` (item), `e` (event), `a` (action),
  `t` (type). The class controls how an item behaves and how it is shown.
- Errors are returned as `{ "error": "…" }` with an appropriate HTTP status
  (`400` invalid input, `401` no/invalid token, `404` not found).

## Endpoints

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

### Search

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" }
]

Read an item


!g

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:

!g