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:
| Value | Who can view | Who can edit | Use 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_edititems can be edited by any user with a ListHub account — no explicit sharing needed- Even on
public_edititems, 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 (
visibilityfield) or the web UI edit form
Authentication
Most API endpoints require authentication via one of:
- API Key — pass as a Bearer token in the
Authorizationheader - Session cookie — if logged in via the web UI
Authorization: Bearer mem_your_api_key_hereAPI 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.gitRegister 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/registerFields:
username— alphanumeric, at least 2 characters (required)password— at least 8 characters (required)display_name— human-readable name (default: username)email— optional email addresskey_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 password409— 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/tokenFields:
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/registerFields:
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:
- Chrome 146 or newer (Canary as of writing)
- Visit
chrome://flags/#enable-webmcp-testingand enable - Install the Model Context Tool Inspector extension
- Visit any ListHub page — tools auto-register
Tools registered on every page:
listhub_search(query)— full-text search across public itemslisthub_get_item(slug)— fetch an item by sluglisthub_list_my_items({visibility, type, tag})— list the logged-in user's itemslisthub_create_item({title, content, item_type, visibility, tags, slug})listhub_append_to_list({slug, entry})listhub_upsert_item({slug, ...fields})— idempotent create-or-updatelisthub_set_visibility({slug, visibility})
Page-context tools (only registered on the relevant page):
listhub_save_current_item_to_my_collection()— only on/@user/slugitem pageslisthub_browse_user_items()— only on/@userprofile 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
visibilityfield - 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 querytag— filter by tagtype— 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/abc123Response:
{
"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/newFields:
title— item title (default: "Untitled")content— markdown content (default: "")slug— URL slug (auto-generated from title if omitted)item_type—note,list, ordocument(default: "note")visibility—private,shared,public, orpublic_edit(default: "private"). See Visibility Levels below.tags— array of tag stringsfile_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/editUpdate 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/abc123Delete item
DELETE /api/v1/items/:id — requires write scope
curl -X DELETE -H "Authorization: Bearer $KEY" \
https://listhub.globalbr.ai/api/v1/items/abc123Response: {"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/appendThis 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-listUpsert 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-listFields:
title— item title (default: derived from slug)content— markdown contentitem_type—note,list, ordocument(default: "note")visibility—private,shared,public, orpublic_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-listResponse: {"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/sharepermission 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/alicePublic 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-appsResponse:
{
"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/editErrors:
403— Item is notpublic_edit404— 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/appendEdit 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/keysResponse: 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
.mdfiles 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 pushAuthentication 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/newwith title, content, tags - Append to a list:
POST /api/v1/items/:id/appendwith an entry - Search:
GET /api/v1/search?q=...to find relevant items - Read:
GET /api/v1/items/:idto retrieve full content - Update:
POST /api/v1/items/:id/editto 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 pullperiodically 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-onlyFeatured
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.mdwith 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 pushFeatured 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.mdFor 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 featuredFeatured 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/featuredResponse:
{"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.mdResponse:
{"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.mdFields:
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.mdResponse: {"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