API Reference
Introduction
The xbrain memory-api is a FastAPI application running on port 8000 inside the Docker Compose stack. It is the single write/read interface for all memory data — whether the source is LibreChat, Open WebUI, a LangGraph agent, or the Chrome extension.
All endpoints (except GET /v1/healthz) require two headers:
| Header | Value | Description |
|---|---|---|
Authorization |
Bearer {token} |
Google ID token (from OAuth login) or GitHub OAuth token. Bridge services use BRIDGE_SHARED_SECRET-signed JWTs. |
X-Team-Scope |
{team_scope} |
Team identifier. Must match the team_scope field in any payload. Requests with mismatched scopes return 400. |
Base URL: https://api.grooveos.app (replace with your domain)
Authentication
xbrain supports four authentication mechanisms (post-Phase 12):
-
xbrain session token (
xbt_): Issued byPOST /v1/auth/github/signinafter a successful GitHub App sign-in. This is the primary token used by the web app and Chrome extension for all subsequent calls. -
GitHub user-to-server token (
ghu_): Short-lived (~8h) access token issued by the GitHub App. Stored Fernet-encrypted on the user row, looked up via an HMACgithub_access_token_hashpartial index. Auto-refreshed via the pairedghr_refresh token (~6 months). - Google OAuth (legacy): Standard Google ID token from the OAuth consent screen. Still accepted for backward compatibility; new sign-ins should use the GitHub App path.
-
Bridge JWT: HMAC-signed JWT using
BRIDGE_SHARED_SECRET. Used by internal bridge services (librechat-bridge, openwebui-bridge) that act on behalf of users without forwarding their tokens.
Phase 12 — GitHub App migration
As of Phase 12 (2026-05-17), xbrain authenticates via the xbrain GitHub App
(Client ID Iv23liVnZvIN0Lo6isof). The legacy GITHUB_API_PAT long-lived
token is removed from the server env; server-to-server calls to GitHub use App JWTs +
installation tokens (ghs_, ~1h TTL). See
GitHub Auth for the full flow.
bash — authenticating requests# xbrain session token (Phase 12 — returned by /v1/auth/github/signin)
curl https://api.grooveos.app/v1/me \
-H "Authorization: Bearer $XBT_TOKEN" \
-H "X-Team-Scope: excalibur"
# Google ID token (legacy)
curl https://api.grooveos.app/v1/me \
-H "Authorization: Bearer $GOOGLE_ID_TOKEN" \
-H "X-Team-Scope: excalibur"
# GitHub user token (ghu_ — short-lived, auto-refreshed via ghr_)
curl https://api.grooveos.app/v1/me/github \
-H "Authorization: Bearer $GHU_TOKEN" \
-H "X-Team-Scope: excalibur"
Health
GET /v1/healthz
Health check endpoint. No authentication required. Used by Docker Compose healthcheck and load balancers.
| Property | Value |
|---|---|
| Auth required | No |
| Response | 200 OK |
bash — health checkcurl https://api.grooveos.app/v1/healthz
# Response:
{
"status": "ok",
"version": "1.0.0"
}
User Profile
GET /v1/me
Returns the authenticated user's profile, including their team memberships and source identity (Google or GitHub).
| Property | Value |
|---|---|
| Auth required | Yes |
| Response | 200 OK |
bash — get user profilecurl https://api.grooveos.app/v1/me \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Response:
{
"id": "a1b2c3d4-...",
"email": "alice@excalibur.game",
"source_user_id": "google:116249012345678901234",
"is_admin": false,
"teams": ["excalibur", "engineering"]
}
GET /v1/me/github
Returns GitHub profile information if the user authenticated via GitHub OAuth. Includes org membership verification result.
bash — get GitHub profilecurl https://api.grooveos.app/v1/me/github \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "X-Team-Scope: excalibur"
# Response:
{
"github_username": "alicedev",
"github_id": 8234567,
"github_avatar_url": "https://avatars.githubusercontent.com/u/8234567",
"is_org_member": true,
"org": "excalibur-game"
}
Memory
The memory endpoints implement the core tagging contract. Every item must carry the 7
required fields: team_scope, project_scope,
visibility, confidence, truth_level,
source, validation_status.
POST /v1/memory/upsert
Create or update a memory item. Returns 201 on creation. If an item with the same
source already exists in this team, it is updated in place
(vector + metadata).
| Property | Value |
|---|---|
| Auth required | Yes |
| Success response | 201 Created |
| Error: missing tag field | 422 Unprocessable Entity |
| Error: team_scope mismatch | 400 Bad Request |
bash — upsert a memory item (all 7 required fields)curl -X POST https://api.grooveos.app/v1/memory/upsert \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"item": {
"content": "The Q2 fundraising target is 2M€, confirmed by Alice in the board meeting.",
"team_scope": "excalibur",
"project_scope": "fundraising",
"visibility": "team",
"confidence": 0.9,
"truth_level": "WORKING",
"source": "librechat:conv_abc123:msg_7",
"validation_status": "pending",
"metadata": {
"extracted_by": "librechat-bridge",
"conversation_id": "conv_abc123"
}
}
}'
# Response (201 Created):
{
"id": "mem_f7a3b1c9d2e4...",
"team_scope": "excalibur",
"truth_level": "WORKING"
}
truth_level is write-once via upsert
You can set truth_level when upserting. To promote a fact from
WORKING to VALIDATED or CANONICAL, use the Promotions API — you cannot PATCH
truth_level directly (returns 405).
GET /v1/memory/search
Semantic search over the team's memory using Qdrant vector similarity. Filtered by
team_scope from the X-Team-Scope header. Optionally filter
by project_scope and minimum truth level.
| Query Parameter | Type | Default | Description |
|---|---|---|---|
q | string | required | Search query text (embedded and compared against Qdrant vectors) |
project_scope | string | — | Filter to a specific project |
truth_level_min | string | EPHEMERAL | Minimum truth level to return (EPHEMERAL, WORKING, VALIDATED, CANONICAL, PUBLIC) |
limit | integer | 10 | Number of results (max 100) |
bash — semantic memory searchcurl "https://api.grooveos.app/v1/memory/search?q=fundraising+target&truth_level_min=WORKING&limit=5" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Response:
[
{
"id": "mem_f7a3b1c9d2e4...",
"content": "The Q2 fundraising target is 2M€...",
"score": 0.947,
"team_scope": "excalibur",
"project_scope": "fundraising",
"truth_level": "WORKING",
"confidence": 0.9,
"source": "librechat:conv_abc123:msg_7"
}
]
GET /v1/memory/{item_id}
Retrieve a single memory item by ID. Returns 404 if the item is not in the requesting team's scope.
bash — get a single memory itemcurl https://api.grooveos.app/v1/memory/mem_f7a3b1c9d2e4 \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
PATCH /v1/memory/{item_id}
Update a memory item's mutable fields: content, project_scope,
visibility, confidence, validation_status,
metadata.
405 — truth_level cannot be patched directly
Attempting to PATCH truth_level returns 405 Method Not Allowed.
Truth level promotion is an explicit workflow — use POST /v1/promotions
instead. This is intentional: truth level changes require justification and
(for VALIDATED and above) admin approval.
bash — update confidence and validation_statuscurl -X PATCH https://api.grooveos.app/v1/memory/mem_f7a3b1c9d2e4 \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"confidence": 0.95,
"validation_status": "human_reviewed"
}'
# Response (200 OK):
{
"id": "mem_f7a3b1c9d2e4...",
"updated_fields": ["confidence", "validation_status"]
}
# Attempting to patch truth_level:
curl -X PATCH https://api.grooveos.app/v1/memory/mem_f7a3b1c9d2e4 \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{"truth_level": "VALIDATED"}'
# Response (405 Method Not Allowed):
{
"detail": "truth_level cannot be patched directly. Use POST /v1/promotions."
}
DELETE /v1/memory/{item_id}
Delete a memory item. Removes from both Qdrant (vector) and PostgreSQL (metadata). Returns 204 No Content on success.
bash — delete a memory itemcurl -X DELETE https://api.grooveos.app/v1/memory/mem_f7a3b1c9d2e4 \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Response: 204 No Content
Conversations
POST /v1/conversations
Create a new conversation record. Conversations are the organizational unit for messages — every message belongs to a conversation.
bash — create a conversationcurl -X POST https://api.grooveos.app/v1/conversations \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"title": "Q2 Fundraising Strategy Session",
"project_scope": "fundraising",
"source": "librechat:conv_abc123"
}'
# Response (201 Created):
{
"id": "conv_uuid_...",
"title": "Q2 Fundraising Strategy Session",
"team_scope": "excalibur",
"project_scope": "fundraising",
"owner_user_id": "a1b2c3d4-...",
"created_at": "2026-05-06T10:30:00Z"
}
GET /v1/conversations
List conversations for the authenticated team. Results are paginated and scoped to
X-Team-Scope. Query params: project_scope, limit
(default 20), offset.
bash — list team conversationscurl "https://api.grooveos.app/v1/conversations?project_scope=fundraising&limit=10" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
GET /v1/conversations/{conversation_id}
Retrieve a single conversation with its metadata. Does not include messages —
use GET /v1/messages?conversation_id= for messages.
bash — get conversation by IDcurl https://api.grooveos.app/v1/conversations/conv_uuid_123 \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
Messages
POST /v1/messages
Log a message to memory-api. If the referenced conversation does not exist, it is created silently (upsert-silent pattern). Used by librechat-bridge and openwebui-bridge to stream conversation content into xbrain.
bash — log a message (bridge usage)curl -X POST https://api.grooveos.app/v1/messages \
-H "Authorization: Bearer $BRIDGE_JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"conversation_id": "conv_abc123",
"role": "user",
"content": "What is our fundraising target for Q2?",
"source": "librechat",
"user_id": "a1b2c3d4-..."
}'
# Response (201 Created):
{
"id": "msg_uuid_...",
"conversation_id": "conv_abc123"
}
Truth Level Promotions
Truth level promotion is the formal workflow for advancing a fact from EPHEMERAL through WORKING, VALIDATED, CANONICAL, and PUBLIC. Every promotion requires explicit justification. Promotions to VALIDATED and above require admin approval.
POST /v1/promotions
Submit a promotion request for a memory item.
bash — request promotion to VALIDATEDcurl -X POST https://api.grooveos.app/v1/promotions \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"item_id": "mem_f7a3b1c9d2e4",
"target_level": "VALIDATED",
"justification": "Confirmed in board meeting on 2026-05-01. Three stakeholders present."
}'
# Response (202 Accepted):
{
"promotion_id": "promo_uuid_...",
"item_id": "mem_f7a3b1c9d2e4",
"target_level": "VALIDATED",
"status": "pending",
"created_at": "2026-05-06T10:45:00Z"
}
GET /v1/promotions
List pending promotions. Admin only — requires the user's sub to be in
ADMIN_USER_SUBS. Supports query params: status (pending, approved,
rejected), item_id.
bash — list pending promotions (admin)curl "https://api.grooveos.app/v1/promotions?status=pending" \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur"
PATCH /v1/promotions/{promotion_id}
Approve or reject a pending promotion. Admin only. On approval, the memory item's
truth_level is updated atomically.
bash — approve a promotion (admin)curl -X PATCH https://api.grooveos.app/v1/promotions/promo_uuid_123 \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"decision": "approved",
"note": "Verified in board minutes. Elevating to VALIDATED."
}'
# Response (200 OK):
{
"promotion_id": "promo_uuid_123",
"decision": "approved",
"item_id": "mem_f7a3b1c9d2e4",
"new_truth_level": "VALIDATED"
}
System Prompt
GET /v1/system-prompt
Returns CANONICAL facts for the team, pre-formatted for injection into an LLM system
prompt. Filtered by X-Team-Scope and truth_level=CANONICAL.
Used by librechat-bridge, openwebui-bridge, and agent-runtime to ground every LLM
call with verified team knowledge.
bash — fetch team CANONICAL facts for system promptcurl https://api.grooveos.app/v1/system-prompt \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Response (200 OK, text/plain):
Team knowledge (CANONICAL, excalibur):
- Q2 fundraising target: 2M€ (confirmed 2026-05-01)
- Tech lead: Alice Dupont (since 2025-06-01)
- Current runway: 18 months (as of 2026-04-15)
- Primary investor: Excalibur Capital (lead), Angel Group (co-lead)
Graph
GET /v1/graph/neighbors
Query the Neo4j temporal knowledge graph for entities connected to a given entity within the team scope. Results include temporal validity of each relationship.
| Query Parameter | Type | Required | Description |
|---|---|---|---|
entity | string | Yes | Entity name to query (person, project, concept) |
team_scope | string | Yes | Team graph partition to search in |
depth | integer | No | Graph traversal depth (default: 1, max: 3) |
bash — get graph neighbors for an entitycurl "https://api.grooveos.app/v1/graph/neighbors?entity=Alice&team_scope=excalibur" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Response:
{
"entity": "Alice",
"team_scope": "excalibur",
"neighbors": [
{
"name": "Excalibur.Engineering",
"relationship": "WAS_TECH_LEAD",
"valid_from": "2025-01-15",
"valid_until": "2025-05-31"
},
{
"name": "Q2-Fundraising",
"relationship": "CONTRIBUTED_TO",
"valid_from": "2025-03-01",
"valid_until": null
},
{
"name": "Bob",
"relationship": "TRANSFERRED_ROLE_TO",
"valid_from": "2025-06-01",
"valid_until": null
}
]
}
Audit
GET /v1/audit
Returns the audit log — every write operation (upsert, delete, promotion, admin action) is logged with user, timestamp, action type, and the affected resource. Admin only.
| Query Parameter | Type | Description |
|---|---|---|
team_scope | string | Filter by team (admin sees all teams; regular user sees own) |
action | string | Filter by action type: upsert, delete, promote, approve, reject |
user_id | string | Filter by actor user ID |
limit | integer | Max results (default 50, max 500) |
offset | integer | Pagination offset |
bash — fetch audit log for a teamcurl "https://api.grooveos.app/v1/audit?team_scope=excalibur&action=promote&limit=20" \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur"
# Response:
{
"total": 42,
"items": [
{
"id": "audit_uuid_...",
"timestamp": "2026-05-06T10:45:00Z",
"user_id": "a1b2c3d4-...",
"user_email": "alice@excalibur.game",
"action": "promote",
"resource_type": "memory_item",
"resource_id": "mem_f7a3b1c9d2e4",
"detail": {"target_level": "VALIDATED", "justification": "..."}
}
]
}
Brain Monitor Phase 11
Universal feed across 7 entity types — memory_item, granola_note,
conversation, message, team_message, task,
contact — exposed via the v_brain_events view (migration
0018). All Brain Monitor endpoints require X-Team-Scope and are
gated on team membership. See the Brain Monitor guide
for the full UX.
GET /v1/brain/events
List entities for one team. Paginated, filterable by entity type and truth level. Supports
cursor-based pagination via since + cursor query params for the
30-second polling loop used by the Brain Monitor page.
| Query Parameter | Type | Description |
|---|---|---|
entity_type | string? | Filter to one type from the closed allow-list |
truth_level | string? | Filter on EPHEMERAL / WORKING / VALIDATED / CANONICAL / PUBLIC |
since | ISO 8601 | Only rows created after this timestamp (polling) |
cursor | string? | Next-page cursor returned by the previous call |
limit | integer | Page size (default 50, max 200) |
include_deleted | boolean | Surface soft-deleted rows (faded in UI) |
bash — list this team's brain eventscurl "https://api.grooveos.app/v1/brain/events?limit=20&include_deleted=false" \
-H "Authorization: Bearer $XBT_TOKEN" \
-H "X-Team-Scope: excalibur"
# Response:
{
"items": [
{
"entity_type": "memory_item",
"entity_id": "mem_abc...",
"team_scope": "excalibur",
"truth_level": "WORKING",
"deleted_at": null,
"deleted_by": null,
"source": "librechat:conv_abc123",
"created_by": null,
"created_at": "2026-05-17T12:30:00Z",
"preview": "Q2 planning is confirmed for May 15th..."
}
],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiLi4uIn0="
}
PATCH /v1/brain/events/{entity_type}/{entity_id}
Update the truth_level of one entity inline. Only the author of the row or a
team admin (team_members.role='admin') can call this; otherwise the response is
403 with the locked wording "You can only edit items you created. Contact a team admin
to modify items created by others."
bash — promote one rowcurl -X PATCH "https://api.grooveos.app/v1/brain/events/memory_item/mem_abc..." \
-H "Authorization: Bearer $XBT_TOKEN" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{"truth_level": "VALIDATED"}'
DELETE /v1/brain/events/{entity_type}/{entity_id}
Soft-delete the row. Sets deleted_at=NOW() and
deleted_by=<principal.user.id> on the underlying table, and synchronously
marks the Qdrant payload with deleted_at_ts for vector-backed entities
(memory_item, granola_note). The row is recoverable for 30 days
before brain-janitor hard-purges it from Postgres, Qdrant, and Neo4j.
bash — soft deletecurl -X DELETE "https://api.grooveos.app/v1/brain/events/task/task_xyz" \
-H "Authorization: Bearer $XBT_TOKEN" \
-H "X-Team-Scope: excalibur"
# 204 No Content
POST /v1/brain/events/{entity_type}/{entity_id}/restore
Restore a soft-deleted row. Sets deleted_at=NULL; the row becomes visible to
every list endpoint and is re-included in Qdrant search results. Subject to the same
authorization rule as PATCH/DELETE.
bash — restore within the 30-day windowcurl -X POST "https://api.grooveos.app/v1/brain/events/task/task_xyz/restore" \
-H "Authorization: Bearer $XBT_TOKEN" \
-H "X-Team-Scope: excalibur"
# 200 OK with the restored row
Superadmin endpoints — /v1/admin/brain/*
Cross-team aggregate + drill-down endpoints. Every call is gated by
assert_is_superadmin against the ADMIN_USER_SUBS env list;
/v1/admin/brain/events writes a synchronous audit_log row with
action='superadmin_brain_access' before serving any data
(if the audit write fails the response is 500, never a data leak).
| Endpoint | What it returns |
|---|---|
GET /v1/admin/brain/overview | Per-team matrix — counts × entity_type × truth_level |
GET /v1/admin/brain/storage | Per-team pg_rows + qdrant_points + minio_bytes (N/A on degrade) |
GET /v1/admin/brain/activity | 30-day events/day sparkline per team |
GET /v1/admin/brain/sources | Top-5 sources per team plus "other" bucket |
GET /v1/admin/brain/events?team_slug=<slug> | Audited cross-team drill-down (read-only) |
Lockdown — kill-switch for cross-team visibility
Setting ADMIN_USER_SUBS="" (empty) and restarting memory-api
forces all five admin endpoints to return 403 for every caller. There is no UI path to
add a superadmin — this is intentional.
GitHub App Authentication Phase 12
POST /v1/auth/github/signin
Exchanges a GitHub OAuth authorization code (issued after the user consents on the
xbrain GitHub App's consent screen) for an xbrain session token (xbt_).
On the first sign-in for a user the row is upserted in the users table; the
encrypted ghu_/ghr_ pair is stored on the same row.
| Body field | Type | Description |
|---|---|---|
code | string | OAuth code returned to the redirect URI (~10 min TTL) |
redirect_uri | string | Must match one of the App's registered callback URLs |
state | string? | CSRF nonce echoed back (recommended) |
Response shape (SigninGithubOut):
json — successful signin with org installed{
"xbt_token": "xbt_eyJ...",
"user": {"id": "...", "github_login": "alice", "primary_email": "alice@..."},
"install_required": false,
"install_url": null,
"org_login": null
}
json — install required (App NOT installed on org){
"xbt_token": null,
"install_required": true,
"install_url": "https://github.com/apps/xbrain/installations/new?state=...",
"org_login": "dejavudev"
}
When install_required: true, the frontend renders an
Install xbrain on <org> banner pointing to install_url. After the
org admin completes the install consent screen, GitHub redirects back to
https://grooveos.app/account/teams/?installation_id=<int>&setup_action=install
and the page re-fires the signin flow (hybrid lookup self-heals if the install webhook is
slow).
POST /v1/webhooks/github/installation
Webhook endpoint that GitHub calls when the xbrain App is installed, uninstalled, suspended,
unsuspended, or its repo selection changes. memory-api verifies the
X-Hub-Signature-256 HMAC over the raw request body using
GITHUB_APP_WEBHOOK_SECRET, then upserts the
installations table. Returns 200 on success, 401 on signature mismatch.
| X-GitHub-Event | action | Effect |
|---|---|---|
installation | created / new_permissions_accepted | Upsert installations row (revoked_at=NULL) |
installation | deleted | Set revoked_at=NOW() |
installation | suspend | Set suspended_at=NOW() |
installation | unsuspend | Clear suspended_at |
installation_repositories | — | Log only (no repo perms used in v1) |
Hybrid installation lookup
If the install webhook is delayed, find_installation_for_org falls back to
minting an App JWT and calling GET /orgs/{org}/installation directly, then
upserts the row idempotently. This self-heals missed webhooks within one signin request.
Teams
GET /v1/teams
List teams the authenticated user belongs to.
bash — list user teamscurl https://api.grooveos.app/v1/teams \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Response:
[
{"scope": "excalibur", "name": "Excalibur Game", "role": "admin"},
{"scope": "engineering", "name": "Engineering", "role": "member"}
]
POST /v1/admin/teams
Create a new team. Admin only. The team scope must be a unique slug (lowercase, hyphens allowed).
bash — create a team (admin)curl -X POST https://api.grooveos.app/v1/admin/teams \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"name": "Engineering",
"scope": "engineering"
}'
# Response (201 Created):
{
"id": "team_uuid_...",
"scope": "engineering",
"name": "Engineering",
"created_at": "2026-05-06T10:00:00Z"
}
Google Drive Integration
GET /v1/admin/drive/auth
Initiates the Google Drive OAuth2 flow. Redirects to Google's consent screen. Admin only. After consent, Google redirects back to the callback URL with an auth code which drive-sync exchanges for tokens.
bash — initiate Drive OAuth (redirect in browser)# This endpoint redirects — open in browser, not curl
GET https://api.grooveos.app/v1/admin/drive/auth
?team_scope=excalibur
&Authorization=Bearer+{admin_jwt}
POST /v1/admin/drive/mappings
Map a Google Drive folder to a team scope and project. Once mapped, drive-sync will watch the folder for changes and upsert new/modified files into memory-api.
bash — map a Drive folder to a projectcurl -X POST https://api.grooveos.app/v1/admin/drive/mappings \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"folder_id": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"team_scope": "excalibur",
"project_scope": "fundraising",
"truth_level": "WORKING",
"label": "Fundraising Deck Folder"
}'
# Response (201 Created):
{
"mapping_id": "map_uuid_...",
"folder_id": "1BxiMVs...",
"team_scope": "excalibur",
"label": "Fundraising Deck Folder",
"status": "active",
"webhook_registered": true
}
GET /v1/admin/drive/mappings
List all active Drive folder mappings for the team.
bash — list Drive mappingscurl https://api.grooveos.app/v1/admin/drive/mappings \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur"
POST /v1/drive/webhook
Google Drive push notification endpoint. Called by Google when a watched folder changes. This is an internal endpoint — Google calls it automatically after a mapping is registered. Validates the Google push notification signature before processing.
Internal endpoint
POST /v1/drive/webhook is called by Google, not by your application.
It is exposed via Nginx at the public URL so Google can reach it. No user auth required —
it validates the Google push notification headers instead.
Projects
POST /v1/admin/projects
Create a new project within a team. Projects are used as the project_scope
for memory items, conversations, and Drive mappings.
bash — create a project (admin)curl -X POST https://api.grooveos.app/v1/admin/projects \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"name": "Series A Fundraising",
"slug": "fundraising",
"team_scope": "excalibur",
"deploy_target": "firebase"
}'
# Response (201 Created):
{
"id": "proj_uuid_...",
"slug": "fundraising",
"name": "Series A Fundraising",
"team_scope": "excalibur",
"status": "active",
"created_at": "2026-05-06T10:00:00Z"
}
GET /v1/admin/projects
List all projects for the team. Admin only.
bash — list team projectscurl https://api.grooveos.app/v1/admin/projects \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur"
CRM & Contacts Team & Enterprise
Contact management with automatic extraction from memory items and meeting notes. All endpoints require Team or Enterprise plan — Starter returns 403. See CRM & Contacts for full documentation.
GET /v1/crm/contacts
List contacts for the authenticated team with pagination.
bashcurl "https://api.grooveos.app/v1/crm/contacts?limit=50&offset=0" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
POST /v1/crm/contacts
Create a new contact. Email must be unique within the team.
bashcurl -X POST "https://api.grooveos.app/v1/crm/contacts" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","name":"Alice Martin","company":"Acme"}'
PUT /v1/crm/contacts
Upsert a contact by (team_scope, email). Safe to call repeatedly — idempotent.
bashcurl -X PUT "https://api.grooveos.app/v1/crm/contacts" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","role":"VP Engineering"}'
PATCH /v1/crm/contacts/{contact_id}
Update fields of an existing contact.
DELETE /v1/crm/contacts/{contact_id}
Delete a contact. Logged to audit trail.
Tasks Team & Enterprise
Task management with automatic generation from meeting notes and chat messages. All endpoints require Team or Enterprise plan — Starter returns 403. See Task Tracking for full documentation including polling and chat-based task detection.
GET /v1/tasks
List tasks with optional filters. Supports since timestamp for polling.
bash# All tasks for the team
curl "https://api.grooveos.app/v1/tasks" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
# Filter by status + poll for new tasks
curl "https://api.grooveos.app/v1/tasks?status=todo&since=2026-05-07T00:00:00Z" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur"
POST /v1/tasks
Create a task manually. created_by is set to the authenticated user's ID.
Bridge JWTs are rejected (401) — this endpoint requires a real user identity.
bashcurl -X POST "https://api.grooveos.app/v1/tasks" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"title": "Send deck to investor",
"project_scope": "fundraising",
"due_date": "2026-05-14",
"visibility": "team",
"confidence": 1.0,
"truth_level": "WORKING",
"validation_status": "pending"
}'
GET /v1/tasks/{task_id}
Retrieve a single task by ID.
PATCH /v1/tasks/{task_id}
Update a task. Status changes generate a differentiated audit entry:
task.status_changed (with from/to fields)
vs task.updated for non-status changes.
bashcurl -X PATCH "https://api.grooveos.app/v1/tasks/t1a2b3c4..." \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{"status": "done"}'
DELETE /v1/tasks/{task_id}
Delete a task. Logged to audit trail.
Meeting Integration (Granola) Team & Enterprise
Granola meeting notes are automatically polled every 5 minutes and ingested as memory items + contacts + tasks. See Meeting Integration for setup instructions and the full processing pipeline.
POST /v1/admin/granola-integration
Register a Granola API key for the team. Admin only. The key is Fernet-encrypted at rest.
bashcurl -X POST "https://api.grooveos.app/v1/admin/granola-integration" \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{"api_key":"grn_live_...","default_project_scope":"general"}'
GET /v1/admin/granola-integration
Get the current Granola integration config for the team. API key is not returned (write-only).
PATCH /v1/admin/granola-integration
Update the integration (e.g. rotate the API key).
DELETE /v1/admin/granola-integration
Remove the Granola integration. The granola-sync service stops polling this team.
POST /v1/integrations/granola/ingest
Push a Granola note directly without waiting for the 5-minute poll cycle.
Atomically writes: memory item + contacts + tasks. Note-level idempotency via
source_ref — pushing the same note twice is a no-op.
bashcurl -X POST "https://api.grooveos.app/v1/integrations/granola/ingest" \
-H "Authorization: Bearer $JWT" \
-H "X-Team-Scope: excalibur" \
-H "Content-Type: application/json" \
-d '{
"note_id": "grn_note_abc123",
"title": "Investor call — Series A",
"summary": "Discussed Q2 targets. Alice will send deck by Friday.",
"participants": [{"email":"alice@example.com","name":"Alice Martin"}],
"action_items": [{"title":"Send deck","due_date":"2026-05-09"}],
"created_at": "2026-05-07T10:00:00Z"
}'
Error Codes
memory-api uses standard HTTP status codes. All error responses include a
detail field with a human-readable message.
| Code | Meaning | Common causes |
|---|---|---|
400 |
Bad Request | team_scope in payload does not match X-Team-Scope header; malformed JSON body |
401 |
Unauthorized | Missing or expired Bearer token; invalid Google/GitHub token; invalid bridge JWT signature |
403 |
Forbidden | Authenticated user is not a member of the requested team; non-admin attempting an admin-only action |
404 |
Not Found | Memory item, conversation, promotion, or project does not exist within this team scope |
405 |
Method Not Allowed | Attempted to PATCH truth_level directly — use POST /v1/promotions instead |
422 |
Unprocessable Entity | One or more required tagging fields missing from the memory item: team_scope, project_scope, visibility, confidence, truth_level, source, validation_status |
500 |
Internal Server Error | Qdrant or PostgreSQL connection failure; unhandled exception. Check docker logs memory-api. |
json — example error response{
"detail": "Missing required field: truth_level. All memory items must carry the 7 tagging contract fields."
}
Quick Reference
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/healthz | None | Health check |
| GET | /v1/me | Bearer | Authenticated user profile |
| GET | /v1/me/github | Bearer | GitHub profile + org membership |
| GET | /v1/teams | Bearer | List user's teams |
| POST | /v1/admin/teams | Admin | Create team |
| POST | /v1/memory/upsert | Bearer | Create/update memory item |
| GET | /v1/memory/search | Bearer | Semantic search |
| GET | /v1/memory/{id} | Bearer | Get memory item |
| PATCH | /v1/memory/{id} | Bearer | Update memory item (not truth_level) |
| DELETE | /v1/memory/{id} | Bearer | Delete memory item |
| POST | /v1/conversations | Bearer | Create conversation |
| GET | /v1/conversations | Bearer | List conversations |
| GET | /v1/conversations/{id} | Bearer | Get conversation |
| POST | /v1/messages | Bearer | Log message (upsert-silent) |
| POST | /v1/promotions | Bearer | Request truth level promotion |
| GET | /v1/promotions | Admin | List promotions |
| PATCH | /v1/promotions/{id} | Admin | Approve/reject promotion |
| GET | /v1/system-prompt | Bearer | CANONICAL facts for LLM prompt |
| GET | /v1/graph/neighbors | Bearer | Query temporal knowledge graph |
| GET | /v1/audit | Admin | Audit log |
| GET | /v1/admin/drive/auth | Admin | Initiate Drive OAuth |
| POST | /v1/admin/drive/mappings | Admin | Map Drive folder to team/project |
| GET | /v1/admin/drive/mappings | Admin | List Drive mappings |
| POST | /v1/drive/webhook | Drive push notification (internal) | |
| POST | /v1/admin/projects | Admin | Create project |
| GET | /v1/admin/projects | Admin | List team projects |
| POST | /v1/auth/github/signin | None | Phase 12 — GitHub App OAuth code → xbt_ token |
| POST | /v1/webhooks/github/installation | HMAC | Phase 12 — install/uninstall/suspend events |
| GET | /v1/brain/events | Bearer | Phase 11 — universal team feed (7 entity types) |
| PATCH | /v1/brain/events/{type}/{id} | Bearer | Phase 11 — update truth_level inline |
| DELETE | /v1/brain/events/{type}/{id} | Bearer | Phase 11 — soft delete (30-day window) |
| POST | /v1/brain/events/{type}/{id}/restore | Bearer | Phase 11 — restore soft-deleted |
| GET | /v1/admin/brain/overview | Superadmin | Phase 11 — cross-team counts matrix |
| GET | /v1/admin/brain/storage | Superadmin | Phase 11 — pg/Qdrant/MinIO storage per team |
| GET | /v1/admin/brain/activity | Superadmin | Phase 11 — 30-day events/day sparkline |
| GET | /v1/admin/brain/sources | Superadmin | Phase 11 — top-5 sources per team |
| GET | /v1/admin/brain/events | Superadmin | Phase 11 — audited drill-down (read-only) |