HTTP API reference
Every FastAPI endpoint Personify exposes — request shape, response shape, curl examples.
The Personify HTTP API is a FastAPI app served by Uvicorn. npm start runs it
at http://127.0.0.1:18765 and serves the UI at http://localhost:18766.
An auto-generated Swagger UI lives at /docs.
The core endpoints are grouped here by purpose. The API has no authentication — keep it on 127.0.0.1 or behind a reverse proxy. See self-hosting for production guidance.
All POST bodies are JSON. All responses are JSON unless noted. The UI also uses
/api/... routes for exports, vaults, embeddings, repo intake, runs, items, and
MCP status; Swagger at /docs is the generated source of truth.
Health
GET /health
Liveness check. Returns immediately, does not touch the database.
Response
{ "status": "ok" }Example
curl http://localhost:18765/healthSources & stats
GET /sources
List active sources registered in the database (i.e. sources that have at least one ingested item or registered export).
Response
[
{ "slug": "chatgpt", "label": "ChatGPT", "created_at": "2026-04-12T08:14:00Z" },
{ "slug": "gmail", "label": "Gmail", "created_at": "2026-04-12T09:01:00Z" }
]Example
curl http://localhost:18765/sourcesGET /stats
Aggregate counts: items, exports, runs, and per-source / per-account breakdowns.
Response
{
"items": 128402,
"exports": 12,
"runs": 14,
"items_per_source": { "gmail": 82140 },
"items_per_account": { "myname@example.com": 82140 },
"sources": [{ "slug": "gmail", "label": "Gmail" }],
"accounts": [{ "handle": "myname@example.com", "display_name": null }]
}Example
curl http://localhost:18765/statsSearch
POST /search
Full-text search over item_text.body using Postgres' built-in FTS. Best for keyword and phrase queries.
Request
{
"query": "rust borrow checker",
"limit": 25,
"source": "github"
}| Field | Type | Default | Notes |
|---|---|---|---|
query | string | required | Search expression. |
limit | int | 25 | Maximum hits returned. |
source | string | optional | Restrict to a single source slug. |
Response
[
{
"id": 4127,
"source": "github",
"account": "my-org",
"kind": "code",
"ts": "2025-11-04T12:14:00Z",
"title": "src/borrow.rs",
"snippet": "fn check_borrow(...) ...",
"score": 0.412
}
]Example
curl -X POST http://localhost:18765/search \
-H "Content-Type: application/json" \
-d '{"query":"rust borrow checker","limit":10,"source":"github"}'POST /semantic-search
pgvector cosine similarity search over embedded chunks. Requires the optional embeddings extra and a vault embed pass.
Request
{
"query": "the talk where someone explained ownership with a library metaphor",
"limit": 25,
"source": null
}Same shape as /search. The query is embedded with the same model used to embed your items.
Response
Same shape as /search, but vector results include distance and chunk_idx
instead of score (lower distance = more similar).
Example
curl -X POST http://localhost:18765/semantic-search \
-H "Content-Type: application/json" \
-d '{"query":"library metaphor for ownership","limit":10}'Items & timeline
GET /items/{id}
Retrieve a single item with its full text body, media references, and tags.
Response
{
"id": 4127,
"source": "github",
"account": "my-org",
"kind": "code",
"ts": "2025-11-04T12:14:00Z",
"title": "src/borrow.rs",
"body": "fn check_borrow(...) { ... }",
"media": [],
"tags": [{ "key": "topic", "value": "rust" }],
"metadata": { "repo": "my-org/borrowctl", "path": "src/borrow.rs" }
}Example
curl http://localhost:18765/items/4127GET /timeline
Items with non-null timestamps, in a date range, most recent first.
Query parameters
| Param | Type | Description |
|---|---|---|
start | ISO 8601 datetime | Inclusive lower bound. |
end | ISO 8601 datetime | Exclusive upper bound. |
source | string | Restrict to a source slug. |
limit | int | Default 200. |
Response
[
{ "id": 4127, "source": "github", "account": "my-org", "kind": "code", "ts": "2025-11-04T12:14:00Z", "title": "src/borrow.rs" }
]Example
curl "http://localhost:18765/timeline?start=2025-11-01&end=2025-12-01&source=github"Graph
GET /graph/entities/search
Search entities by name or alias substring.
Query parameters
| Param | Type | Description |
|---|---|---|
q | string | Required. Substring matched against name, canonical_name, and aliases. |
type | string | Optional. One of the 22 entity types. |
limit | int | Default 20. |
Response
[
{ "id": 81, "type": "Project", "name": "Personify", "canonical_name": "personify" }
]Example
curl "http://localhost:18765/graph/entities/search?q=personify&type=Project"POST /graph/entities
Create an entity manually (most are extracted automatically during ingest).
Request
{
"type": "Project",
"name": "Personify",
"aliases": ["personify", "the vault"]
}Response — the created entity.
Example
curl -X POST http://localhost:18765/graph/entities \
-H "Content-Type: application/json" \
-d '{"type":"Project","name":"Personify"}'GET /graph/entities/{id}
Get an entity with all its aliases and item-grounded evidence.
Response
{
"entity": { "id": 81, "type": "Project", "name": "Personify", "canonical_name": "personify" },
"aliases": [{ "id": 9, "alias": "the vault", "normalized_alias": "the vault", "source": null }],
"evidence": [
{ "id": 22, "source_type": "item", "source_id": 4127, "quote": "Personify is a local-first ..." }
]
}GET /graph/entities/{id}/neighborhood
Walk the graph outward from an entity. depth is capped at 2.
Query parameters
| Param | Type | Description |
|---|---|---|
depth | int | 1 (default) or 2. |
Response
{
"center": { "id": 81, "type": "Project", "name": "Personify" },
"nodes": [{ "id": 81, "type": "Project", "name": "Personify" }],
"edges": [{ "source_entity_id": 81, "target_entity_id": 14, "relationship_type": "USES" }]
}GET /graph/entities/{id}/context
LLM-friendly grounding payload: the entity, its neighborhood, evidence snippets, and suggested follow-up queries. Designed to be dropped into a prompt.
Response
{
"entity": { "...": "..." },
"related_entities": [],
"relationships": [],
"evidence": [{ "item_id": 4127, "snippet": "..." }],
"suggested_queries": ["how does Personify ingest GitHub repos?"]
}POST /graph/relationships
Create a relationship.
Request
{
"source_entity_id": 81,
"target_entity_id": 14,
"relationship_type": "USES"
}relationship_type is one of the 18 enumerated types — see architecture.
Response — the created relationship.
Vault management
GET /api/vaults
Return the active vault plus discovered vault profiles.
curl http://localhost:18765/api/vaultsPOST /api/vaults
Create and initialize a named vault. This creates the Postgres database inside
the existing Docker container, creates the filesystem root under ./vaults/,
applies schema, seeds parser sources, and optionally activates it.
curl -X POST http://localhost:18765/api/vaults \
-H "Content-Type: application/json" \
-d '{"name":"work","activate":true}'POST /api/vaults/{name}/activate
Switch the running process to an existing vault.
curl -X POST http://localhost:18765/api/vaults/work/activateMCP control
GET /api/mcp/status
Return whether the UI-gated HTTP MCP transport is enabled.
curl http://localhost:18765/api/mcp/statusPOST /api/mcp/start
Open the HTTP MCP gate mounted at /mcp.
POST /api/mcp/stop
Close the HTTP MCP gate. When closed, /mcp returns 503.
Schema
The Swagger UI at http://localhost:18765/docs is the live, generated truth. The shapes shown above are the stable surface; if you're writing a client, generate it from the OpenAPI document at /openapi.json.