Project Registry
Manual project records that group related resources (a Portainer stack, a knowledge-vault subtree, a planner project) into a single bundle. Used by AI tools to ground Claude in "what this project is" — not to be confused with Portainer stacks themselves, which are tracked separately in the stacks table.
What the registry is (and isn't)
The registry is a manual, user-curated list of projects. You decide what counts as a project — a microservice repo, a homelab service, a homelab room, a personal site. Each project can:
- Optionally link to a Portainer stack (
portainer_stack_id+portainer_endpoint_id) for redeploys. - Point at a knowledge-vault path so Brain pages under that path count as project context.
- Be filtered, tagged, owned, and described — fields that show up in the project picker and in the Claude-context bundle.
Portainer stacks are NOT projects. Stacks live in the internal/store stacks table and are managed via the Portainer-backed /api/stacks surface. A project can link to a stack, but the registry itself never auto-discovers stacks.
Creating a project
POST /api/projects (operator role). Fields:
| Field | Required | Notes |
|---|---|---|
name | yes | Display name (e.g. "ContextBay Master"). |
key | yes | Short slug, unique. Surfaced in URLs and CLI flags. |
description | no | Free-form prose; included verbatim in the Claude-context bundle. |
repo_url | no | Link to source repo; surfaced in the UI but not auto-fetched. |
tags | no | String array. Used by the list filter (?tag=foo). |
icon | no | Lucide icon name shown in the picker. |
owner | no | Free-form owner string. |
knowledge_path | no | Path inside the Brain vault to scope KB excerpts to this project. |
portainer_stack_id | no | Numeric stack id (paired with endpoint id) — enables /redeploy. |
portainer_endpoint_id | no | Portainer endpoint id where the stack lives. |
Validation: name and key are required. key must be unique — duplicates return 409 Conflict. Linking to a stack that another project already owns also returns 409.
curl -sS -X POST http://localhost:7480/api/projects \
-H "X-API-Key: cb_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Homelab — Media",
"key": "homelab-media",
"description": "Plex + ARR stack on the basement node",
"tags": ["homelab","media"],
"owner": "ops",
"knowledge_path": "homelab/media",
"portainer_stack_id": 7,
"portainer_endpoint_id": 1
}'Listing + filtering
GET /api/projects (viewer role). Query params:
tag=<t>— projects with this tag.status=<s>—active, archived, etc.has_stack=true|false— only projects with (or without) a Portainer link.q=<text>— substring match on name/description/key.include=system— include CB's built-in system projects (otherwise hidden).
PATCH /api/projects/{id} (partial update — only non-nil fields are written) and DELETE /api/projects/{id} round out the CRUD surface. Both emit project.updated / project.deleted events on the bus.
Claude-context bundle
GET /api/projects/{id}/claude-context returns a JSON bundle that summarises the project's current state. AI tooling injects it into Claude prompts so the model starts grounded.
The bundle (internal/project/context.go) contains:
| Field | Notes |
|---|---|
project | The full project record (name, key, description, tags, owner, repo_url). |
stack | If linked: stack id, endpoint id, status, compose ref. Null otherwise. |
containers | Containers in the linked stack with name/image/state/health/uptime. |
knowledge | Markdown excerpt from the project's knowledge_path, plus last-updated timestamp. |
planner | Roll-up: open issues, in-progress, done last 7d, recent issues, active sprint. |
recent_events | Activity events filtered to the project's container names (deploys, restarts, etc). |
warnings | Non-fatal issues encountered while assembling the bundle (KB page missing, stack not found, etc). |
meta | generated_at + cache_hint_seconds. |
Pass ?verbose=1 to expand the knowledge excerpt (full page rather than the default summary), get more recent events, and disable truncation. Useful for agentic flows that want the maximum grounding.
Local-mode: scan for git repos
GET /api/projects/local is a separate, simpler endpoint. It walks one level deep under a configured root (defaults to /data/projects; override via the projects_scan_path setting) and reports every directory containing a .git.
For each found repo it shells out to git for:
branch(current HEAD branch).is_dirty(anygit status --porcelainoutput).remote_url(origin).last_commit(subject + relative time).
This endpoint is read-only and never writes to the registry — it's a quick way for the AI to see what's actually checked out on the master's data volume. Pair it with the registry by importing one of the scan results into a real project record via POST /api/projects when you decide it's worth tracking.
Redeploying the linked stack
POST /api/projects/{id}/redeploy (operator role). The handler:
- Looks up the project. Returns 404 if unknown.
- Refuses with
409 Conflictif no Portainer stack is linked. - Calls the configured
StackRedeployer.RedeployStack(endpointID, stackID)— currently the Portainer client. Portainer pulls fresh images and recreates the stack. - Returns
{"status":"redeployed"}on success.
The action is audited and emits a project.redeployed event. Failures from Portainer surface as 502 Bad Gateway with the Portainer error in the body.
Related
- API Reference — Project endpoints + the AI surface that consumes the claude-context bundle.
- Architecture — where projects, stacks, and the planner sit in the data model.

