The REST API
APIs and MCP

The REST API

The REST API is the hub's front door for programs. A program sends a request to an address, carrying a key that says who it is. It gets an answer back. Every page in the hub is built on these same requests, so anything the site can show you, a program can fetch, and anything you can change on the site, a program can change.

Description

This page says how the addresses are shaped and how a program proves who it is. It also says how a program follows live data, and what bounds it. The full list of calls, with each one's parameters, body and responses, is the REST reference on the help site.

Addresses

An address is just a path, with no prefix in front of it: your markdown content, your projects and your tokens each have one. In the hub's own browser code the same calls are written under /api, because that is the website's way of passing a call through to the backend on the same origin. The backend itself never sees /api.

The root of the API answers a program with the names of its documentation, its OpenAPI document and its health check, and sends a browser to the help site. The OpenAPI document at /doc is generated at boot from the same definitions that serve the API, so it always describes what is running. A health call at /health reports the service and its database pools.

Routes made from tables

Most of the API is made from the database itself. Every table the hub exposes gets the same family of calls, at an address made from its schema and table name: list its rows, add one, read one, change one and remove one. A list returns at most 500 rows, and a string in the query filters on that column. A new row is checked against the table's own shape before it is written.

Two query parameters, ecosystemId and workspace, let a call run in another scope you have access to.

Some columns are the hub's to manage, and a program cannot set them. They are stripped from every body: id, user_id, created_at, updated_at, sync_version, recall_count, last_recalled_at, embedding, embedding_model, created_by, granted_by, added_by and assigned_by. Each exposed table is given a tier, owner, catalog or admin, which says who may reach it; a table with no tier stops the backend from starting. Hooks run before and after a row is added, changed or removed, and decorators add computed fields to what is read.

Routes written by hand

What a table cannot say on its own is written by hand: signing in and tokens, storage, chat, search, sync, billing, Shipr, the feature catalog, ownership transfer, integrations, projects, audience, persona bootstrap and chat, access and roles, registries, gamification and games, and the admin surfaces. Each is described in the same OpenAPI document, and a test fails when a route is served that the document does not describe.

Proving who you are

A program sends its key as a bearer token in the Authorization header. The accepted keys are:

  • A session, the short-lived token your browser holds after signing in on the hub.
  • A personal API token, which starts with tmp_ and acts as you. You mint one in Settings or by a call to the API.
  • An application token, a tmp_ token bound to one of your applications rather than to a person.
  • A persona acting token, a tmp_ token bound to a persona and the person it acts for.
  • A storage token, which starts with adh_ and reaches one bucket and nothing else.
  • A customer token, issued by the hub's sign-in server to one of your products' applications, for that product's own users.

A personal token carries a scope: a list of the parts of the hub it may reach, each one optionally read-only. The API can list the scopes you may ask for. A customer token is confined to reading who it is, a product's feature flags and server bag, and sync.

A request with no key is refused with 401. One whose key lacks the scope or the verb is refused with 403. One for something you may not know exists is answered 404, as if it were not there.

The auth and identity pages say how the hub decides who a request is from.

Following live data

Some things change while you watch: a chat, your notifications, a background job, a project board, a deployment. Each has a stream a program can hold open and receive changes on, as server-sent events. The stream takes the token in the query, as access_token, because a browser cannot set a header on that kind of connection. It sends a heartbeat every 15 seconds and closes when the request does. There is a stream for a direct-message chat, for chat as a whole, for notifications, for a processing job, for a project, for Shipr and for one Shipr run.

Limits and usage

A request body has a size limit, and one past it is refused with 413. Every request writes a usage record. When your tier's allowance is used up the answer is 402, and when you are sending too fast it is 429. You can read your own usage from the API or in Settings.

Tech Detail
APIs & protocols
RESTOpenAPI