Guide

Connect emeris to your other tools

Three doors. Tokens open all of them, and you make tokens under Settings → Integrations.

Tokens

A token is a password for one tool. Make one per tool in Settings → Integrations, tick what it may do, and copy it once.

Tokens look like emr_ followed by 40 letters and digits. If one leaks, revoke it in Settings and make a new one; the old one stops working immediately. Send it as a header: Authorization: Bearer emr_…. If a tool can't set headers, put it on the URL as ?token=emr_… instead.

Capture into your inbox

Send a POST with a line of text to one address and it lands in your personal inbox. Any tool with a "webhook" or "HTTP request" step can do it.

POST https://api.emeris.app/functions/v1/inbox-webhook
Authorization: Bearer emr_…
Content-Type: application/json

{"text": "Call the accountant about Q3", "source": "zapier", "external_id": "row-1042"}
FieldRequiredWhat it does
textyesThe item. Up to 2,000 characters.
notesnoExtra detail, up to 10,000 characters.
urlnoA link, added to the notes.
sourcenoName of the sending tool, e.g. zapier. Shown on the item.
external_idnoYour id for the item. Send the same id twice and you get one item, not two. An Idempotency-Key header does the same job.

The body can be JSON or a plain form post (application/x-www-form-urlencoded), whichever your tool sends. The reply is JSON: 201 with the new item's id, or 200 if that external_id was already captured.

To check a token without adding anything, open the same address in a browser with ?token=emr_… on the end, or send a GET. You get your name and the token's permissions back.

Capture recipes

Zapier

  1. Pick any trigger, then add the action Webhooks by Zapier → POST.
  2. URL: https://api.emeris.app/functions/v1/inbox-webhook. Payload type: json (form works too).
  3. Data: text = the thing to capture; source = zapier; external_id = the trigger's record id.
  4. Headers: Authorization = Bearer emr_….

Make

  1. Add an HTTP → Make a request module.
  2. URL as above, method POST, body type Raw, content type JSON.
  3. Request content: {"text": "{{1.subject}}", "source": "make", "external_id": "{{1.id}}"}.
  4. Add a header Authorization with value Bearer emr_….

n8n

  1. Add an HTTP Request node, method POST, URL as above.
  2. Authentication: Generic → Header Auth, name Authorization, value Bearer emr_….
  3. Body content type JSON with text, source (n8n) and external_id.

iPhone Shortcut

  1. New shortcut: Ask for Input (text) → Get Contents of URL.
  2. URL as above. Method POST. Headers: Authorization = Bearer emr_….
  3. Request body JSON: text = Provided Input, source = shortcuts.
  4. Add it to the share sheet and you can capture a web page from any app: set url to the shared URL.

Slack workflow

  1. In Slack, open Workflow Builder, start from a reaction or a form.
  2. Add the step Send a web request: method POST, URL as above.
  3. Headers: Authorization = Bearer emr_…. Body: text = the message text, source = slack, external_id = the message timestamp.

Command line

curl -X POST https://api.emeris.app/functions/v1/inbox-webhook \
  -H "Authorization: Bearer emr_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Renew the domain", "source": "shell"}'

Get events out

Add a web address in Settings → Integrations → Outbound webhooks and emeris sends it a signed message when something happens. Use it as the trigger in Zapier, Make or n8n.

EventWhenIn data
task.scheduledA task is put on the calendar, by you or by autoscheduletask, event (calendar id, start, end, tz), via: manual or auto
task.rescheduledA scheduled task is moved in emeristask, old_event, new_event
task.unscheduledA task is taken off the calendartask, removed_event
task.completedA task is finishedtask, completed_at, event if it was scheduled
done.loggedSomething is logged as done by handentry (id, title, logged_at, project)
inbox.capturedAn item lands in the inbox, from the app, email or a webhookitem (id, text, source)
booking.createdSomeone books time through a booking linkbooking (id, link, start, end, tz, hosts). No guest name or email.

Every message has the same shape:

{
  "id": "6f1c…",
  "type": "task.completed",
  "created_at": "2026-09-05T09:12:44+00:00",
  "user_id": "…",
  "api_version": "2026-09-01",
  "data": { "task": { "id": "…", "title": "Renew the domain", "project": { "id": "…", "title": "Company setup" } }, "completed_at": "…" }
}

Headers on each request: X-Emeris-Event (the type), X-Emeris-Delivery (unique per attempt), X-Emeris-Timestamp (unix seconds) and X-Emeris-Signature. Reply with any 2xx within 10 seconds. Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours, then dropped. After 20 dropped messages in a row the webhook pauses; re-enable it in Settings once the receiver is fixed.

Only public https addresses. emeris refuses http://, private and local addresses, and follows no redirects. Moves made directly in Google Calendar don't produce task.rescheduled; emeris reads those on the next Week view, not the moment they happen.

Check a signature

Take the timestamp header, a dot, and the raw body; HMAC-SHA256 it with your webhook secret; compare with the v1= value in the signature header.

JavaScript (Node)

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody must be the exact bytes received, not re-serialised JSON.
export function verifyEmeris(headers, rawBody, secret) {
  const ts = headers["x-emeris-timestamp"];
  const sig = (headers["x-emeris-signature"] || "").replace(/^v1=/, "");
  if (!ts || !sig) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5-minute window
  const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return expected.length === sig.length &&
    timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(sig, "hex"));
}

Python

import hmac, hashlib, time

def verify_emeris(headers: dict, raw_body: bytes, secret: str) -> bool:
    ts = headers.get("X-Emeris-Timestamp", "")
    sig = headers.get("X-Emeris-Signature", "").removeprefix("v1=")
    if not ts or not sig:
        return False
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Zapier, Make and n8n webhook triggers accept the message without checking; the signature matters when you write your own receiver.

Add emeris to Claude Code

emeris has a read-only MCP server. Point Claude Code, Claude Desktop or Cursor at it with a Read token and ask about your day, your week, your projects and your free time.

Make a token with Read in Settings → Integrations, then in a terminal:

claude mcp add --transport http emeris https://api.emeris.app/functions/v1/mcp \
  --header "Authorization: Bearer emr_…"

Then try: "What did I finish this week?", "What's on today?", "Which projects have no next action?", "When am I free on Thursday?"

Cursor

Add this to ~/.cursor/mcp.json (or the project's .cursor/mcp.json):

{
  "mcpServers": {
    "emeris": {
      "url": "https://api.emeris.app/functions/v1/mcp",
      "headers": { "Authorization": "Bearer emr_…" }
    }
  }
}

Claude Desktop

Settings → Developer → Edit Config, then the same block as Cursor under mcpServers.

What the assistant can see

ToolAnswers
get_todayThe day's timeline, Top 3, next actions by context, done today
get_weekSeven days of task blocks, meetings, done counts and hours by project
list_tasks, search_tasksTasks by status, project, context; title and notes search
get_done_log, get_time_statsWhat was finished, and where the hours went, by project, category, energy or day
list_projects, get_review_stateProjects and their next actions; what a weekly review would look at
get_availabilityYour own free windows from your calendars

Read-only. Nothing over this connection can create, change or delete anything in emeris. Every tool is marked read-only for the client, and the server has no write path.

Claude.ai's web "custom connectors" require OAuth, which this server does not offer yet, so they can't connect. Claude Code, Claude Desktop and Cursor work with the token header.

Limits and errors

ReplyMeaning
401No token, an unknown token, or a revoked one.
403The token exists but lacks the permission: Capture for the inbox address, Read for the assistant.
404Integrations aren't switched on for this account yet.
413Too big: the body is over 32 KB, or a field is over its limit.
429Slow down. Capture allows 60 requests a minute per token and 1,000 a day; the assistant allows 120 calls a minute. The Retry-After header says how long to wait.

Stuck? support@emeris.app.