Complete API reference for Historicle's REST API, enabling remote access to your journal data.
| 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) |
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.
Why self-signed certificates are safe for local use:
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: Add the -k flag to skip certificate verification:
curl -k https://10.0.0.217:11435/api/v1/infoAll endpoints except /api/v1/info require authentication via API key.
hist_<32-character-random-string>Authorization: Bearer hist_abc123...For endpoints that can't send headers (e.g., <img> tags):
https://server:11435/api/v1/media/123?api_key=hist_abc123...| Scope | Permissions |
|---|---|
query |
Read-only access to entries, people, insights, conversations |
create_entry |
Create new entries and people |
manage_entries |
Update and delete entries, conversations, feedback |
write |
Upload and delete media |
admin |
Full access to all endpoints |
| Status | Description |
|---|---|
| 401 | Missing API key, invalid key, or expired key |
| 403 | Insufficient scope for the requested operation |
All errors follow OpenAI-compatible format:
{
"error": {
"message": "Human-readable error description",
"type": "error_type",
"code": "error_code"
}
}| 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 |
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/infoHealth 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
}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"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 most recent entries.
Scope: query
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 10 | Number of entries (max 100) |
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 | false | 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 a specific entry by ID.
Scope: query
Update an entry.
Scope: manage_entries
Request Body:
{
"content": "Updated entry content..."
}Delete an entry and associated media.
Scope: manage_entries
Response: 204 No Content
Get years with entry counts.
Scope: query
Response:
[
{"year": 2024, "count": 156},
{"year": 2023, "count": 234}
]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 entries for a specific date (YYYY-MM-DD format).
Scope: query
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 the date range of all entries.
Scope: query
Response:
{
"earliest": "2020-01-15T10:30:00Z",
"latest": "2024-12-24T10:30:00Z"
}Get available emotions for filtering.
Scope: query
Response:
["joy", "sadness", "anger", "fear", "surprise", "neutral"]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
}Create a new person.
Scope: create_entry
Request Body:
{
"name": "Bob",
"bio": "My brother",
"tags": ["family"],
"metadata": {
"how_met": "Family member",
"relationship_context": "Sibling"
}
}Search people by name.
Scope: query
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search term |
Get a specific person.
Scope: query
Update a person.
Scope: manage_entries
Delete a person.
Scope: manage_entries
Get entry IDs where a person is mentioned.
Scope: query
Response:
[
"550e8400-e29b-41d4-a716-446655440000",
"550e8400-e29b-41d4-a716-446655440001"
]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.
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
}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 typesResponse (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"
}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 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"
}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 an interest.
Scope: manage_entries
Response: 204 No Content
Note: Deleting an interest will cascade and remove all entry-interest associations.
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.
Serve original media file.
Scope: query
Response: Binary file with appropriate Content-Type
Note: For <img> tags, use query parameter auth:

Serve thumbnail image (always JPEG, max 400x400).
Scope: query
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"
}List all media for an entry.
Scope: query
Upload media files (multipart/form-data).
Scope: write
Content-Type: multipart/form-data
Form Fields:
| Field | Description |
|---|---|
file or files |
Image file(s) to upload |
Response:
{
"attached": [{ "id": "...", ... }],
"failed": []
}Upload media as base64 (better for mobile Safari).
Scope: write
Request Body:
{
"filename": "photo.jpg",
"data": "base64_encoded_image_data..."
}Max Size: 50MB per file
Delete a media attachment.
Scope: write
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
}
]Create a new conversation.
Scope: manage_entries
Request Body:
{
"title": "Vacation planning"
}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..."
}
]
}
]
}Update conversation title.
Scope: manage_entries
Delete a conversation.
Scope: manage_entries
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 |
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 embedding status for chat messages.
Scope: query
Embed pending conversation messages.
Scope: manage_entries
Get insights analysis status.
Scope: query
Response:
{
"total_entries": 245,
"analyzed_entries": 220,
"unanalyzed_entries": 25,
"coverage_percent": 89.8
}Get aggregate text statistics.
Scope: query
Get text trends over time.
Scope: query
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"
}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
}Analyze entry with LLM (richer feedback, requires loaded model).
Scope: query
Get feedback for an entry.
Scope: query
Update feedback status.
Scope: manage_entries
Request Body:
{
"acknowledged": true,
"dismissed": false
}Get active writing focus.
Scope: query
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
Clear active focus.
Scope: manage_entries
Get progress on a focus area.
Scope: query
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 a random available prompt.
Scope: query
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
category |
string | Optional category filter |
Get a specific prompt.
Scope: query
Dismiss a prompt.
Scope: manage_entries
Undismiss a prompt.
Scope: manage_entries
Mark prompt as used with an entry.
Scope: manage_entries
Request Body:
{
"entry_id": "entry-uuid"
}Get prompt usage statistics.
Scope: query
Get all categories with counts.
Scope: query
Reset all dismissed prompts.
Scope: manage_entries
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 date range of all photos.
Scope: query
These endpoints provide compatibility with tools expecting OpenAI's API format.
List available models.
Scope: query
Response:
{
"object": "list",
"data": [
{
"id": "historicle-local",
"object": "model",
"created": 1734787200,
"owned_by": "historicle"
}
]
}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"
}
}
}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
}
]
}| Format | Example | Usage |
|---|---|---|
| RFC 3339 | 2024-12-24T10:30:00Z |
API timestamps |
| Date only | 2024-12-24 |
Date filters, history endpoints |
All list endpoints support pagination:
{
"entries": [...],
"total": 245,
"limit": 20,
"offset": 0
}offset for paginationRequests are rate-limited per API key. If exceeded:
{
"error": {
"message": "Rate limit exceeded",
"type": "rate_limit_error",
"code": "rate_limit_exceeded"
}
}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 headers are configured for all origins. OPTIONS preflight requests don't require authentication.
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/entriescurl -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/searchcurl -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/completionscurl -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/mediaHistoricle includes an MCP (Model Context Protocol) server that allows AI assistants like Claude Desktop to interact with your journal data directly.
Requires: Pro license
The MCP server runs as a headless process using stdio transport (JSON-RPC over stdin/stdout). Claude Desktop launches it automatically when configured.
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
/Applications/Historicle.app/Contents/MacOS/historicle --mcp-stdioThe server communicates via stdin/stdout using JSON-RPC 2.0.
| Tool | Description |
|---|---|
search_entries |
Semantic search across journal entries |
list_entries |
List recent journal entries |
get_entry |
Get a specific entry by ID |
list_people |
List people in the journal |
get_person |
Get details about a specific person |
list_interests |
List interests/hashtags |
get_interest |
Get details about a specific interest |
Once configured, you can ask Claude Desktop questions like:
Claude will use the MCP tools to query your local Historicle database and provide answers based on your actual journal content.
"MCP server requires a Pro license"
Server not connecting
Logs