---
title: "Talk to a Semurg node over plain HTTP: addresses, the token, answers and errors"
slug: connect-over-rest
date: 2026-10-12
author: Semurg
section: how-to
tags: [connections, rest, api, auth, how-to]
summary: Everything a node does is reachable over plain HTTP with one token. How to address a node, send the token, read an answer, and read a refusal, with a hello world you can run in a minute.
status: published
covers: [route:GET /v1/whoami, route:GET /api/health, route:GET /api/version, route:POST /v1/query, route:GET /v1/status]
---
Every Semurg node answers plain HTTP. There is no client library you must use and no account to create: a node, its address and its
API token are the whole setup. This post covers the rules every REST call follows. The other connections (the MCP endpoint, the
WebSocket, the client libraries) are covered in their own posts and answer the same questions the same way, because each capability
has one implementation behind every door.

> Diagram route-families: Health and version answer anyone with no token; every data, memory, status and cluster route needs the node's token, in either header.

## The address

A node listens on `http://localhost:4000` on the machine it runs on, and only there, until you decide otherwise. The routes fall into
two families:

- Public routes that say whether the node is up and which release it runs: `/api/health` and `/api/version`. They need no token.
- Everything else: your data, memory, queries, the node's status, on routes whose path begins with v1, such as `/v1/query`. Every one of them needs the token.

The full list of routes, with every operation, is in the [API reference](/blog/reference), which is generated from the code of the published release.

## The token

The installer prints the node's API token when it finishes, and keeps it on the node in `/etc/semurg/semurg.env`; a Docker node prints
it in its log and keeps it in the container. Send it with every call to a v1 route, in either header:

- `Authorization: Bearer <token>`
- `x-api-key: <token>`

Without it, such a call is refused with `401`; with it, the same call goes through:

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -si http://localhost:4000/v1/whoami | head -1
curl -si -H "Authorization: Bearer $SEMURG_API_TOKEN" http://localhost:4000/v1/whoami | head -1
```

```expect
expect: the first call answers HTTP 401, the second HTTP 200
assert: line1~^HTTP/[0-9.]+ 401;line2~^HTTP/[0-9.]+ 200
```

## A refusal tells you what to do

A refused call answers JSON with `"ok": false`, an `error` that says what was wrong and, where there is more to say, a `detail`. The
`401` goes further, because the usual question is not "which header?" but "where is my token?": it says what your request carried, how
to send the token, and where to find it.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -s http://localhost:4000/v1/whoami
```

```expect
expect: a refusal that says what the request carried, how to send the token, and where to find it
assert: json:.ok==false and (.fix|type)=="string" and (.you_sent|type)=="string"
```

If your shell variable is empty, the `401` says that too: an empty token is the most common mistake, and the answer names it rather
than pretending the header was missing. On a node exposed to other machines, the answer no longer says where the token file is; it
tells a stranger to ask the operator.

The other statuses follow the same pattern: `422` for a call whose body is wrong, naming the field; `404` for something that is not
there; `409` for a request that cannot be completed as asked; `413` for a call past a size bound; `501` for a capability this release
does not carry; `503` for something that could not be read or done right now, safe to retry.

## An answer

A successful call answers JSON with `"ok": true` and the fields of that operation. With the token, `whoami` says who the node thinks
you are:

> Diagram a-rest-call: A call to a data route is checked for the token first; without it the node refuses and says where the token is; with it the call reaches its operation, which answers ok true with its fields or ok false with an error and a detail that names what to change.

```bash runnable kind=shell-node needs=token
export SEMURG_API_TOKEN=<your token>
curl -s -H "Authorization: Bearer $SEMURG_API_TOKEN" http://localhost:4000/v1/whoami
```

```expect
expect: an authenticated answer naming your scope of access
assert: json:.ok==true and .authenticated==true
```

## One route for every kind of data

Most reads and writes of data go through one route, `POST /v1/query`, with an `op` naming the operation and the operation's own
fields beside it. An unknown `op` is refused with the nearest real one suggested and the full list of operations, so a typo answers
with its own fix. The memory calls have their own routes, such as `/v1/memory/recall`, and the node's own state is at `/v1/status` and
on the cluster routes, such as `/v1/cluster/util`.

## Behind your own proxy

To reach a node from other machines, put it behind your own TLS proxy and use the proxy's `https` address; every path stays the same.
A node without a token that is reachable from other machines serves callers there only a small public sandbox, never your data, so
set a token before you expose a node that holds anything private.

## Next

- [Look after your node](/blog/look-after-your-node): health, version, status and utilisation.
- [Give your agent memory over MCP](/blog/agent-memory-mcp-five-minutes).
- [Objects, streams, search and places](/blog/objects-streams-search-and-places): the data operations on `/v1/query`.
