ListHub API Documentation

Base URL: https://listhub.globalbr.ai/api/v1

Design Philosophy

ListHub is a personal knowledge and list management system built around three access layers:

  • Web UI — for humans browsing, creating, and editing items in the browser
  • REST API — for programmatic access by assistants, scripts, and integrations
  • Git — for file-based workflows: clone your data as markdown, edit locally, push changes back

All three layers read and write the same underlying data. Changes made through any layer are immediately visible to the others. Items created via the API appear in git on the next pull. Files pushed via git appear in the web UI and API instantly.

Items are markdown documents with metadata (title, tags, visibility, type). They're stored as structured data in the database but mirrored to git as .md files with YAML frontmatter, so they're always human-readable and portable.

The design favors simplicity: SQLite for storage, bare git repos with plumbing commands for sync, plain Bearer tokens for auth. No external dependencies beyond git itself.

Visibility Levels

Every item has a visibility setting that controls who can view and edit it:

ValueWho can viewWho can editUse case
private Owner only Owner only Personal notes, drafts
shared Owner + shared users Owner + users with edit permission Collaboration with specific people
public Anyone (no auth needed) Owner only Published content, read-only sharing
public_edit Anyone (no auth needed) Any authenticated user Wiki-style collaborative lists, community content

Key points:

  • public_edit items can be edited by any user with a ListHub account — no explicit sharing needed
  • Even on public_edit items, only the owner can change visibility, slug, or delete the item
  • All edits are versioned — you can always see what changed
  • Set via the API (visibility field) or the web UI edit form

Authentication

Most API endpoints require authentication via one of:

  • API Key — pass as a Bearer token in the Authorization header
  • Session cookie — if logged in via the web UI
Authorization: Bearer mem_your_api_key_here

API keys can be created from Settings, via the token endpoint below, or via the key management endpoints. Keys have scopes (read, write) that control access. The raw key is shown only once at creation time.

Noos SSO

ListHub supports single sign-on via Noos. Click "Sign in with Noos" on the login page to authenticate with your Noos account. On first login, a ListHub account is automatically created and linked to your Noos identity.

Noos-authenticated users don't have a local password. For git access (clone/push), create an API key in Settings and use it as your git password:

git clone https://yourname:[email protected]/git/yourname.git

Register a new account (no auth required)

POST /api/v1/auth/register — no authentication required

Create a new ListHub account and receive an API key in a single call. This is the recommended way for AI agents and integrations to self-onboard — no browser, no human intervention needed.

curl -X POST -H "Content-Type: application/json" \ -d '{"username": "myagent", "password": "securepass123", "key_name": "my-agent"}' \ https://listhub.globalbr.ai/api/v1/auth/register

Fields:

  • username — alphanumeric, at least 2 characters (required)
  • password — at least 8 characters (required)
  • display_name — human-readable name (default: username)
  • email — optional email address
  • key_name — label for the API key (default: "api-token")
  • scopes — comma-separated scopes (default: "read,write")

Response: 201

{ "id": "user-id", "username": "myagent", "key": "mem_abc123...", "key_id": "key-id", "key_name": "my-agent", "scopes": "read,write", "message": "Account created. Save the key — it cannot be retrieved again." }

Errors:

  • 400 — Missing/invalid username or password
  • 409 — Username or email already taken

After registration, use the returned key as a Bearer token for all subsequent API calls.

Get API key (token endpoint)

POST /api/v1/auth/token — no authentication required

Exchange a username and password for a new API key. This is the recommended way for assistants and integrations to bootstrap themselves.

curl -X POST -H "Content-Type: application/json" \ -d '{"username": "alice", "password": "...", "name": "my-assistant"}' \ https://listhub.globalbr.ai/api/v1/auth/token

Fields:

  • username — ListHub username (required)
  • password — account password (required)
  • name — label for the key (default: "api-token")
  • scopes — comma-separated scopes (default: "read,write")

Response: 201

{ "id": "key-id", "key": "mem_abc123...", "name": "my-assistant", "scopes": "read,write", "username": "alice" }

Save the key value — it cannot be retrieved again.

Register a new account

POST /api/v1/auth/register — no authentication required

Create a new ListHub account and get an API key in a single call. This is the fastest way for an agent to get started — no browser needed.

curl -X POST -H "Content-Type: application/json" \ -d '{"username": "myagent", "password": "securepass123", "key_name": "my-assistant"}' \ https://listhub.globalbr.ai/api/v1/auth/register

Fields:

  • username — desired username, 2+ alphanumeric characters (required)
  • password — account password, 8+ characters (required)
  • display_name — display name (optional, defaults to username)
  • email — email address (optional)
  • key_name — label for the auto-generated API key (default: "api-token")
  • scopes — comma-separated scopes (default: "read,write")

Response: 201

{ "id": "user-id", "username": "myagent", "key": "mem_abc123...", "key_id": "key-id", "key_name": "my-assistant", "scopes": "read,write", "message": "Account created. Save the key - it cannot be retrieved again." }

Save the key value — it cannot be retrieved again. Use it as Authorization: Bearer mem_abc123... for all subsequent API calls.

WebMCP — in-tab tools for browser agents

ListHub registers itself as a WebMCP provider on every page. Browser-based AI agents (Chrome 146+ with the WebMCP flag enabled) can call ListHub's API directly via navigator.modelContext, inheriting the user's existing browser session. No API key handoff. No cross-origin dance.

This is the W3C standard (Google + Microsoft) for letting websites declare callable tools that in-tab agents can invoke. ListHub's tools are thin wrappers around the REST API documented below.

Enabling WebMCP in Chrome:

  1. Chrome 146 or newer (Canary as of writing)
  2. Visit chrome://flags/#enable-webmcp-testing and enable
  3. Install the Model Context Tool Inspector extension
  4. Visit any ListHub page — tools auto-register

Tools registered on every page:

  • listhub_search(query) — full-text search across public items
  • listhub_get_item(slug) — fetch an item by slug
  • listhub_list_my_items({visibility, type, tag}) — list the logged-in user's items
  • listhub_create_item({title, content, item_type, visibility, tags, slug})
  • listhub_append_to_list({slug, entry})
  • listhub_upsert_item({slug, ...fields}) — idempotent create-or-update
  • listhub_set_visibility({slug, visibility})

Page-context tools (only registered on the relevant page):

  • listhub_save_current_item_to_my_collection() — only on /@user/slug item pages
  • listhub_browse_user_items() — only on /@user profile pages

The implementation lives in /static/webmcp.js and is loaded with <script defer> from base.html. It is a no-op in browsers without WebMCP support, so the page works fine for everyone else.

The page sets data-listhub-webmcp="ready" on the <html> element when tools are registered, so agents can probe whether the surface is live.

Private content blocks

Any item's content can contain HTML comment blocks that are rendered for the owner but stripped when anyone else views the item. This lets you mix personal notes into otherwise-public content without splitting into two items.

Public intro that everyone sees. <!-- private --> Secret section. Only the owner sees this when rendering. Non-owners get the text stripped from the HTML entirely. <!-- /private --> More public content below.

Rules:

  • Markers are <!-- private --> and <!-- /private -->
  • Case-insensitive, whitespace inside the marker is allowed
  • Non-greedy: the first <!-- /private --> closes the nearest open block. Don't nest.
  • The raw markdown still contains the block — the filter runs at render time only
  • For whole-item privacy, prefer the visibility field
  • Agents writing items should use this pattern when they want to mix personal context into shareable content

Items

List items

GET /api/v1/items

Returns all items owned by the authenticated user.

Query parameters:

  • q — full-text search query
  • tag — filter by tag
  • type — filter by item type (note, list, document)
  • visibility — filter by visibility (private, shared, public)
# List all items curl -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/items # Search for items containing "recipe" curl -H "Authorization: Bearer $KEY" \ "https://listhub.globalbr.ai/api/v1/items?q=recipe" # Filter by tag curl -H "Authorization: Bearer $KEY" \ "https://listhub.globalbr.ai/api/v1/items?tag=reading"

Response: Array of item objects (without content). Each item includes id, slug, title, item_type, visibility, revision, tags, created_at, updated_at.

Get item

GET /api/v1/items/:id

Returns a single item with full content.

curl -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/items/abc123

Response:

{ "id": "abc123", "slug": "my-note", "title": "My Note", "content": "The full markdown content...", "item_type": "note", "visibility": "private", "revision": 3, "tags": ["tag1", "tag2"], "created_at": "2026-02-11 10:00:00", "updated_at": "2026-02-11 12:30:00" }

Create item

POST /api/v1/items/new — requires write scope

curl -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "My New Note", "content": "Hello world", "visibility": "private", "item_type": "note", "tags": ["example"] }' \ https://listhub.globalbr.ai/api/v1/items/new

Fields:

  • title — item title (default: "Untitled")
  • content — markdown content (default: "")
  • slug — URL slug (auto-generated from title if omitted)
  • item_typenote, list, or document (default: "note")
  • visibilityprivate, shared, public, or public_edit (default: "private"). See Visibility Levels below.
  • tags — array of tag strings
  • file_path — git repo file path (default: {slug}.md)

Response: 201 with the created item object.

Edit content

POST /api/v1/items/:id/edit — requires write scope

Replaces the item's content and bumps the revision.

curl -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"content": "Updated content here"}' \ https://listhub.globalbr.ai/api/v1/items/abc123/edit

Update metadata

PUT /api/v1/items/:id — requires write scope

Update title, slug, visibility, item_type, or tags without changing content. Non-owners can update title, tags, and item_type on public_edit items (but not slug or visibility).

curl -X PUT -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "New Title", "visibility": "public", "tags": ["updated", "tags"] }' \ https://listhub.globalbr.ai/api/v1/items/abc123

Delete item

DELETE /api/v1/items/:id — requires write scope

curl -X DELETE -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/items/abc123

Response: {"ok": true}

Append to list

POST /api/v1/items/:id/append — requires write scope

Appends a bullet point to an item's content. Useful for list-type items.

curl -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"entry": "Buy milk"}' \ https://listhub.globalbr.ai/api/v1/items/abc123/append

This appends - Buy milk as a new line to the item's content.

Slug-Based Access

These endpoints let you create, read, update, and delete items by slug instead of by ID. Useful for integrations that know the item's slug but not its internal ID.

Get item by slug

GET /api/v1/items/by-slug/:slug

Returns the full item matching the slug for the authenticated user.

curl -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/items/by-slug/grocery-list

Upsert item by slug

PUT /api/v1/items/by-slug/:slug — requires write scope

Creates or updates an item by slug. If an item with this slug exists for the authenticated user, it updates the content (bumps revision, adds version). If not, it creates a new item with this exact slug (no auto-incrementing like -1, -2).

curl -X PUT -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Grocery List", "content": "- Milk\n- Eggs", "visibility": "private" }' \ https://listhub.globalbr.ai/api/v1/items/by-slug/grocery-list

Fields:

  • title — item title (default: derived from slug)
  • content — markdown content
  • item_typenote, list, or document (default: "note")
  • visibilityprivate, shared, public, or public_edit (default: "private"). See Visibility Levels below.
  • tags — array of tag strings

Response: 200 if updated, 201 if created. Returns the item object.

Delete item by slug

DELETE /api/v1/items/by-slug/:slug — requires write scope

curl -X DELETE -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/items/by-slug/grocery-list

Response: {"ok": true}

Search

GET /api/v1/search?q=:query

Full-text search across all your items using SQLite FTS5.

curl -H "Authorization: Bearer $KEY" \ "https://listhub.globalbr.ai/api/v1/search?q=python+flask"

Sharing

Share item

POST /api/v1/items/:id/share — requires write scope

curl -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"shared_with": "alice", "permission": "read"}' \ https://listhub.globalbr.ai/api/v1/items/abc123/share

permission can be read or edit. Automatically sets item visibility to shared.

Revoke share

DELETE /api/v1/items/:id/share/:username — requires write scope

curl -X DELETE -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/items/abc123/share/alice

Public Editing

Items with public_edit visibility can be read by anyone and edited by any authenticated user. These endpoints use the @username/slug pattern so you don't need to know the item's internal ID.

Get public item

GET /api/v1/public/:username/:slug — no authentication required

Returns a public or public_edit item. The response includes a can_edit field indicating whether the item accepts edits from any user.

curl https://listhub.globalbr.ai/api/v1/public/jacobreal/agent-friendly-apps

Response:

{ "id": "abc123", "slug": "agent-friendly-apps", "title": "Agent-Friendly Apps", "content": "...", "visibility": "public_edit", "owner": "jacobreal", "can_edit": true, "tags": ["apps", "agents"], ... }

Edit public item content

POST /api/v1/public/:username/:slug/edit — requires authentication, requires write scope

Replace the content of a public_edit item. Any authenticated user can call this.

curl -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"content": "Updated content here"}' \ https://listhub.globalbr.ai/api/v1/public/jacobreal/agent-friendly-apps/edit

Errors:

  • 403 — Item is not public_edit
  • 404 — User or item not found

Append to public item

POST /api/v1/public/:username/:slug/append — requires authentication, requires write scope

Append a bullet point to a public_edit item. Any authenticated user can call this.

curl -X POST -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"entry": "New list item here"}' \ https://listhub.globalbr.ai/api/v1/public/jacobreal/agent-friendly-apps/append

Edit via existing endpoints

If you already know the item's ID (e.g., from a previous API call), the standard /items/:id/edit and /items/:id/append endpoints also work for public_edit items — you don't need to be the owner.

API Keys

These endpoints require session authentication (logged in via web UI). For programmatic key creation, use the /api/v1/auth/token endpoint above instead.

List keys

GET /api/v1/keys

Create key

POST /api/v1/keys

curl -X POST -H "Content-Type: application/json" \ -b cookies.txt \ -d '{"name": "my-app", "scopes": "read,write"}' \ https://listhub.globalbr.ai/api/v1/keys

Response: includes the raw API key (shown only once):

{"id": "...", "key": "mem_abc123...", "name": "my-app", "scopes": "read,write"}

Delete key

DELETE /api/v1/keys/:id

Git Access

Each user gets a bare git repo. Items are mirrored as markdown files with YAML frontmatter. Changes flow both ways:

  • Web/API → Git: Creating or editing items via the web UI or API automatically commits to the git repo. Pull to get changes.
  • Git → DB: Pushing .md files to the repo creates or updates items in the database via a post-receive hook.
# Clone your repo git clone https://username:[email protected]/git/username.git # Pull latest changes (after creating/editing items via web or API) git pull # Push local changes (creates/updates items in the database) git add . && git commit -m "update notes" && git push

Authentication uses your ListHub username and password (or an API key as the password).

File format

Items are stored as markdown with YAML frontmatter:

--- title: My Note slug: my-note visibility: private type: note tags: [tag1, tag2] --- Your markdown content here...

Files without frontmatter are also accepted on push — metadata will be inferred from the filename. After a sync cycle, all files will have explicit frontmatter.

Assistant Integration Guide

ListHub is designed to be used by AI assistants as a persistent knowledge store. Here's the recommended workflow.

1. Bootstrap: register or authenticate

If you don't have an account yet, register one — it creates the account and returns an API key in a single call:

curl -X POST -H "Content-Type: application/json" \ -d '{"username": "my-agent", "password": "securepass123"}' \ https://listhub.globalbr.ai/api/v1/auth/register # Response: {"username": "my-agent", "key": "mem_abc123...", ...}

If you already have an account, exchange credentials for a key:

curl -X POST -H "Content-Type: application/json" \ -d '{"username": "alice", "password": "...", "name": "my-assistant"}' \ https://listhub.globalbr.ai/api/v1/auth/token # Response: {"key": "mem_abc123...", ...}

2. Use the API for reads and writes

The API is the recommended interface for assistants. It's stateless, returns immediate confirmation, and handles all business logic (slugs, versioning, search indexing, git sync).

Core operations:

  • Save a note: POST /api/v1/items/new with title, content, tags
  • Append to a list: POST /api/v1/items/:id/append with an entry
  • Search: GET /api/v1/search?q=... to find relevant items
  • Read: GET /api/v1/items/:id to retrieve full content
  • Update: POST /api/v1/items/:id/edit to replace content

3. Example tool definitions

If your assistant framework uses tool/function calling (OpenAI, Anthropic, etc.), here are recommended tool definitions:

{ "name": "listhub_create", "description": "Create a new item in the user's ListHub. Use for saving notes, starting lists, or storing any information the user wants to keep.", "parameters": { "type": "object", "properties": { "title": {"type": "string", "description": "Item title"}, "content": {"type": "string", "description": "Markdown content"}, "item_type": {"type": "string", "enum": ["note", "list", "document"]}, "visibility": {"type": "string", "enum": ["private", "shared", "public"]}, "tags": {"type": "array", "items": {"type": "string"}} }, "required": ["title", "content"] } } { "name": "listhub_append", "description": "Append an entry to an existing list item.", "parameters": { "type": "object", "properties": { "item_id": {"type": "string", "description": "The item ID to append to"}, "entry": {"type": "string", "description": "The text to append as a bullet point"} }, "required": ["item_id", "entry"] } } { "name": "listhub_search", "description": "Search the user's ListHub items by keyword.", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "Search query"} }, "required": ["query"] } } { "name": "listhub_read", "description": "Read the full content of a ListHub item.", "parameters": { "type": "object", "properties": { "item_id": {"type": "string", "description": "The item ID to read"} }, "required": ["item_id"] } }

4. If you want local files too

For assistants that also need file-system access to the user's data, the recommended pattern is:

  • Write via the API — the API is the source of truth for all mutations
  • Read via git pull — clone the repo once, then git pull periodically to get the latest files

This avoids merge conflicts from multiple writers. The API handles all the complexity (deduplication, versioning, indexing), and git provides a clean read-only mirror as markdown files.

# One-time setup git clone https://user:[email protected]/git/user.git ~/listhub-data # Periodic sync (cron, timer, or after API writes) git -C ~/listhub-data pull --ff-only

Featured

ListHub hosts Featured — a curated, hierarchical index of lists and collections, browsable at /featured. Featured is a git repo that any authenticated user can contribute to.

How it works

  • Featured lives in a shared bare git repo at /git/directory.git
  • The folder structure in git is the browsable tree hierarchy on the web
  • Each folder has an index.md with content and links to ListHub items
  • Any authenticated user can clone, edit, and push — no pull requests needed
  • Edit history comes free from git (every push is a commit)

Contributing

# Clone Featured git clone https://username:[email protected]/git/directory.git featured # Add a new category mkdir music cat > music/index.md << 'EOF' --- title: Music --- # Music Playlists, albums, and music recommendations. - [My Playlist](/@alice/playlist) EOF # Or add a link to an existing category echo "- [My Reading List](/@alice/reading-list)" >> reading/index.md # Push changes git add . && git commit -m "add music category" && git push

Featured structure

Folders are categories, index.md files are the content displayed for each category. Links use the format /@username/item-slug to point to ListHub items.

featured/ ├── index.md ← root page ├── tech/ │ └── index.md ← "- [AI Tools](/@jacob/ai-tools)" ├── cooking/ │ └── index.md └── reading/ └── index.md

For assistants

Assistants can contribute to Featured by cloning and pushing, just like a human user. Use git HTTP Basic Auth with the user's credentials or API key as the password.

# Clone with API key as password git clone https://alice:[email protected]/git/directory.git featured

Featured API

Featured is a shared, collaboratively-edited knowledge structure. Any authenticated user can read, write, and delete files. Changes are committed to the underlying git repo.

List Featured tree

GET /api/v1/featured — no authentication required

Returns the full list of file paths in Featured.

curl https://listhub.globalbr.ai/api/v1/featured

Response:

{"tree": ["index.md", "tech/index.md", "reading/index.md"]}

Read a Featured file

GET /api/v1/featured/:path — no authentication required

curl https://listhub.globalbr.ai/api/v1/featured/tech/index.md

Response:

{"path": "tech/index.md", "content": "---\ntitle: Tech\n---\n# Tech\n..."}

Create or update a Featured file

PUT /api/v1/featured/:path — requires write scope

Creates or overwrites a file in Featured. The change is committed to git with the authenticated user as the author.

curl -X PUT -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "---\ntitle: Music\n---\n# Music\n\n- [My Playlist](/@alice/playlist)", "message": "Add music category" }' \ https://listhub.globalbr.ai/api/v1/featured/music/index.md

Fields:

  • content — file content, typically markdown with optional YAML frontmatter (required)
  • message — git commit message (default: "Update {path}")

Response:

{"ok": true, "path": "music/index.md", "commit": "abc123..."}

Delete a Featured file

DELETE /api/v1/featured/:path — requires write scope

curl -X DELETE -H "Authorization: Bearer $KEY" \ https://listhub.globalbr.ai/api/v1/featured/music/index.md

Response: {"ok": true, "path": "music/index.md", "commit": "def456..."}

Errors

Error responses return JSON with an error field:

{"error": "Not found"} # 404 {"error": "Authentication required"} # 401 {"error": "Invalid credentials"} # 401 (token endpoint) {"error": "Write scope required"} # 403 {"error": "Slug already in use"} # 409