A friendly little APIfor your tasks.

It's nine endpoints with JSON in and out, which is plenty to bring tasks in from the tools you already use and send them back out again. Everything on this page is live, so have fun.

Base URLhttps://hey-emilo.com/api/v1

All you need is one key.

Create an API key in Settings, under Integrations, and send it as a bearer token on every request. We'll only show it to you once, so pop it somewhere safe.

  1. Open Settings → Integrations and choose Create API key. Give it a name you'll recognise in a year.

  2. Copy the key. It starts with emk_ and you won't see it again; revoke and create another if you lose it.

  3. Send it in the Authorization header. Keys belong to a workspace and act with the permissions of the admin who made them.

The header

Authorization: Bearer emk_...

Check the key works

curl https://hey-emilo.com/api/v1/me \
  -H "Authorization: Bearer emk_your_key_here"
{
  "workspace": { "id": "4c2a...", "name": "Product team", "kind": "team" },
  "key": { "name": "Zapier", "prefix": "emk_1a2b3c", "scopes": ["read", "write"] }
}

Here are the nine endpoints.

Paths are relative to the base URL, and ids are the ones you see in the app's address bar.

API endpoints: method, path and what each does
MethodPath
GET/tasksList tasks. Filter with ?status=open|done and ?project=<id>.
POST/tasksCreate a task: title, and optionally priority, due_date, assignee_id, project_id, notes.
PATCH/tasks/:idChange any of those fields, or set status to done or open.
DELETE/tasks/:idSoft-delete a task. It can be restored in the app for an hour.
GET/projectsList projects in the workspace.
POST/projectsCreate a project: name, and optionally colour.
GET/tasks/:id/commentsList comments on a task, oldest first.
POST/tasks/:id/commentsAdd a comment: body, and optionally mentions as an array of user ids.
GET/meThe workspace and key this token belongs to.

Three worked examples to try.

Copy one, swap in your key and give it a go. Ids are shortened and responses are trimmed to the fields that matter; the live API returns the full row.

Create a task

Request

curl -X POST https://hey-emilo.com/api/v1/tasks \
  -H "Authorization: Bearer emk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Send the spring price list",
    "priority": "high",
    "due_date": "2026-10-24",
    "assignee_id": "9f1e..."
  }'

Response

{
  "id": "b7d0...",
  "title": "Send the spring price list",
  "status": "open",
  "priority": "high",
  "due_date": "2026-10-24",
  "assignee_id": "9f1e...",
  "project_id": null,
  "created_at": "2026-10-22T09:14:03Z"
}

List open tasks in a project

Request

curl "https://hey-emilo.com/api/v1/tasks?status=open&project=3e8c..." \
  -H "Authorization: Bearer emk_your_key_here"

Response

{
  "data": [
    { "id": "b7d0...", "title": "Send the spring price list", "priority": "high", "due_date": "2026-10-24", "status": "open" },
    { "id": "c201...", "title": "Book the van for Friday", "priority": "medium", "due_date": "2026-10-23", "status": "open" }
  ]
}

Mark a task done

Request

curl -X PATCH https://hey-emilo.com/api/v1/tasks/b7d0... \
  -H "Authorization: Bearer emk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "status": "done" }'

Response

{ "id": "b7d0...", "status": "done", "completed_at": "2026-10-22T16:40:11Z" }

Errors

Errors come back as JSON with a single error field that says what to fix.

  • 400The body is missing something or a value is wrong. The message names the field.
  • 401No key, or a revoked one.
  • 403The key's workspace can't do that, for example writing to a paused team workspace.
  • 404No task or project with that id in this workspace.
  • 429Over the rate limit. Wait for the window in Retry-After.
{ "error": "due_date must be a date in YYYY-MM-DD format." }

A rate limit of 60 a minute.

Each key gets 60 requests per minute. If you go over, you'll get a 429 with a Retry-After header telling you how many seconds to wait. Batch your reads and you'll probably never see it.

Webhooks for six events.

Add a URL in Settings → Integrations and pick the events you care about, and Emilo will POST a JSON body to it as things happen.

  • task.created

    A task is added, by anyone or by the API.

  • task.updated

    Title, priority, due date, project or notes change.

  • task.assigned

    The assignee changes.

  • task.completed

    A task is marked done.

  • task.deleted

    A task is deleted.

  • comment.created

    A comment is added to a task.

What a delivery looks like

POST https://example.com/hooks/emilo
X-Emilo-Event: task.assigned
X-Emilo-Delivery: 7a1f...
X-Emilo-Signature: sha256=3f9c...
Content-Type: application/json

{
  "event": "task.assigned",
  "workspace_id": "4c2a...",
  "at": "2026-10-22T09:14:03Z",
  "task": { "id": "b7d0...", "title": "Send the spring price list", "assignee_id": "9f1e...", "priority": "high", "due_date": "2026-10-24" }
}

Check the signature

Every delivery is signed. The X-Emilo-Signature header is sha256= followed by the hex HMAC-SHA256 of the raw request body, using the webhook's secret from Settings as the key. Compare it before you trust the body.

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

export function verify(rawBody: string, header: string, secret: string): boolean {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  return expected.length === header.length && timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}

Retries

Answer with any 2xx within 10 seconds and the delivery is done. Anything else is retried five times with growing gaps (about 1 minute, 5 minutes, 30 minutes, 2 hours, then 12 hours) before it's marked failed. Each attempt carries the same X-Emilo-Delivery id, so you can de-duplicate on it.

Your tasks in your calendar app.

Every account has its own private calendar feed. Subscribe to it from Google Calendar, Apple Calendar or Outlook and your dated tasks will appear right alongside your plans, read-only, updating on that app's own refresh schedule.

  1. Open Settings → Integrations and copy your calendar feed URL. It contains a private token; anyone with the URL can read your dated tasks, so treat it like a password.

  2. In your calendar app, add a calendar by URL (Google calls it 'From URL', Apple 'New Subscription', Outlook 'Subscribe from web') and paste it in.

  3. Tasks with a date show as all-day events. Tasks with a time show at that time. Done tasks drop off the feed.

The feed is one-way: changes made in your calendar app don't come back to Emilo.

Sign up, then grab your key.

API keys and webhooks live under Settings → Integrations in any workspace, and your personal account is free with no limits.