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.

Sessions are sliding: when a request authenticated by the cookie arrives with less than half of JWT_EXPIRY_HOURS left, the response carries a fresh auth_token cookie. The renewal stops at JWT_MAX_SESSION_HOURS after login, and also once the user has been deleted or has changed their password. A session already issued keeps working until it expires. Bearer tokens (JWT or API) are never refreshed.

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

Authorization: Bearer meerkat_<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

Employment History

Method Path Description
GET /contacts/:id/employment-history List entries, current and most recent first
POST /contacts/:id/employment-history Create an entry
PUT /contacts/:id/employment-history/:eid Update an entry
DELETE /contacts/:id/employment-history/:eid Delete an entry
POST /contacts/:id/employment-history/:eid/end End a current entry (sets end_date to the optional date query parameter, YYYY-MM-DD, or else today in UTC); 400 if already ended

POST / PUT body:

{
  "organization": "Acme Inc.",
  "department": "Engineering",
  "job_title": "Developer",
  "role": "Team lead",
  "start_date": "2021-03",
  "end_date": "",
  "notes": ""
}

At least one of organization, department, job_title or role is required (entries backfilled from a contact with only a job title have no organization). start_date and end_date accept YYYY, YYYY-MM or YYYY-MM-DD; an empty end_date marks the entry as current, and end_date must not be before start_date. Responses wrap the entry (or list) in an employment_history key.

Every change recomputes the contact’s organization, department, job_title and role from the current entry with the latest start_date. Entries can be embedded in GET /contacts with includes=employment_history, and are included in GET /contacts/:id as employment_histories (not when the fields filter is used). Webhook events: employment_history.created, employment_history.updated, employment_history.deleted.

Creating a contact with these fields, PUT /contacts/:id, importing, or a CardDAV sync with non-empty values updates the current entry, or creates one if none exists. Changing a value overwrites the current entry, so to record a job change and keep the previous job, end the entry and create a new one through the endpoints above.

PUT /contacts/:id keeps the stored value of any of these fields that is blank or missing, so it can’t clear them. Importing and CardDAV sync replace all four unless all four are blank. Blanking all four does not end or delete the entry: the values are re-derived from it (end or delete the entry instead).

PUT /contacts/:id, CardDAV and remote address book sync treat values identical to the most recently ended entry as stale: the values are re-derived from the current entry, or emptied if there is none, and no entry is created or changed. To reopen a job through the API, create a new entry through the endpoints above.

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

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 meerkat_… 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

Method Path Description
GET /health Health check (no auth, no versioning). Accessible directly (without prepending base URL).

Back to top

Meerkat CRM - Self-hosted personal contact management

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