Find an endpoint (118)

118 endpoints

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

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/info

Authentication

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:

  1. On the computer running Historicle, open Settings → Server → API Keys.
  2. 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.
  3. Update each client that uses the old key: scripts, environment variables, saved Authorization headers, and any URLs containing an api_key parameter. For the web UI, open the server URL in a private browser window and sign in with the replacement key to test it.
  4. Verify that each client can perform its intended operations with the new key.
  5. 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. localhost on 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/info

GET /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 materials
  • website - Websites and online resources
  • course - Online or offline courses
  • video - Video content
  • podcast - Podcast episodes or series
  • article - Articles and blog posts
  • tool - Tools and applications
  • other - 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 offset for 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/entries

Search 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/search

Chat 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/completions

Upload 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/media

MCP 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

  1. Locate your Claude Desktop config file:

    ~/Library/Application Support/Claude/claude_desktop_config.json
  2. Add the Historicle MCP server configuration:

    {
      "mcpServers": {
        "historicle": {
          "command": "/Applications/Historicle.app/Contents/MacOS/historicle",
          "args": ["--mcp-stdio"]
        }
      }
    }
  3. Restart Claude Desktop

Running Manually (Testing)

/Applications/Historicle.app/Contents/MacOS/historicle --mcp-stdio

The 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/data after --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.