Guide
Connect emeris to your other tools
Updated 5 September 2026
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.
- Capture lets the tool add items to your inbox. Zapier, Shortcuts and scripts need this.
- Read lets the tool see your ledger. An AI assistant needs this. A Read token cannot add anything.
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"}
| Field | Required | What it does |
|---|---|---|
text | yes | The item. Up to 2,000 characters. |
notes | no | Extra detail, up to 10,000 characters. |
url | no | A link, added to the notes. |
source | no | Name of the sending tool, e.g. zapier. Shown on the item. |
external_id | no | Your 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
- Pick any trigger, then add the action Webhooks by Zapier → POST.
- URL:
https://api.emeris.app/functions/v1/inbox-webhook. Payload type: json (form works too). - Data:
text= the thing to capture;source=zapier;external_id= the trigger's record id. - Headers:
Authorization=Bearer emr_….
Make
- Add an HTTP → Make a request module.
- URL as above, method POST, body type Raw, content type JSON.
- Request content:
{"text": "{{1.subject}}", "source": "make", "external_id": "{{1.id}}"}. - Add a header
Authorizationwith valueBearer emr_….
n8n
- Add an HTTP Request node, method POST, URL as above.
- Authentication: Generic → Header Auth, name
Authorization, valueBearer emr_…. - Body content type JSON with
text,source(n8n) andexternal_id.
iPhone Shortcut
- New shortcut: Ask for Input (text) → Get Contents of URL.
- URL as above. Method POST. Headers:
Authorization=Bearer emr_…. - Request body JSON:
text= Provided Input,source=shortcuts. - Add it to the share sheet and you can capture a web page from any app: set
urlto the shared URL.
Slack workflow
- In Slack, open Workflow Builder, start from a reaction or a form.
- Add the step Send a web request: method POST, URL as above.
- 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.
| Event | When | In data |
|---|---|---|
task.scheduled | A task is put on the calendar, by you or by autoschedule | task, event (calendar id, start, end, tz), via: manual or auto |
task.rescheduled | A scheduled task is moved in emeris | task, old_event, new_event |
task.unscheduled | A task is taken off the calendar | task, removed_event |
task.completed | A task is finished | task, completed_at, event if it was scheduled |
done.logged | Something is logged as done by hand | entry (id, title, logged_at, project) |
inbox.captured | An item lands in the inbox, from the app, email or a webhook | item (id, text, source) |
booking.created | Someone books time through a booking link | booking (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
| Tool | Answers |
|---|---|
get_today | The day's timeline, Top 3, next actions by context, done today |
get_week | Seven days of task blocks, meetings, done counts and hours by project |
list_tasks, search_tasks | Tasks by status, project, context; title and notes search |
get_done_log, get_time_stats | What was finished, and where the hours went, by project, category, energy or day |
list_projects, get_review_state | Projects and their next actions; what a weekly review would look at |
get_availability | Your 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
| Reply | Meaning |
|---|---|
401 | No token, an unknown token, or a revoked one. |
403 | The token exists but lacks the permission: Capture for the inbox address, Read for the assistant. |
404 | Integrations aren't switched on for this account yet. |
413 | Too big: the body is over 32 KB, or a field is over its limit. |
429 | Slow 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.