API Reference

Base URL

/api/v1

Authentication

Auth endpoints (/login, /register, /logout, /password-reset/*) are public. All other endpoints require authentication.

Admin endpoints (/admin/*) additionally require the user to have the admin flag set.

Login sets an httpOnly JWT cookie. Subsequent requests must include it (i.e. send with credentials: 'include'). The cookie name and domain are configured via COOKIE_DOMAIN and COOKIE_SECURE.

Alternatively, API tokens can be used in the Authorization header:

Authorization: Bearer mycorrhizal_<token>

API tokens are created and managed via the admin endpoints, the tokens always have normal user rights. The plaintext token is only returned once at creation time.

Error Responses

All errors follow the same structure:

{
  "code": "NOT_FOUND",
  "message": "Contact not found",
  "details": {}
}

Common error codes:

Code HTTP status
UNAUTHORIZED 401
INVALID_CREDENTIALS 401
TOKEN_EXPIRED 401
FORBIDDEN 403
NOT_FOUND 404
VALIDATION_ERROR 400
INVALID_INPUT 400
ALREADY_EXISTS 409
RATE_LIMIT_EXCEEDED 429
INTERNAL_ERROR 500

Request IDs

Every request and response carries an X-Request-ID header created by the middleware.


Endpoints

Auth

Method Path Description
POST /register Create a new user account
POST /login Authenticate and set session cookie
POST /logout Clear session cookie
POST /check-password-strength Validate a password without registering
POST /password-reset/request Send a password reset email
POST /password-reset/confirm Apply a password reset token

Users

Method Path Description
GET /users/me Get the current user
POST /users/change-password Change password
PATCH /users/language Update UI language preference
PATCH /users/date-format Update date format preference
GET /users/custom-fields Get custom field names
PATCH /users/custom-fields Update custom field names

Contacts

Method Path Description
GET /contacts List contacts (supports search and circle filter)
POST /contacts Create a contact
GET /contacts/:id Get a contact (supports filtering the returned fields)
PUT /contacts/:id Update a contact
DELETE /contacts/:id Delete a contact
POST /contacts/:id/archive Archive a contact
POST /contacts/:id/unarchive Unarchive a contact
GET /contacts/circles List all circles in use
GET /contacts/random Get five random contacts
GET /contacts/birthdays Get upcoming birthdays
POST /contacts/:id/profile_picture Upload a profile picture (multipart)
GET /contacts/:id/profile_picture Get a contact’s profile picture
GET /proxy/image Proxy an external image URL for upload preview

Relationships

Method Path Description
GET /contacts/:id/relationships List outgoing relationships
GET /contacts/:id/incoming-relationships List incoming relationships
POST /contacts/:id/relationships Create a relationship
PUT /contacts/:id/relationships/:rid Update a relationship
DELETE /contacts/:id/relationships/:rid Delete a relationship

Notes

Method Path Description
GET /contacts/:id/notes List notes for a contact
POST /contacts/:id/notes Create a note for a contact
GET /notes List unassigned notes
POST /notes Create an unassigned note
GET /notes/:id Get a note
PUT /notes/:id Update a note
DELETE /notes/:id Delete a note

Activities

Method Path Description
GET /activities List all activities
POST /activities Create an activity
GET /activities/:id Get an activity
PUT /activities/:id Update an activity
DELETE /activities/:id Delete an activity
GET /contacts/:id/activities List activities for a contact

Reminders

Method Path Description
GET /reminders List all reminders
GET /reminders/upcoming List upcoming reminders (used by dashboard)
GET /reminders/:id Get a reminder
PUT /reminders/:id Update a reminder
DELETE /reminders/:id Delete a reminder
POST /reminders/:id/complete Mark a reminder complete (creates timeline entry)
GET /contacts/:id/reminders List reminders for a contact
POST /contacts/:id/reminders Create a reminder for a contact
GET /contacts/:id/reminder-completions List completion history for a contact (timeline entries)
DELETE /reminder-completions/:id Delete a completion entry

Import

Method Path Description
POST /contacts/import/upload Upload a CSV file, returns parsed preview data
POST /contacts/import/preview Apply column mapping, returns contacts with duplicate detection
POST /contacts/import/confirm Execute the import with per-row decisions
POST /contacts/import/vcf/upload Upload a VCF file, returns contacts with duplicate detection
POST /contacts/import/vcf/confirm Execute the VCF import
POST /contacts/import/jscontact/upload Upload a JSContact (RFC 9553) JSON file, returns contacts with duplicate detection
POST /contacts/import/records Start an import from a JSON batch of neutral Card/CRM records (bulk-import API for external clients)

Bulk import contract (external clients)

The records endpoint is the repeatable bulk-import contract for external clients (e.g. the Android app): it serves both a first-run “import your contacts” prompt and a standing “Import from contacts” entry point with the same flow.

Session lifecycle. An import is a three-step, server-side-session flow:

  1. POST /contacts/import/records (JSON batch, 1–500 records) — or an upload endpoint for a file — returns an ImportPreviewResponse with a session_id, per-row duplicate detection (duplicate_match + merge_diff), and within-batch detection (batch_duplicate_of).
  2. (CSV only) POST /contacts/import/preview applies column mappings to the session.
  3. POST /contacts/import/vcf/confirm (VCF/JSContact/records) or /contacts/import/confirm (CSV) executes the import with one action per row: add, update (merge into the matched existing contact), or skip. The response is an ImportResult summary.

Idempotent confirm. Confirming a session consumes it. A retry of the same session_id within the 15-minute session window returns the original result as a no-op — it does not re-apply the import. This makes a dropped confirm response safe to retry: a client that times out mid-request can simply re-send the same confirm instead of re-uploading (which would create duplicates). To change a decision, start a new upload (new session_id).

Retry guidance. If a confirm is retried and the session has aged out (>15 min, or the server restarted), it returns 404 NOT_FOUND; the client must re-upload. The confirm endpoints are type-scoped: /contacts/import/confirm rejects records-sourced sessions and /contacts/import/vcf/confirm rejects CSV-sourced ones — a retried confirm must target the same endpoint the preview came from.

Guarantees. Sessions are scoped to the owning user; a foreign or expired session_id is 404. Sessions are held in memory only and lost on server restart. Batch limit is 500 records; the import commits per-row, so one failing row does not abort the rest. Ownership scoping, per-row decisions, and the 15-minute expiry are enforced server-side.

Export

Method Path Description
GET /export Download all data as CSV
GET /export/vcf Download all contacts as VCF (includes photos)

Network

Method Path Description
GET /graph Get contact network graph data

API Tokens

Method Path Description
GET /admin/api-tokens List all API tokens for the current user
POST /admin/api-tokens Create an API token — returns the plaintext token once
DELETE /admin/api-tokens/:id Revoke an API token

POST /admin/api-tokens body:

{ "name": "my token" }

Response includes token (the mycorrhizal_… plaintext value) only on creation. Subsequent list responses omit it.

Admin

Method Path Description
GET /admin/users List all users
GET /admin/users/:id Get a user
PATCH /admin/users/:id Update a user (e.g. set admin flag)
DELETE /admin/users/:id Delete a user

Health

All three are unauth, unversioned, and accessible directly (without prepending the base URL). See deployment.md for which probe each consumer should use.

Method Path Description
GET /health/live Liveness — is the process running? Returns immediately, no I/O. For restart policies.
GET /health/ready Readiness — can this instance serve? DB + migrations + filesystem. 503 when not. For traffic gating.
GET /health Deep health — the healthy/degraded/unhealthy roll-up, plus the build version and client-compat fields. Only 503s on a DB read failure. The per-facet breakdown (job names, integrity/restore-drill state, integration reachability) is admin-only at /admin/system-status (issue #864).

Back to top

Mycorrhizal CRM - Self-hosted personal contact management

This site uses Just the Docs, a documentation theme for Jekyll.