Keep REST API reference

The Keep API lets you search notes and saved items together, update Markdown notes, fetch saved content, and manage sources. All responses are JSON unless noted otherwise.

Base URL

https://keep.md/api

Authentication

All requests require a Bearer token in the Authorization header. You can create a personal API key from your settings. See API keys for personal, connected client, and read-only scoped keys.

curl https://keep.md/api/me \
  -H "Authorization: Bearer $KEEP_API_KEY"

Connected client keys

Use a current personal key to create a named library credential for an AI tool or other client. The new key is returned once and can read and write Items, Notes, tags, collections, and highlights.

curl https://keep.md/api/connected-clients \
  -X POST \
  -H "Authorization: Bearer $KEEP_PERSONAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Codex"}'

GET /api/connected-clients lists active clients. Revoke one without affecting other credentials with POST /api/connected-clients/revoke and a JSON body containing its id. Connected client keys cannot create or revoke credentials.

Rate limits

Free accounts can save unlimited links. Paid plans count full-content items per billing cycle. When a paid plan limit is reached, write requests that would add another full-content item return 429 with error: "quota_reached". Read requests are never limited.

Errors

Errors return a JSON object with an error field and an appropriate HTTP status code.

// 401 -- missing or invalid token
{ "error": "unauthorized" }

// 429 -- plan limit reached
{
  "error": "quota_reached",
  "plan": "plus",
  "limitScope": "billing_cycle",
  "linkLimit": 500,
  "linkCount": 500
}

// 404 -- item not found
{ "error": "not_found" }

GET /api/me

Returns your plan, limits, and usage counts.

curl https://keep.md/api/me \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "plan": "plus",
  "linkLimit": 500,
  "linkCount": 42,
  "linkCountMonth": 12,
  "linkCountPeriod": 12,
  "linkCountLifetime": 87
}

GET /api/stats

Usage statistics for a date range.

since -- start of range (timestamp or ISO date). until -- end of range (optional).

curl "https://keep.md/api/stats?since=2026-06-01" \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "total": 12,
  "byStatus": { "stashed": 10, "flagged": 2 },
  "range": { "since": "...", "until": "...", "count": 12 }
}

GET /api/items

List your saved items. Archived items are excluded by default.

since / until -- filter by time. status -- comma-separated filter (for example stashed,flagged). tags -- comma-separated tag names or slugs. collection -- collection id, name, or slug. include=content -- include structured content plus rendered markdown. limit -- 1-1000, default 200. offset -- pagination offset.

curl "https://keep.md/api/items?collection=x-articles&tags=agent-tooling&limit=10&include=content" \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "items": [
    {
      "id": "a1b2c3",
      "url": "https://example.com/article",
      "title": "Example Article",
      "status": "stashed",
      "collectionName": "X Articles",
      "collectionSlug": "x-articles",
      "tagSlugs": ["agent-tooling"],
      "createdAt": 1709251200,
      "contentAvailable": true,
      "contentMarkdown": "---\ntitle: \"Example Article\"\n---\n\nThe full extracted markdown...",
      "content": {
        "schemaVersion": 2,
        "meta": {
          "frontmatter": {
            "title": "Example Article",
            "source": "https://example.com/article",
            "content_type": "article"
          }
        },
        "items": {
          "content": {
            "format": "markdown",
            "markdown": "The full extracted markdown...\n"
          }
        }
      }
    }
  ],
  "limit": 10,
  "offset": 0,
  "count": 1
}

Search your saved items by title, URL, notes, tags, and semantic similarity.

q is required. Supports the same since, until, status, tags, collection, include=content, limit, and offset params as /api/items.

curl "https://keep.md/api/items/search?q=react%20hooks&collection=x-articles&tags=agent-tooling&limit=10" \
  -H "Authorization: Bearer $KEEP_API_KEY"

Search notes and saved items together. q is required. Use types=item,note to choose which resources to search. mode accepts lexical, semantic, or hybrid.

Project and path values help current handoffs rank higher. Add exactProject=true or exactPath=true when they should be hard filters.

curl "https://keep.md/api/search?q=note%20sync&types=item,note&mode=hybrid&limit=10" \
  -H "Authorization: Bearer $KEEP_API_KEY"

Search results contain a stable resource type and ID, a short snippet, and the matched field. Full content stays out of the response unless you add include=content.

Notes

Notes are Markdown documents with revision checks. A write must include the current expectedRevision, so one writer cannot silently overwrite another writer's work.

endpointuse
POST /api/notesCreate a note
GET /api/notesList notes
GET /api/notes/search?q=...Search notes by text
GET /api/notes/exportDownload all notes as a Markdown ZIP
GET /api/notes/:idRead a note
PATCH /api/notes/:idReplace note fields
POST /api/notes/:id/appendAppend Markdown
POST /api/notes/:id/archiveArchive a note
POST /api/notes/:id/restoreRestore a note
GET /api/notes/:id/historyRead revision history

Create a note:

curl -X POST https://keep.md/api/notes \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "OAuth verification handoff",
    "bodyMarkdown": "## Current state\n\nThe review passed.",
    "properties": { "kind": "handoff", "state": "open" },
    "clientRequestId": "handoff-2026-07-21"
  }'

Append a later result using the revision returned by the previous read or write:

curl -X POST https://keep.md/api/notes/note_123/append \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "expectedRevision": 2,
    "bodyMarkdown": "## Result\n\nThe production check passed.",
    "clientRequestId": "production-check-1"
  }'

A stale revision returns 409 with error: "revision_conflict". Read the note again, merge the newer work, and retry. Send Accept: text/markdown to GET /api/notes/:id when you want a full frontmatter document.

Open the authenticated Note page at https://keep.md/notes/:id to read its Markdown, properties, references, and recent history.

Download every current and archived Note as a ZIP with one Markdown file per Note:

curl https://keep.md/api/notes/export \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  --output keep-notes.zip

The route requires notes.read. Each file preserves the stable Note ID, revision, timestamps, archive state when present, custom properties, and Markdown body.

Note sources and context

Attach a saved item or highlight to a note:

curl -X POST https://keep.md/api/notes/note_123/item-links \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "itemId": "a1b2c3",
    "highlightId": "hl_123",
    "relation": "evidence",
    "annotation": "Supports the retrieval decision."
  }'

relation accepts source, evidence, example, inspiration, or annotation. Repeating the same attachment returns the existing link.

Read the note with bounded source summaries and highlights:

curl "https://keep.md/api/notes/note_123/context?history=1" \
  -H "Authorization: Bearer $KEEP_API_KEY"

Add content=1 when full Item content is needed. sourceLimit, historyLimit, and contentBytes keep the response within a useful size. A missing or archived Item is marked unavailable without failing the whole Note response.

Remove an attachment with DELETE /api/notes/:id/item-links/:linkId.

GET /api/items/:id

Get a single Item by ID. This returns both the structured content object and the rendered Markdown when content is available. If the Item has saved highlights, the response includes a highlights array. Callers with notes.read also receive a bounded connectedNotes array with the Note, relationship, and optional annotation. The field is omitted when the caller cannot read Notes.

curl https://keep.md/api/items/a1b2c3 \
  -H "Authorization: Bearer $KEEP_API_KEY"

GET /api/items/:id/highlights

List the highlights attached to one item.

curl https://keep.md/api/items/a1b2c3/highlights \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "highlights": [
    {
      "id": "hl_123",
      "itemId": "a1b2c3",
      "text": "Important selected text",
      "prefix": "Text before the highlight",
      "suffix": "Text after the highlight",
      "note": "Optional note",
      "createdAt": 1709251200,
      "updatedAt": 1709251200
    }
  ]
}

GET /api/highlights/:id

Get one highlight by ID.

curl https://keep.md/api/highlights/hl_123 \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "highlight": {
    "id": "hl_123",
    "itemId": "a1b2c3",
    "text": "Important selected text",
    "prefix": "Text before the highlight",
    "suffix": "Text after the highlight",
    "note": "Optional note",
    "createdAt": 1709251200,
    "updatedAt": 1709251200
  }
}

POST /api/items/:id

Update item metadata or state.

Supports title, notes, tags, collectionId, collectionIds, archived, and processed. Collection inputs accept ids, names, or slugs.

curl -X POST https://keep.md/api/items/a1b2c3 \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Better title", "tags": ["AI Research"], "collectionIds": ["x-articles"], "processed": true }'
{
  "id": "a1b2c3",
  "title": "Better title",
  "tags": ["AI Research"],
  "tagSlugs": ["ai-research"],
  "collectionName": "X Articles",
  "collectionSlug": "x-articles",
  "processedAt": 1709251200
}

GET /api/tags

List the distinct tags currently used by your visible items. Each tag includes its display name and slug.

curl "https://keep.md/api/tags" \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "tags": [
    { "name": "X Articles", "slug": "x-articles" },
    { "name": "Agent Tooling", "slug": "agent-tooling" }
  ]
}

GET /api/collections

List your collections. Each collection includes an id, display name, and slug.

curl "https://keep.md/api/collections" \
  -H "Authorization: Bearer $KEEP_API_KEY"

POST /api/collections

Create a collection by name. If it already exists, the existing one is returned.

curl -X POST "https://keep.md/api/collections" \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "X Articles" }'

GET /api/items/:id/content

Returns the rendered markdown as plain text. Response headers include x-content-size and x-content-truncated.

curl https://keep.md/api/items/a1b2c3/content \
  -H "Authorization: Bearer $KEEP_API_KEY"

POST /api/ingest

Save a URL. Keep fetches the page, normalizes it into structured content, and renders markdown from that contract. Duplicate URLs are deduplicated automatically.

curl -X POST https://keep.md/api/ingest \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/article" }'
{
  "ok": true,
  "id": "a1b2c3",
  "url": "https://example.com/article",
  "extracted": true
}

POST /api/items/archive

Archive items by ID. Archived items are hidden from the default list.

curl -X POST https://keep.md/api/items/archive \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["a1b2c3"] }'
{ "requested": 1, "archived": 1 }

POST /api/items/delete

Permanently delete items by ID.

curl -X POST https://keep.md/api/items/delete \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["a1b2c3"] }'
{ "requested": 1, "deleted": 1 }

GET /api/feed

Returns unprocessed items with structured content plus rendered markdown. Designed for AI tools and scripts that use your saved items as context. Supports the same since, until, q, tags, collection, limit, and offset params as /api/items/search.

curl "https://keep.md/api/feed?limit=5" \
  -H "Authorization: Bearer $KEEP_API_KEY"

POST /api/items/mark-processed

Mark items as processed so they no longer appear in the feed.

curl -X POST https://keep.md/api/items/mark-processed \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["a1b2c3", "d4e5f6"] }'
{ "processed": 2 }

GET /api/items/changes

Stream item change events for delta sync. Use this to keep an external system in sync without re-listing your whole library: poll with a cursor and apply only what changed. For push delivery instead of polling, see Webhooks.

Requires a personal API key. A scoped key only returns changes for items inside its scope.

cursor -- opaque cursor from a previous response's nextCursor. Omit it on the first call. updated_since -- only return changes at or after this time (timestamp or ISO date). tags -- comma-separated tag names or slugs. collection -- collection id, name, or slug. limit -- 1-1000, default 200. include=content -- include structured content plus rendered markdown on the attached item.

curl "https://keep.md/api/items/changes?updated_since=2026-06-01&limit=100" \
  -H "Authorization: Bearer $KEEP_API_KEY"
{
  "events": [
    {
      "id": "evt_123",
      "type": "item.tagged",
      "changedAt": 1709251200000,
      "itemId": "a1b2c3",
      "beforeScope": {
        "status": "stashed",
        "tagSlugs": ["reading"],
        "collectionIds": ["col_1"]
      },
      "afterScope": {
        "status": "stashed",
        "tagSlugs": ["reading", "ai"],
        "collectionIds": ["col_1"]
      },
      "payload": { "changedFields": ["tags"], "tagSlugsAdded": ["ai"] },
      "item": {
        "id": "a1b2c3",
        "url": "https://example.com/article",
        "title": "Example Article"
      }
    }
  ],
  "nextCursor": "...",
  "hasMore": false,
  "limit": 100
}

Each event carries the event type, the itemId, and beforeScope / afterScope snapshots of the item's status, tags, and collections. The current item is attached when it still exists and is in scope; item.deleted events have no attached item. When a scoped key or filter is used and an item leaves that scope, an item.removed_from_scope event is emitted so you can drop it on your side.

Pass nextCursor as the cursor on your next request and keep going while hasMore is true. Event types match the webhook events.

GET /api/sources

List your sources and subscriptions.

curl https://keep.md/api/sources \
  -H "Authorization: Bearer $KEEP_API_KEY"

POST /api/sources

Create an RSS, YouTube, X articles, or email inbox source.

curl -X POST https://keep.md/api/sources \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "rss", "feedUrl": "https://example.com/feed.xml" }'

POST /api/sources/:id/delete

Remove a source.

curl -X POST https://keep.md/api/sources/src_123/delete \
  -H "Authorization: Bearer $KEEP_API_KEY"

openapi.json