My agentic slop goes here. Not intended for anyone else!
README.md

Immiche - Immich API Client Library#

A clean Eio-based OCaml library for interacting with Immich instances, focusing on people and face recognition data.

Overview#

Immiche provides a straightforward API for interacting with Immich's people management endpoints. It uses the Requests library for HTTP operations and follows Eio patterns for concurrency and resource management.

Features#

  • Fetch all people from an Immich instance
  • Search for people by name
  • Fetch individual person details
  • Download person thumbnails
  • Full Eio integration (no Lwt dependency)
  • Type-safe API with result types for error handling

API#

Types#

(* Client type - encapsulates session with connection pooling *)
type ('clock, 'net) t

type person = {
  id: string;
  name: string;
  birth_date: string option;
  thumbnail_path: string;
  is_hidden: bool;
}

type people_response = {
  total: int;
  visible: int;
  people: person list;
}

Client Creation#

create#

Create an Immich client with connection pooling.

val create :
  sw:Eio.Switch.t ->
  env:< clock: _ ; net: _ ; fs: _ ; .. > ->
  ?requests_session:('clock, 'net) Requests.t ->
  base_url:string ->
  api_key:string ->
  unit -> ('clock, 'net) t

Parameters:

  • sw - Eio switch for resource management
  • env - Eio environment (provides clock, net, fs)
  • requests_session - Optional Requests session for connection pooling. If not provided, a new session is created.
  • base_url - Base URL of the Immich instance (e.g., "https://photos.example.com")
  • api_key - API key for authentication

Returns: An Immich client configured for the specified instance

API Functions#

All API functions take a client as their first parameter. The client automatically handles:

  • Connection pooling (reuses TCP connections)
  • Authentication (API key set as default header)
  • Base URL configuration

fetch_people#

Fetch all people from an Immich instance.

val fetch_people : ('clock, 'net) t -> people_response

search_person#

Search for people by name.

val search_person : ('clock, 'net) t -> name:string -> person list

fetch_person#

Fetch details for a specific person.

val fetch_person : ('clock, 'net) t -> person_id:string -> person

download_thumbnail#

Download a person's thumbnail image.

val download_thumbnail :
  ('clock, 'net) t ->
  fs:_ Eio.Path.t ->
  person_id:string ->
  output_path:string ->
  (unit, [> `Msg of string]) result

Example Usage#

Basic Usage#

open Eio.Std

let () =
  Eio_main.run @@ fun env ->
  Switch.run @@ fun sw ->

  (* Create client once with connection pooling *)
  let client = Immiche.create ~sw ~env
    ~base_url:"https://photos.example.com"
    ~api_key:"your-api-key" () in

  (* Fetch all people - connection pooling automatic *)
  let response = Immiche.fetch_people client in
  Printf.printf "Total people: %d\n" response.total;

  (* Search for a person - reuses connections *)
  let results = Immiche.search_person client ~name:"John" in

  (* Download first result's thumbnail *)
  match results with
  | person :: _ ->
      let result = Immiche.download_thumbnail client
        ~fs:(Eio.Stdenv.fs env)
        ~person_id:person.id
        ~output_path:"thumbnail.jpg" in
      begin match result with
      | Ok () -> print_endline "Thumbnail downloaded!"
      | Error (`Msg err) -> Printf.eprintf "Error: %s\n" err
      end
  | [] -> print_endline "No results found"

Sharing Connection Pools#

You can share a Requests.t session across multiple API clients for maximum connection reuse:

Eio_main.run @@ fun env ->
Switch.run @@ fun sw ->

  (* Create shared Requests session *)
  let shared_session = Requests.create ~sw env in

  (* Create multiple clients sharing the same connection pools *)
  let immich1 = Immiche.create ~sw ~env ~requests_session:shared_session
    ~base_url:"https://photos1.example.com"
    ~api_key:"key1" () in

  let immich2 = Immiche.create ~sw ~env ~requests_session:shared_session
    ~base_url:"https://photos2.example.com"
    ~api_key:"key2" () in

  (* Both clients share the same connection pools! *)
  let people1 = Immiche.fetch_people immich1 in
  let people2 = Immiche.fetch_people immich2 in
  ()

Dependencies#

  • eio - Concurrent I/O library
  • requests - HTTP client library
  • ezjsonm - JSON parsing
  • uri - URI encoding
  • fmt - Formatting
  • ptime - Time handling

Implementation Notes#

  • Uses Requests.t for session-based HTTP requests with connection pooling
  • Authentication via x-api-key header (set as default on session creation)
  • Direct Eio style - no Lwt dependencies
  • Connection pools shared across derived sessions
  • Extracted and adapted from bushel/bin/bushel_faces.ml

Benefits#

Connection Pooling - Automatic TCP connection reuse across requests ✅ Clean API - Session-level configuration passed once, not per-request ✅ Injectable Sessions - Can share Requests.t across multiple libraries ✅ Type Safety - Client parameterized by clock/net types ✅ Immutable Configuration - Derived sessions don't affect original

License#

ISC