Find an endpoint (118)
118 endpoints
- GET /api/v1/info
- GET /api/v1/health
- GET /api/v1/entries
- POST /api/v1/entries
- GET /api/v1/entries/recent
- POST /api/v1/entries/search
- GET /api/v1/entries/:id
- PUT /api/v1/entries/:id
- DELETE /api/v1/entries/:id
- POST /api/v1/entries/:id/analyze
- GET /api/v1/history/years
- GET /api/v1/history/stats/:year
- GET /api/v1/history/entries/:date
- GET /api/v1/timeline
- GET /api/v1/timeline/date-range
- GET /api/v1/timeline/emotions
- GET /api/v1/people
- POST /api/v1/people
- GET /api/v1/people/search
- GET /api/v1/people/:id
- PUT /api/v1/people/:id
- DELETE /api/v1/people/:id
- GET /api/v1/people/:id/entries
- GET /api/v1/interests
- POST /api/v1/interests
- GET /api/v1/interests/search
- GET /api/v1/interests/:id
- PUT /api/v1/interests/:id
- DELETE /api/v1/interests/:id
- GET /api/v1/interests/:id/entries
- GET /api/v1/media/:id
- GET /api/v1/media/:id/thumbnail
- GET /api/v1/media/:id/info
- GET /api/v1/entries/:entry_id/media
- POST /api/v1/entries/:entry_id/media
- POST /api/v1/entries/:entry_id/media/base64
- DELETE /api/v1/media/:id
- GET /api/v1/chat/conversations
- POST /api/v1/chat/conversations
- GET /api/v1/chat/conversations/:id
- PATCH /api/v1/chat/conversations/:id
- DELETE /api/v1/chat/conversations/:id
- POST /api/v1/chat/conversations/:id/messages
- GET /api/v1/chat/conversations/search
- GET /api/v1/chat/embedding-status
- POST /api/v1/chat/embed
- GET /api/v1/insights/status
- GET /api/v1/insights/stats
- GET /api/v1/insights/trends
- GET /api/v1/entries/:entry_id/insights
- POST /api/v1/coach/analyze/:entry_id
- POST /api/v1/coach/analyze-llm/:entry_id
- GET /api/v1/entries/:entry_id/feedback
- PATCH /api/v1/coach/feedback/:feedback_id
- GET /api/v1/coach/focus
- POST /api/v1/coach/focus
- DELETE /api/v1/coach/focus
- GET /api/v1/coach/focus/progress/:focus_area
- GET /api/v1/prompts
- GET /api/v1/prompts/random
- GET /api/v1/prompts/:id
- POST /api/v1/prompts/:id/dismiss
- POST /api/v1/prompts/:id/undismiss
- POST /api/v1/prompts/:id/used
- GET /api/v1/prompts/stats
- GET /api/v1/prompts/categories
- POST /api/v1/prompts/reset-dismissed
- GET /api/v1/photos
- GET /api/v1/photos/date-range
- GET /v1/models
- POST /v1/chat/completions
- POST /api/v1/session
- GET /api/v1/session/check
- GET /api/v1/session
- DELETE /api/v1/session
- POST /api/v1/entries/attachment
- POST /api/v1/entries/:entry_id/audio/base64
- POST /api/v1/media/:id/rotate
- POST /api/v1/photos/:id/favorite
- POST /api/v1/people/groups
- GET /api/v1/people/groups/:id/members
- PUT /api/v1/people/groups/:id/members
- GET /api/v1/people/:id/groups
- GET /api/v1/goals
- GET /api/v1/goals/entry/:entry_id
- GET /api/v1/goals/needing-follow-up
- GET /api/v1/goals/unreviewed-count
- GET /api/v1/goals/progress
- GET /api/v1/goals/category-progress
- GET /api/v1/goals/wins
- PUT /api/v1/goals/:id
- PUT /api/v1/goals/:id/status
- PUT /api/v1/goals/:id/move
- POST /api/v1/goals/:id/reviewed
- DELETE /api/v1/goals/:id
- GET /api/v1/goal-groups
- POST /api/v1/goal-groups
- PUT /api/v1/goal-groups/:id
- DELETE /api/v1/goal-groups/:id
- GET /api/v1/goal-trigger-phrases
- POST /api/v1/goal-trigger-phrases
- DELETE /api/v1/goal-trigger-phrases/:id
- GET /api/v1/graph/people/mentions
- GET /api/v1/graph/interests/mentions
- GET /api/v1/graph/data
- GET /api/v1/graph/data/full
- GET /api/v1/graph/timeline
- GET /api/v1/graph/people/cooccurrences
- GET /api/v1/graph/interests/cooccurrences
- GET /api/v1/graph/person-interest/cooccurrences
- POST /api/v1/insights/writing-metrics
- POST /api/v1/insights/analyze
- POST /api/v1/insights/reanalyze
- GET /api/v1/license
- POST /api/v1/export/limits
- POST /api/v1/export/video
- POST /api/v1/export/video/cancel
- GET /api/v1/export/download/:filename
Historicle REST API Documentation
REST API reference for Historicle's local server. Start with phone and tablet setup for browser access, or use the authentication and endpoint reference below for integrations. These endpoints run on your computer, not at historicle.ai.
Table of Contents
- Overview
- Authentication
- Error Response Format
- Endpoints
- Health & Server Info
- Entries
- History & Statistics
- Timeline
- People (Dramatis Personae)
- Interests
- Media
- Chat & Conversations
- Insights
- Writing Coach
- Life Story Prompts
- Photos
- OpenAI-Compatible API
- Browser Session Endpoints
- Attachment Entries and Audio
- People Groups
- Goals and Goal Organization
- Connections Graph
- Additional Insight Operations
- License Status
- Photo Timeline Export
- Additional Information
- Quick Start Examples
- MCP Server (Claude Desktop Integration)
Overview
| Property | Value |
|---|---|
| Base URL | https://<server>:11435 (TLS) or http://<server>:11435 |
| API Version | v1 |
| Authentication | Bearer token or query parameter |
| Content-Type | application/json (unless otherwise noted) |
TLS / HTTPS
By default, the server uses HTTPS with a self-signed certificate. This provides encrypted communication between your devices, keeping your journal data private on your local network.
Trusting the local server: A self-signed certificate encrypts traffic, but the browser cannot independently verify the server identity. Confirm the address against Historicle Settings and trust only your own server. Do not expose the server directly to the public internet; use a VPN into your trusted network for access from elsewhere.
Browser access: The first time you visit the server URL in a browser, you'll see a security warning like "Your connection is not private" or "This site's security certificate is not trusted." This is expected. Click "Advanced" and then "Proceed to [IP address]" (or similar) to continue. You only need to do this once per browser.
curl access: Examples below use -k for initial local testing only; it skips certificate verification. For ongoing use, download your server certificate from /certificate, verify its origin, and use --cacert /path/to/certificate.pem instead:
curl -k https://10.0.0.217:11435/api/v1/infoAuthentication
Protected endpoints accept an API key or a browser session created by signing in with an API key. Scripts can send the key in the Authorization header; the web UI uses a session cookie after login.
API Key Format
hist_<32-character-random-string>Header Authentication (Preferred)
Authorization: Bearer hist_abc123...Query Parameter Authentication
For endpoints that can't send headers (e.g., <img> tags):
https://server:11435/api/v1/media/123?api_key=hist_abc123...API Key Scopes
| Scope | Permissions |
|---|---|
query |
Read-only access to entries, people, insights, conversations |
create_entry |
Create new entries and people |
manage_entries |
Update/delete entries and related records; upload, rotate, and delete media |
| admin | Full access to all endpoints |
Rotating and Revoking API Keys
To rotate a key, replace it with a newly created key and delete the old one:
- On the computer running Historicle, open Settings → Server → API Keys.
- Choose Create API Key, give the replacement a recognizable name, and select only the permissions its clients need. Copy the key when it appears; its full value is shown only once.
- Update each client that uses the old key: scripts, environment variables, saved Authorization headers, and any URLs containing an
api_keyparameter. For the web UI, open the server URL in a private browser window and sign in with the replacement key to test it. - Verify that each client can perform its intended operations with the new key.
- Use the old key's delete button in API Keys and confirm deletion. Deleting a key revokes it and its browser sessions; clients still using it must reconnect with a valid key. Reload your usual browser window and sign in with the replacement key; this replaces the test session in the private window. Journal entries and attachments are preserved.
Free accounts allow one API key. If that slot is occupied, delete the old key before creating its replacement. Plan for a brief interruption while you create the new key and update clients. Pro accounts can create the replacement before deleting the old key.
When to rotate: Replace a key immediately if it was exposed or a device holding it was lost. In that case, delete the old key first rather than waiting for clients to be updated. For routine maintenance, review keys periodically, delete unused ones, and follow any rotation schedule required by your organization. Historicle does not rotate keys automatically or require a fixed rotation interval.
Browser Sessions and Server Addresses
The web UI stores its login in a secure, HttpOnly session cookie. Sessions have a seven-day lifetime that renews during active use, so you do not need to sign in every seven days while using the app regularly.
Use a consistent server URL on each device. The cookie is tied to the server hostname or IP address: a login at https://localhost:11435 is not available at https://10.0.0.217:11435, even when both addresses reach the same computer. Switching between localhost, 127.0.0.1, and a LAN address therefore requires a separate login. Use the configured server port if you changed it from 11435.
- On the computer running Historicle, use
https://localhost:11435. - On another device, use the server computer's LAN address shown in Settings.
localhoston a phone or tablet refers to that device, not your computer.
Each API key supports one active browser session. Signing in again with the same key replaces the previous session, including when you switch addresses or browsers. The previous session must sign in again; you do not need to wait for it to expire. With Pro, create a separate key for each device or browser that needs to remain signed in independently.
The single-session rule applies to browser logins. Scripts sending a Bearer API key do not create a browser session. See Rotating and Revoking API Keys when replacing a key across clients.
Authentication Errors
| Status | Description |
|---|---|
| 401 | Missing API key, invalid key, or expired key |
| 403 | Insufficient scope for the requested operation |
Error Response Format
All errors follow OpenAI-compatible format:
{
"error": {
"message": "Human-readable error description",
"type": "error_type",
"code": "error_code"
}
}Common Status Codes
| Code | Description |
|---|---|
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Authentication failed |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Resource doesn't exist |
| 429 | Too Many Requests - Rate limited |
| 500 | Internal Server Error |
| 503 | Service Unavailable - Required service not ready |
Endpoints
Health & Server Info
GET /api/v1/info
Server information for discovery/pairing. No authentication required.
Response:
{
"name": "Historicle on MacBook-Pro",
"version": "0.1.0",
"api_version": "v1",
"openai_compatible": true,
"base_url": "https://10.0.0.217:11435"
}curl:
curl -k https://10.0.0.217:11435/api/v1/infoGET /api/v1/health
Health check with system capabilities.
Scope: query
Response:
{
"status": "ok",
"version": "0.1.0",
"capabilities": {
"rag_enabled": true,
"llm_loaded": true,
"embedding_model": true
},
"entries_count": 245,
"uptime_seconds": 3600
}Entries
GET /api/v1/entries
List entries with pagination.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 20 | Results per page (max 100) |
offset |
int | 0 | Pagination offset |
start_date |
RFC 3339 | - | Filter entries after date |
end_date |
RFC 3339 | - | Filter entries before date |
Response:
{
"entries": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "Today was a good day...",
"created_at": "2024-12-24T10:30:00Z",
"modified_at": "2024-12-24T10:30:00Z",
"word_count": 156
}
],
"total": 245,
"limit": 20,
"offset": 0
}curl:
curl -k -H "Authorization: Bearer hist_<key>" \
"https://server:11435/api/v1/entries?limit=20"POST /api/v1/entries
Create a new entry.
Scope: create_entry
Request Body:
{
"content": "Today I went to the park with @Alice...",
"created_at": "2024-12-24T10:30:00Z"
}| Field | Type | Required | Description |
|---|---|---|---|
content |
string | Yes | Entry text (supports @mentions) |
created_at |
RFC 3339 | No | Defaults to current time |
Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "Today I went to the park with @Alice...",
"created_at": "2024-12-24T10:30:00Z",
"modified_at": "2024-12-24T10:30:00Z",
"word_count": 45
}GET /api/v1/entries/recent
Get most recent entries.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 10 | Number of entries (max 100) |
POST /api/v1/entries/search
Search entries by text or semantic similarity.
Scope: query
Request Body:
{
"query": "park visit with family",
"semantic": true,
"limit": 10,
"date_range": {
"start": "2024-01-01",
"end": "2024-12-31"
}
}| Field | Type | Default | Description |
|---|---|---|---|
query |
string | Required | Search query |
semantic |
bool | true | Use semantic search (requires embedding model) |
limit |
int | 10 | Max results |
date_range |
object | - | Optional date filter |
Response:
{
"results": [
{
"entry": { ... },
"relevance_score": 0.92,
"excerpt": "I went to the park and saw..."
}
],
"total": 5
}GET /api/v1/entries/:id
Get a specific entry by ID.
Scope: query
PUT /api/v1/entries/:id
Update an entry.
Scope: manage_entries
Request Body:
{
"content": "Updated entry content..."
}DELETE /api/v1/entries/:id
Delete an entry and associated media.
Scope: manage_entries
Response: 204 No Content
POST /api/v1/entries/:id/analyze
Generate semantic search embeddings and writing/emotion insights for an entry's current content. Creating or updating an entry does not analyze it; call this once when editing is finished. Analysis runs in the background, so the response returns before it completes. Embeddings are skipped if the embedding model is not installed.
Scope: create_entry, manage_entries or admin
Response: 204 No Content
History & Statistics
GET /api/v1/history/years
Get years with entry counts.
Scope: query
Response:
[
{"year": 2024, "count": 156},
{"year": 2023, "count": 234}
]GET /api/v1/history/stats/:year
Get daily statistics for a year.
Scope: query
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
emotion |
string | Filter by dominant emotion |
Response:
[
{
"date": "2024-12-24",
"word_count": 456,
"entry_count": 2,
"dominant_emotion": "joy"
}
]GET /api/v1/history/entries/:date
Get entries for a specific date (YYYY-MM-DD format).
Scope: query
Timeline
GET /api/v1/timeline
Get timeline entries with related data (people, media, insights).
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 20 | Results per page (max 100) |
offset |
int | 0 | Pagination offset |
start_date |
YYYY-MM-DD | - | Start date filter |
end_date |
YYYY-MM-DD | - | End date filter |
person_id |
UUID | - | Filter by mentioned person |
interest_id |
UUID | - | Filter by mentioned interest |
emotion |
string | - | Filter by dominant emotion |
Response:
{
"entries": [
{
"entry": { "id": "...", "content": "...", ... },
"people": [{ "id": "...", "name": "Alice" }],
"media": [{ "id": "...", "thumbnail_url": "..." }],
"insights": { "dominant_emotion": "joy", ... }
}
],
"has_more": true,
"total_count": 456
}GET /api/v1/timeline/date-range
Get the date range of all entries.
Scope: query
Response:
{
"earliest": "2020-01-15T10:30:00Z",
"latest": "2024-12-24T10:30:00Z"
}GET /api/v1/timeline/emotions
Get available emotions for filtering.
Scope: query
Response:
["joy", "sadness", "anger", "fear", "surprise", "neutral"]People (Dramatis Personae)
GET /api/v1/people
List all people.
Scope: query
Response:
{
"people": [
{
"id": "person-uuid",
"name": "Alice",
"bio": "My best friend from college",
"tags": ["friend", "college"],
"metadata": {
"how_met": "University",
"relationship_type": "friend"
},
"created_at": "2024-01-01T00:00:00Z",
"modified_at": "2024-01-01T00:00:00Z"
}
],
"total": 42
}POST /api/v1/people
Create a new person.
Scope: create_entry
Request Body:
{
"name": "Bob",
"bio": "My brother",
"tags": ["family"],
"metadata": {
"how_met": "Family member",
"relationship_context": "Sibling"
}
}GET /api/v1/people/search
Search people by name.
Scope: query
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search term |
GET /api/v1/people/:id
Get a specific person.
Scope: query
PUT /api/v1/people/:id
Update a person.
Scope: manage_entries
DELETE /api/v1/people/:id
Delete a person.
Scope: manage_entries
GET /api/v1/people/:id/entries
Get entry IDs where a person is mentioned.
Scope: query
Response:
[
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
]Interests
Interests are topics, hobbies, or concepts that can be referenced in journal entries using #hashtag syntax. Like people, interests can have their own metadata including resources, notes, and visual customization.
GET /api/v1/interests
List all interests.
Scope: query
Response:
{
"interests": [
{
"id": "interest-uuid",
"name": "Photography",
"description": "Landscape and nature photography",
"color": "#22c55e",
"icon": "mdi:camera",
"metadata": {
"started_date": "2024-01-15",
"resources": [
{
"title": "Photography 101",
"url": "https://example.com/course",
"resource_type": "course",
"notes": "Great beginner course"
}
],
"notes": "Learning composition and lighting"
},
"created_at": "2024-01-01T00:00:00Z",
"modified_at": "2024-01-01T00:00:00Z"
}
],
"total": 15
}POST /api/v1/interests
Create a new interest.
Scope: create_entry
Request Body:
{
"name": "Hiking",
"description": "Mountain trails and outdoor exploration",
"color": "#10b981",
"icon": "mdi:hiking",
"metadata": {
"started_date": "2023-06-01",
"resources": [
{
"title": "Best Trails App",
"url": "https://trails.example.com",
"resource_type": "tool",
"notes": "Helpful for finding new routes"
}
],
"notes": "Goal: Complete 50 trails this year"
}
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Interest name (max 500 chars) |
description |
string | No | Interest description (max 10,000 chars) |
color |
string | No | Hex color code (e.g., #22c55e) |
icon |
string | No | Iconify icon name (e.g., mdi:camera) |
metadata |
object | No | Interest metadata (see below) |
InterestMetadata Structure:
| Field | Type | Description |
|---|---|---|
started_date |
string | When the user started this interest (YYYY-MM-DD) |
resources |
array | Related resources (books, websites, courses, etc.) |
notes |
string | Personal notes (max 10,000 chars) |
InterestResource Structure:
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Resource title (max 500 chars) |
url |
string | No | Resource URL (max 2,000 chars) |
resource_type |
string | Yes | Type of resource (see below) |
notes |
string | No | Notes about this resource |
ResourceType Values:
book- Books and written materialswebsite- Websites and online resourcescourse- Online or offline coursesvideo- Video contentpodcast- Podcast episodes or seriesarticle- Articles and blog poststool- Tools and applicationsother- Other resource types
Response (201):
{
"id": "interest-uuid",
"name": "Hiking",
"description": "Mountain trails and outdoor exploration",
"color": "#10b981",
"icon": "mdi:hiking",
"metadata": {
"started_date": "2023-06-01",
"resources": [
{
"title": "Best Trails App",
"url": "https://trails.example.com",
"resource_type": "tool",
"notes": "Helpful for finding new routes"
}
],
"notes": "Goal: Complete 50 trails this year"
},
"created_at": "2024-12-24T10:30:00Z",
"modified_at": "2024-12-24T10:30:00Z"
}GET /api/v1/interests/search
Search interests by name.
Scope: query
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search term (prefix match) |
Response:
[
{
"id": "interest-uuid",
"name": "Photography",
"description": "Landscape and nature photography",
"color": "#22c55e",
"icon": "mdi:camera",
"metadata": { ... },
"created_at": "2024-01-01T00:00:00Z",
"modified_at": "2024-01-01T00:00:00Z"
}
]Note: Returns up to 10 results matching the query prefix.
GET /api/v1/interests/:id
Get a specific interest.
Scope: query
Response:
{
"id": "interest-uuid",
"name": "Photography",
"description": "Landscape and nature photography",
"color": "#22c55e",
"icon": "mdi:camera",
"metadata": {
"started_date": "2024-01-15",
"resources": [],
"notes": "Learning composition and lighting"
},
"created_at": "2024-01-01T00:00:00Z",
"modified_at": "2024-01-01T00:00:00Z"
}PUT /api/v1/interests/:id
Update an interest.
Scope: manage_entries
Request Body:
{
"name": "Photography",
"description": "Updated description",
"color": "#3b82f6",
"icon": "mdi:camera-enhance",
"metadata": {
"started_date": "2024-01-15",
"resources": [
{
"title": "Advanced Photography",
"url": "https://example.com/advanced",
"resource_type": "course",
"notes": "Next level techniques"
}
],
"notes": "Progressing to advanced techniques"
}
}Response:
{
"id": "interest-uuid",
"name": "Photography",
"description": "Updated description",
"color": "#3b82f6",
"icon": "mdi:camera-enhance",
"metadata": { ... },
"created_at": "2024-01-01T00:00:00Z",
"modified_at": "2024-12-24T10:30:00Z"
}DELETE /api/v1/interests/:id
Delete an interest.
Scope: manage_entries
Response: 204 No Content
Note: Deleting an interest will cascade and remove all entry-interest associations.
GET /api/v1/interests/:id/entries
Get entry IDs where an interest is mentioned.
Scope: query
Response:
[
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
]Note: Returns an array of entry UUIDs that contain #hashtag references to this interest.
Media
GET /api/v1/media/:id
Serve original media file.
Scope: query
Response: Binary file with appropriate Content-Type
Note: For <img> tags, use query parameter auth:
<img src="https://server:11435/api/v1/media/123?api_key=hist_..." />GET /api/v1/media/:id/thumbnail
Serve thumbnail image (always JPEG, max 400x400).
Scope: query
GET /api/v1/media/:id/info
Get media metadata without the file.
Scope: query
Response:
{
"id": "media-uuid",
"entry_id": "entry-uuid",
"format": "jpeg",
"width": 1920,
"height": 1080,
"file_size": 456789,
"created_at": "2024-12-24T10:30:00Z",
"thumbnail_url": "/api/v1/media/media-uuid/thumbnail",
"original_url": "/api/v1/media/media-uuid"
}GET /api/v1/entries/:entry_id/media
List all media for an entry.
Scope: query
POST /api/v1/entries/:entry_id/media
Upload media files (multipart/form-data).
Scope: manage_entries or admin
Content-Type: multipart/form-data
Form Fields:
| Field | Description |
|---|---|
file or files |
Image file(s) to upload |
Response:
{
"attached": [{ "id": "...", ... }],
"failed": []
}POST /api/v1/entries/:entry_id/media/base64
Upload media as base64 (better for mobile Safari).
Scope: manage_entries or admin
Request Body:
{
"filename": "photo.jpg",
"data": "base64_encoded_image_data..."
}Max Size: 50MB per file
DELETE /api/v1/media/:id
Delete a media attachment.
Scope: manage_entries or admin
Chat & Conversations
GET /api/v1/chat/conversations
List all conversations.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | - | Max conversations |
Response:
[
{
"id": "conv-uuid",
"title": "Planning vacation",
"created_at": "2024-12-24T10:30:00Z",
"modified_at": "2024-12-24T10:30:00Z",
"message_count": 5
}
]POST /api/v1/chat/conversations
Create a new conversation.
Scope: manage_entries
Request Body:
{
"title": "Vacation planning"
}GET /api/v1/chat/conversations/:id
Get conversation with messages.
Scope: query
Response:
{
"id": "conv-uuid",
"title": "Planning vacation",
"messages": [
{
"id": "msg-uuid",
"role": "user",
"content": "Where should I go?",
"created_at": "2024-12-24T10:30:00Z",
"sources": []
},
{
"id": "msg-uuid-2",
"role": "assistant",
"content": "Based on your entries...",
"sources": [
{
"entry_id": "entry-uuid",
"date": "2024-06-15",
"excerpt": "I really enjoyed..."
}
]
}
]
}PATCH /api/v1/chat/conversations/:id
Update conversation title.
Scope: manage_entries
DELETE /api/v1/chat/conversations/:id
Delete a conversation.
Scope: manage_entries
POST /api/v1/chat/conversations/:id/messages
Send a message with streaming response (Server-Sent Events).
Scope: query
Request Body:
{
"content": "What did I do last summer?",
"use_rag": true
}Response: Server-Sent Events stream
SSE Event Types:
| Event | Description |
|---|---|
user_message |
User message saved |
content_delta |
Token chunk from LLM |
message_complete |
Generation finished |
assistant_message |
Full assistant response |
sources |
RAG sources used |
error |
Error occurred |
GET /api/v1/chat/conversations/search
Search conversations.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | Required | Search query |
type |
string | "hybrid" | "semantic", "text", or "hybrid" |
limit |
int | 10 | Max results |
GET /api/v1/chat/embedding-status
Get embedding status for chat messages.
Scope: query
POST /api/v1/chat/embed
Embed pending conversation messages.
Scope: manage_entries
Insights
GET /api/v1/insights/status
Get insights analysis status.
Scope: query
Response:
{
"total_entries": 245,
"analyzed_entries": 220,
"unanalyzed_entries": 25,
"coverage_percent": 89.8
}GET /api/v1/insights/stats
Get aggregate text statistics.
Scope: query
GET /api/v1/insights/trends
Get text trends over time.
Scope: query
GET /api/v1/entries/:entry_id/insights
Get insights for a specific entry.
Scope: query
Response:
{
"entry_id": "entry-uuid",
"dominant_emotion": "joy",
"emotion_scores": {
"joy": 0.8,
"sadness": 0.1,
"anger": 0.05,
"fear": 0.03,
"surprise": 0.02
},
"word_count": 456,
"analysis_timestamp": "2024-12-24T10:30:00Z"
}Writing Coach
POST /api/v1/coach/analyze/:entry_id
Analyze entry with heuristics (fast, no LLM required).
Scope: query
Response:
{
"entry_id": "entry-uuid",
"feedback": [
{
"id": "feedback-uuid",
"category": "emotion_depth",
"title": "Explore your feelings",
"suggestion": "How did these events make you feel?",
"priority": "high",
"highlight_start": 45,
"highlight_end": 78,
"acknowledged": false,
"dismissed": false
}
],
"emotion_depth_score": 42.5
}POST /api/v1/coach/analyze-llm/:entry_id
Analyze entry with LLM (richer feedback, requires loaded model).
Scope: query
GET /api/v1/entries/:entry_id/feedback
Get feedback for an entry.
Scope: query
PATCH /api/v1/coach/feedback/:feedback_id
Update feedback status.
Scope: manage_entries
Request Body:
{
"acknowledged": true,
"dismissed": false
}GET /api/v1/coach/focus
Get active writing focus.
Scope: query
POST /api/v1/coach/focus
Set writing focus area.
Scope: manage_entries
Request Body:
{
"focus_area": "similes",
"scope": "week"
}Focus Areas: emotional_depth, sensory_details, similes, metaphors, narrative_flow, character_depth, showing_not_telling, specificity
DELETE /api/v1/coach/focus
Clear active focus.
Scope: manage_entries
GET /api/v1/coach/focus/progress/:focus_area
Get progress on a focus area.
Scope: query
Life Story Prompts
GET /api/v1/prompts
List prompts with filtering.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
category |
string | - | Filter by category |
include_dismissed |
bool | false | Include dismissed prompts |
include_used |
bool | false | Include used prompts |
GET /api/v1/prompts/random
Get a random available prompt.
Scope: query
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
category |
string | Optional category filter |
GET /api/v1/prompts/:id
Get a specific prompt.
Scope: query
POST /api/v1/prompts/:id/dismiss
Dismiss a prompt.
Scope: manage_entries
POST /api/v1/prompts/:id/undismiss
Undismiss a prompt.
Scope: manage_entries
POST /api/v1/prompts/:id/used
Mark prompt as used with an entry.
Scope: manage_entries
Request Body:
{
"entry_id": "entry-uuid"
}GET /api/v1/prompts/stats
Get prompt usage statistics.
Scope: query
GET /api/v1/prompts/categories
Get all categories with counts.
Scope: query
POST /api/v1/prompts/reset-dismissed
Reset all dismissed prompts.
Scope: manage_entries
Photos
GET /api/v1/photos
Get paginated photos with various viewing modes.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 20 | Results per page (max 100) |
offset |
int | 0 | Pagination offset |
mode |
string | "chronological" | View mode (see below) |
shuffle_seed |
int | - | Seed for reproducible shuffles |
start_date |
YYYY-MM-DD | - | Start date filter |
end_date |
YYYY-MM-DD | - | End date filter |
person_id |
UUID | - | Filter by person |
emotion |
string | - | Filter by emotion |
View Modes: chronological, shuffle, clustered, seasons, emotion, people, similar
GET /api/v1/photos/date-range
Get date range of all photos.
Scope: query
OpenAI-Compatible API
These endpoints provide compatibility with tools expecting OpenAI's API format.
GET /v1/models
List available models.
Scope: query
Response:
{
"object": "list",
"data": [
{
"id": "historicle-local",
"object": "model",
"created": 1734787200,
"owned_by": "historicle"
}
]
}POST /v1/chat/completions
Generate chat completions with RAG support.
Scope: query
Request Body:
{
"model": "historicle-local",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What did I do last summer?"}
],
"stream": false,
"temperature": 0.7,
"max_tokens": 1024,
"historicle_options": {
"use_rag": true,
"rag_top_k": 5,
"date_range": {
"start": "2024-06-01",
"end": "2024-08-31"
}
}
}Send historicle_options explicitly: omitting the entire object currently disables RAG. Within an explicitly supplied object, omitted use_rag and rag_top_k default to true and 5.
Compatibility: These names apply to the next app release containing this API update. Earlier releases use lineage-local, lineage_options, and lineage_sources. The updated server still accepts the old model name and lineage_options input alias, but responses now use historicle_sources and historicle-local; integrations that read sources must update that field name. Streaming responses do not include source entries.
historicle_options:
| Field | Type | Default | Description |
|---|---|---|---|
use_rag |
bool | true | Enable RAG context retrieval |
rag_top_k |
int | 5 | Number of entries to retrieve |
date_range |
object | - | Optional date filtering |
Response (non-streaming):
{
"id": "chatcmpl-uuid",
"object": "chat.completion",
"created": 1734787200,
"model": "historicle-local",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Based on your journal entries..."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 150,
"completion_tokens": 200,
"total_tokens": 350
},
"historicle_sources": [
{
"entry_id": "entry-uuid",
"date": "2024-07-15",
"excerpt": "Went to the beach with family...",
"relevance_score": 0.92
}
]
}Browser Session Endpoints
Session login exchanges a key for a cookie; it does not require an existing session. Protected endpoints accept a valid key or session cookie unless noted. Key deletion invalidates its sessions; signing in with the same key replaces its previous active session.
POST /api/v1/session
Body: {"api_key":"hist_..."}. Returns success, expires_at (Unix seconds), key_name, scopes, and optionally session_replaced. Sets an HttpOnly, SameSite=Lax cookie (Secure over HTTPS). Invalid or expired keys return 401.
GET /api/v1/session/check
Public authentication check. Returns {"authenticated":false} when signed out, or session information (authenticated, key_name, scopes, expires_at) when signed in.
GET /api/v1/session
Requires authentication. Returns session information; an API-key request can be authenticated without a browser-session expiry.
DELETE /api/v1/session
Requires authentication. Logs out the current browser session and clears its cookie. Does not delete the API key.
Attachment Entries and Audio
POST /api/v1/entries/attachment
Scope: create_entry. Body: {} or {"created_at":"2026-10-04T09:00:00-05:00"}. Creates an empty entry for subsequent attachments and returns an entry object (201). Use the media or audio upload route afterward.
POST /api/v1/entries/:entry_id/audio/base64
Scope: manage_entries or admin. Body: {"filename":"memo.wav","data":"BASE64_AUDIO","auto_transcribe":false}. Returns attachment results with attached and failed arrays. The HTTP upload does not transcribe: auto_transcribe is accepted for compatibility but transcription must be run from the desktop app. The route has a 100 MB body limit, including base64 overhead.
POST /api/v1/media/:id/rotate
Scope: manage_entries or admin. Body: {"degrees":90,"trim":false}; degrees must be 90, 180, or 270. Returns updated media information. Invalid rotation requests return 400; unsupported lossless operations can return 422.
POST /api/v1/photos/:id/favorite
Scope: manage_entries or admin. No body. Toggles the favorite flag and returns {"is_favorite":true} or false.
People Groups
People groups are distinct from goal groups. Read operations require query.
POST /api/v1/people/groups
Scope: create_entry or admin. Body: {"name":"Family","bio":"Close family","member_ids":["PERSON_UUID"]}. bio and member_ids are optional. Returns the created group as a person object (201).
GET /api/v1/people/groups/:id/members
Returns an array of people belonging to the group.
PUT /api/v1/people/groups/:id/members
Scope: manage_entries or admin. Body: {"member_ids":["PERSON_UUID"]}. Replaces the group's membership; an empty array clears it. Returns 204.
GET /api/v1/people/:id/groups
Returns an array of groups containing this person.
Goals and Goal Organization
Reads require query; writes require manage_entries or admin. These routes operate on goals detected from journal entries. There is no standalone POST-goal route. The desktop Goals UI is a Pro feature; route authentication and scope checks are separate from that UI gate.
Goal objects include id, entry_id, goal_text, category, status, detected_at, last_follow_up, reviewed, excerpt, matched_phrase, rating, achieved_at, group_id, and position. Optional values may be null. Categories are health, career, relationships, personal, financial, learning, creative, spiritual, community, social, or other. Status values are active, achieved, abandoned, or unknown; the UI's Completed label corresponds to achieved.
GET /api/v1/goals
Optional query status=active. Returns an array of goal objects.
GET /api/v1/goals/entry/:entry_id
Returns goals detected in the specified entry.
GET /api/v1/goals/needing-follow-up
Optional query days=30 (default 30). Returns active goals needing follow-up.
GET /api/v1/goals/unreviewed-count
Returns a JSON integer count.
GET /api/v1/goals/progress
Returns {"total_xp":0,"level":1} with the current values.
GET /api/v1/goals/category-progress
Returns an array of category, total_xp, and level objects.
GET /api/v1/goals/wins
Returns counts in completed_week, completed_month, completed_year, and completed_all_time.
PUT /api/v1/goals/:id
Body: {"goal_text":"Read more","category":"learning","rating":3,"reviewed":true}. rating may be null. Returns JSON null on success.
PUT /api/v1/goals/:id/status
Body: {"status":"achieved"}. Returns JSON null on success.
PUT /api/v1/goals/:id/move
Body: {"group_id":null,"position":0}. A null group moves the goal out of a box. Returns JSON null.
POST /api/v1/goals/:id/reviewed
No body. Marks the goal reviewed; returns JSON null.
DELETE /api/v1/goals/:id
Dismisses the detection so editing the source entry does not immediately recreate it. Does not delete the journal entry. Returns JSON null.
GET /api/v1/goal-groups
Returns an array of groups with id, label, color, position, and created_at.
POST /api/v1/goal-groups
Body: {"label":"This year","color":"#336699"}. Optional color; new groups are appended. Returns the created group (201).
PUT /api/v1/goal-groups/:id
Body: {"label":"This year","color":"#336699","position":0}. Omitted position defaults to 0. Returns JSON null.
DELETE /api/v1/goal-groups/:id
Deletes the group; returns JSON null.
GET /api/v1/goal-trigger-phrases
Returns an array of id, phrase, and created_at objects.
POST /api/v1/goal-trigger-phrases
Body: {"phrase":"I intend to"}. Returns the created phrase (201).
DELETE /api/v1/goal-trigger-phrases/:id
Removes the custom phrase; returns JSON null.
Connections Graph
All graph routes require authentication via the router. The current handlers do not require an additional named scope. These read-only routes take no body or query parameters.
GET /api/v1/graph/people/mentions
Returns an array of mention counts (id, name, count, color, is_group).
GET /api/v1/graph/interests/mentions
Returns interest mention counts with the same shape.
GET /api/v1/graph/data
Returns nodes and edges for the basic connections graph.
GET /api/v1/graph/data/full
Returns nodes and edges including co-occurrence relationships.
GET /api/v1/graph/timeline
Returns time-bucketed graph data for the timeline visualization, including its start date and day count.
GET /api/v1/graph/people/cooccurrences
Returns an array with entity1_id, entity1_type, entity2_id, entity2_type, and count for people pairs.
GET /api/v1/graph/interests/cooccurrences
Returns co-occurrence objects for interest pairs.
GET /api/v1/graph/person-interest/cooccurrences
Returns co-occurrence objects for person/interest pairs.
Additional Insight Operations
POST /api/v1/insights/writing-metrics
Scope: query. Body: {"content":"Text to analyze"}. Returns writing metrics computed from the supplied text; this is not an LLM chat endpoint.
POST /api/v1/insights/analyze
Scope: manage_entries or admin. Body: {"batch_size":100} (optional batch size). Analyzes a batch of entries missing insights. Returns analyzed_count and remaining.
POST /api/v1/insights/reanalyze
Scope: manage_entries or admin. Body: {} or {"batch_size":100}. Reanalyzes existing entries and returns analyzed_count and remaining.
License Status
GET /api/v1/license
Requires authentication, with no additional scope check in the handler. Returns the desktop license status, including is_pro; it does not purchase or restore a license. Manage purchases in the desktop app.
Photo Timeline Export
The current export format is gif; MP4 is not implemented. The filters object accepts optional start_date, end_date, person_id, emotion, has_embedding, and is_favorite fields; {} selects all photos. The desktop export UI is a Pro feature. HTTP operations require the scopes listed below.
POST /api/v1/export/limits
Scope: query. Body: {"filters":{},"mode":"one_picture_per_day"}. Mode can also be all_pictures. Returns estimated_frames, optional estimated_size_mb, and warnings.
POST /api/v1/export/video
Scope: manage_entries or admin. Example body:
{
"config": {
"mode": "one_picture_per_day",
"format": "gif",
"resolution": "720p",
"frame_duration_ms": 500,
"order": "oldest_first",
"filters": {}
}
}Resolution: 480p, 720p, or 1080p. Order: oldest_first, newest_first, or random. The response is an SSE stream with progress, completed, or error events. Completion includes download_url. The current implementation buffers export progress before delivering it, so do not assume live frame-by-frame updates.
POST /api/v1/export/video/cancel
Scope: manage_entries or admin. Returns 200, but cancellation is currently a no-op because export state is not shared across requests. Do not rely on this route to stop an export.
GET /api/v1/export/download/:filename
Scope: query. Downloads the temporary exported file. Use the filename or URL returned by the completion event rather than constructing a path. The download handler removes the temporary file after reading it.
Additional Information
Date/Time Formats
| Format | Example | Usage |
|---|---|---|
| RFC 3339 | 2024-12-24T10:30:00Z |
API timestamps |
| Date only | 2024-12-24 |
Date filters, history endpoints |
Pagination
All list endpoints support pagination:
{
"entries": [...],
"total": 245,
"limit": 20,
"offset": 0
}- Default limit: 20
- Maximum limit: 100
- Use
offsetfor pagination
Rate Limiting
Requests are rate-limited per API key. If exceeded:
{
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error",
"code": "rate_limit_exceeded"
}
}Server-Sent Events (SSE)
Streaming endpoints use SSE format:
event: content_delta
data: {"type":"content_delta","content":"Hello "}
event: content_delta
data: {"type":"content_delta","content":"world!"}
event: message_complete
data: {"type":"message_complete"}CORS
CORS headers are configured for all origins. OPTIONS preflight requests don't require authentication.
Quick Start Examples
Create an Entry
curl -k -X POST \
-H "Authorization: Bearer hist_your_key_here" \
-H "Content-Type: application/json" \
-d '{"content":"Today I learned about the Historicle API!"}' \
https://10.0.0.217:11435/api/v1/entriesSearch Entries
curl -k -X POST \
-H "Authorization: Bearer hist_your_key_here" \
-H "Content-Type: application/json" \
-d '{"query":"vacation","semantic":true,"limit":5}' \
https://10.0.0.217:11435/api/v1/entries/searchChat with RAG
curl -k -X POST \
-H "Authorization: Bearer hist_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"model": "historicle-local",
"messages": [{"role":"user","content":"What did I do last week?"}],
"stream": false,
"historicle_options": {"use_rag": true}
}' \
https://10.0.0.217:11435/v1/chat/completionsUpload Photo
curl -k -X POST \
-H "Authorization: Bearer hist_your_key_here" \
-F "file=@photo.jpg" \
https://10.0.0.217:11435/api/v1/entries/ENTRY_ID/mediaMCP Server (Claude Desktop Integration)
Historicle includes an MCP (Model Context Protocol) server that allows AI assistants like Claude Desktop to interact with your journal data directly.
Requires: Pro license
How It Works
The MCP server runs as a headless process using stdio transport (JSON-RPC over stdin/stdout). Claude Desktop launches it automatically when configured.
Setup for Claude Desktop
Locate your Claude Desktop config file:
~/Library/Application Support/Claude/claude_desktop_config.jsonAdd the Historicle MCP server configuration:
{ "mcpServers": { "historicle": { "command": "/Applications/Historicle.app/Contents/MacOS/historicle", "args": ["--mcp-stdio"] } } }Restart Claude Desktop
Running Manually (Testing)
/Applications/Historicle.app/Contents/MacOS/historicle --mcp-stdioThe server communicates via stdin/stdout using JSON-RPC 2.0.
Available MCP Tools
The standard --mcp-stdio launch is read-only. The table includes the handler's write-capable tools for completeness, but create_entry, update_entry, create_person, and create_interest are not advertised or accepted by that launch. search_entries is advertised only when the embedding service is available.
| Tool | Description |
|---|---|
read_entry |
Read the full content of a specific journal entry by ID |
get_recent_entries |
Get the most recent journal entries (default: 5, max: 20) |
get_entries_by_date |
Get journal entries within a specific date range |
create_entry |
Create a new journal entry (requires explicit user permission) |
update_entry |
Update the content of an existing journal entry. Automatically re-syncs @mention and #hashtag relationships and regenerates search embeddings. Requires explicit user permission. |
get_life_context |
Get a natural language briefing about the journal owner: their identity, key relationships, top interests, and journaling statistics. Call this first to understand who the user is. |
get_entry_media |
Get the list of photo/image attachments for a specific journal entry. Returns metadata (filename, dimensions, format, favorite status) but not file contents. |
get_journal_stats |
Get journal statistics including date range, total entries/words, and counts of people/interests. Use this first to understand the scope of the journal before fetching entries. |
create_person |
Create a new person to track in the journal. Use this before referencing someone with @mentions. Requires explicit user permission. |
get_entries_by_person |
Find journal entries mentioning a specific person |
get_entries_by_person_and_interest |
Find entries that mention BOTH a specific person AND a specific interest/hashtag. Use this for queries like 'camping with Liam' or 'times I went hiking with Sarah'. |
list_people |
List all tracked people in the journal with their mention counts, bios, and relationship tags (e.g., son, wife, colleague, father-in-law) |
create_interest |
Create a new interest/hashtag to track in the journal. Use this before referencing a topic with #hashtags. Requires explicit user permission. |
get_entries_by_interest |
Find journal entries with a specific hashtag/interest |
list_interests |
List all tracked interests/hashtags in the journal with their descriptions, colors, mention counts, and associated resources (books, websites, articles, etc.) |
list_goals |
List the user's goals detected from their journal, with category (e.g. health, career, learning), status (active, achieved, abandoned), detection date, and importance rating. Optionally filter by status. Use for questions like 'what are my active goals?' or 'what goals have I completed?'. |
get_goals_for_entry |
Get the goals detected in a specific journal entry (by entry ID). |
get_goals_needing_follow_up |
Find active goals the user hasn't mentioned recently (no follow-up in N days, default 30). Use for 'which goals am I neglecting?' or 'what should I check in on?'. |
search_entries_by_emotion |
Find journal entries by emotional tone. Supported emotions: joy, sadness, anger, fear, love, surprise, gratitude, excitement, pride, positive (happy emotions), negative (sad/angry/fearful emotions). |
search_entries |
Semantic search across journal entries using natural language queries |
Example Interactions
Once configured, you can ask Claude Desktop questions like:
- "What have I written about recently?"
- "Search my journal for entries about hiking"
- "Who are the people mentioned in my journal?"
- "What topics do I write about most?"
Claude will use the MCP tools to query your local Historicle database and provide answers based on your actual journal content.
Troubleshooting
"MCP server requires a Pro license"
- Upgrade to Pro in Historicle Settings to enable MCP functionality
Server not connecting
- Verify the path to the Historicle binary is correct
- Check that Historicle is installed in /Applications
- Historicle and MCP can run concurrently using SQLite WAL. If busy, let imports or restores finish and verify the data directory.
- App Store/TestFlight data is discovered in the sandbox container. With multiple installations, pass
--data-dir /absolute/path/to/dataafter--mcp-stdio. - If license validation is stale, open Historicle and restore or validate your Pro purchase.
Logs
- MCP mode writes diagnostic logs to files so stdout remains clean for JSON-RPC.
- Use the log-directory action in Historicle Settings to find the logs. Startup failures can also be reported on stderr.