Base URL
https://app.dmu.email/api/v1
Authentication
Bearer dmk_… API key
Rate limit
120 requests a minute per key

Follow changes

GET/sync

Returns what changed since your last call, so you can keep a copy of conversations, mailboxes and labels without listing them all again.

Needs the permissionsync:read

Call it once without since before you list what you mean to keep. It answers with no items and a cursor. Then list, and from then on call it with since set to the last cursor you got.

Each item is the thing as it is now, as the key’s owner sees it. A delete means they can no longer see it: it was deleted, moved out of their sight, or their access ended.

Messages aren’t included. Fetch Get a thread for the conversations you keep.

Changes are kept for 30 days. A cursor older than that answers 410 cursor_expired: start again without since.

Query parameters

sincestringoptional
The cursor of your last call. Leave it out the first time.
limitnumberoptional
1 to 500, or 100 if you leave it out.

Returns

A list of changes, and beside it cursor, hasMore and reset. Each change has:

typestring
thread, mailbox or label.
idstring
The thing that changed.
opstring
upsert, or delete when the key’s owner can no longer see it.
dataobject
With upsert: a thread as List threads gives it plus mailboxId, unread, labelIds and assigneeIds; a mailbox as List mailboxes does; a label’s id, name, colour, scope, teamIds and mailboxIds, without counts. A thread’s mailboxId is null for a conversation started in DMU Mail before anything was sent, until its starter chooses a mailbox.
cursorstring
Pass it as since next time. It’s never null.
hasMoreboolean
True when there’s more: call again at once.
resetboolean
True when who can see what changed wholesale: discard what you hold, list it again, and carry on from cursor.

Errors

400bad_cursor
since isn’t a cursor this endpoint gave.
400bad_limit
limit isn’t a whole number from 1 to 500.
401invalid_key
The key is wrong, expired or revoked. With no Authorization header at all, the code is missing_credentials.
403missing_scope
The key doesn’t have sync:read. The X-Required-Scope header names what’s missing.
410cursor_expired
The cursor is older than the 30 days of changes kept.
429rate_limited
More than 120 requests a minute from this key. Wait for Retry-After seconds.
curl "https://app.dmu.email/api/v1/sync?since=c_9f2a%E2%80%A6&limit=100" \
  -H "Authorization: Bearer dmk_7f3a9c2e1b4d_Xq8v…"

Response

200 OK

{
  "data": [
    {
      "type": "thread",
      "id": "a41c93f0",
      "op": "upsert",
      "data": {
        "id": "a41c93f0",
        "subject": "Invoice INV-2026-0841",
        "status": "closed",
        "mailboxId": "9e53e6ba",
        "unread": false,
        "labelIds": ["5b0e2c71"],
        "assigneeIds": []
      }
    },
    {
      "type": "label",
      "id": "e2a4f809",
      "op": "delete"
    }
  ],
  "cursor": "c_9f2b…",
  "hasMore": false,
  "reset": false
}

Every response wraps its result in data. A page of threads adds nextCursor, which is null on the last page.