The Keep API lets you search Notes and Items, update Markdown Notes, manage Projects, fetch saved content, and manage sources. Responses are JSON unless a route states another format.
Base URL
https://keep.md/apiAuthentication
All requests require a Bearer token in the Authorization header. Create a
personal key in Connection settings.
See API keys for personal, connected client, and read-only
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, Projects, 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 is the start of the range as a timestamp or ISO date. until is the
optional end of the range.
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 and until filter by time. status is a comma-separated status
filter. tags accepts tag names or slugs. collection accepts an ID, name, or
slug. source limits results to one capture source or website.
include=content adds structured content and rendered Markdown. limit is
1-1000 and defaults to 200. offset controls pagination.
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://github.com/iannuttall/seo",
"title": "SEO CLI",
"status": "stashed",
"collectionName": "X Articles",
"collectionSlug": "x-articles",
"tagSlugs": ["agent-tooling"],
"createdAt": 1709251200,
"contentAvailable": true,
"contentMarkdown": "---\ntitle: \"SEO CLI\"\n---\n\n# SEO CLI\n\nAudit websites from the command line...",
"content": {
"schemaVersion": 2,
"meta": {
"frontmatter": {
"title": "SEO CLI",
"source": "https://github.com/iannuttall/seo",
"content_type": "repository"
}
},
"items": {
"content": {
"format": "markdown",
"markdown": "# SEO CLI\n\nAudit websites from the command line...\n"
}
},
"media": []
}
}
],
"limit": 10,
"offset": 0,
"count": 1
}GET /api/items/search
Search saved Items by title, URL, notes, tags, and meaning.
q is required. This route supports the same filters as /api/items, including
source. Set mode to lexical, semantic, or hybrid.
curl "https://keep.md/api/items/search?q=react%20hooks&collection=x-articles&tags=agent-tooling&limit=10" \
-H "Authorization: Bearer $KEEP_API_KEY"GET /api/items/:id/read
Read a saved Item without returning the whole captured document. view
defaults to overview and accepts overview, search, lines, or full.
curl "https://keep.md/api/items/a1b2c3/read?view=overview" \
-H "Authorization: Bearer $KEEP_API_KEY"
curl "https://keep.md/api/items/a1b2c3/read?view=search&q=payment%20failure&contextLines=2&limit=5" \
-H "Authorization: Bearer $KEEP_API_KEY"
curl "https://keep.md/api/items/a1b2c3/read?view=lines&lines=40:90" \
-H "Authorization: Bearer $KEEP_API_KEY"Overview returns metadata, document statistics, and headings. Search returns
at most 200 lines and 16 KB across its windows. An exact slice can contain at
most 200 lines and 32 KB. view=full is the deliberate opt-in for the whole
document.
GET /api/items/:id/highlights
Add limit and offset for a bounded highlight page. limit defaults to 25
and cannot exceed 100. The response includes hasMore and nextOffset.
GET /api/items/:id/media
Return ordered media references without the captured document or private
storage keys. limit defaults to 20 and cannot exceed 100. Pass nextOffset
back as offset to fetch the next page.
GET /api/search
Search Notes and saved Items together. q is required. Use types=item,note to
choose resource types. mode accepts lexical, semantic, or hybrid.
Project and path values help current handoffs rank higher. Add
exactProject=true or exactPath=true when they must be filters. You can also
filter Notes with kind, state, and tags.
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 snippet of at most 600
UTF-8 bytes, and the matched field. Note matches also include the current
revision and a relevant line range when Keep can locate it. Full content stays
out of the response unless you add include=content; explicit content is
shared fairly across results and reports truncation. A client can pass the
returned revision and range directly to the progressive Note read endpoint.
GET /api/context/brief
Return a bounded index of recent Notes for one Git project. project is
required. path improves ranking for directory-specific Notes, and limit is
capped at 20.
curl "https://keep.md/api/context/brief?project=https%3A%2F%2Fgithub.com%2Fiannuttall%2Fkeep&path=packages%2Fcli" \
-H "Authorization: Bearer $KEEP_API_KEY"The response contains Note IDs, titles, descriptions, kind, state, paths, tags, updated times, and stable URLs. It never includes Note bodies or attached Item content.
Notes
Notes are Markdown documents with revision checks. Replacements, metadata
changes, archive actions, and restores require the current expectedRevision.
Appends can omit it because Keep applies them to the latest revision without
discarding another writer’s appended Markdown.
Note responses use the bare ID shown by Keep. Routes also accept older IDs that
begin with note_.
| endpoint | use |
|---|---|
POST /api/notes |
Create a note |
GET /api/notes |
List compact Note manifests; add include=content for bodies |
GET /api/notes/search?q=... |
Search notes as compact result coordinates |
GET /api/notes/export |
Download all notes as a Markdown ZIP |
GET /api/notes/:id |
Read a note |
GET /api/notes/:id/read |
Read an overview, search result, or line range |
PATCH /api/notes/:id |
Update the supplied Note fields |
POST /api/notes/:id/append |
Append Markdown |
POST /api/notes/:id/archive |
Archive a note |
POST /api/notes/:id/restore |
Restore a note |
GET /api/notes/:id/history |
Read revision summaries; add include=content for bodies |
POST /api/notes/:id/revisions/:revision/restore |
Restore an older revision as a new revision |
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.",
"kind": "handoff",
"state": "open",
"properties": { "path": "packages/cli" },
"clientRequestId": "handoff-2026-07-21"
}'Update metadata without replacing the body:
curl -X PATCH https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7 \
-H "Authorization: Bearer $KEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expectedRevision": 1,
"tags": [],
"project": null,
"state": "closed",
"clientRequestId": "close-handoff-1"
}'Fields omitted from an update stay unchanged. Send an empty tags array to
remove every tag. Send null for project, repository, kind, or state
to clear that field. properties contains custom metadata only. Updating it
does not change tags, Project, repository, kind, or state.
Successful write responses are compact receipts. note contains id, title,
and currentRevision; revision contains revisionNumber and operation.
They do not echo Markdown. Read the returned Note revision only when you need to
verify content. The receipt’s metadataDiff contains added
and removed tag slugs plus before and after values for Project, kind, or state
when they changed. Project values in the diff are Project IDs.
Append a later result without reading the current revision first:
curl -X POST https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7/append \
-H "Authorization: Bearer $KEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bodyMarkdown": "## Result\n\nThe production check passed.",
"clientRequestId": "production-check-1"
}'Keep resolves the latest revision and safely retries one concurrent append.
Include expectedRevision only when the append must stay pinned to a known
version. A stale pinned revision returns 409 with
error: "revision_conflict". Send Accept: text/markdown to
GET /api/notes/:id when you want a full frontmatter document.
Restore an older version without deleting the revisions that followed it:
curl -X POST https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7/revisions/2/restore \
-H "Authorization: Bearer $KEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expectedRevision": 5,
"clientRequestId": "restore-note-123-v2"
}'The restored content becomes revision 6 in this example. The full history stays available.
Open the authenticated Note page at https://app.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.zipThe route requires notes.read. Each file preserves the stable Note ID,
revision, timestamps, archive state when present, custom properties, and
Markdown body.
Progressive Note reads
Use GET /api/notes/:id/read when a client does not need the full body.
Set view to one of these values:
| Mode | Required parameters | Result |
|---|---|---|
overview |
none | Revision, counts, and heading outline |
search |
q |
Matching line windows |
lines |
lines=start:end |
One exact line range |
You can add revision to pin the read to one revision. An exact range can
contain at most 200 lines and 32 KB. Search results use bounded context windows
and return at most 200 lines and 16 KB in total. A 413 response means the
requested exact range is still too large; request a narrower range. These reads
do not use Note credits.
curl "https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7/read?view=overview" \
-H "Authorization: Bearer $KEEP_API_KEY"
curl "https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7/read?view=search&q=rollout&revision=4" \
-H "Authorization: Bearer $KEEP_API_KEY"
curl "https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7/read?view=lines&lines=40%3A90&revision=4" \
-H "Authorization: Bearer $KEEP_API_KEY"Note sources and context
Attach a saved item or highlight to a note:
curl -X POST https://keep.md/api/notes/c4e2a975-d582-4fb9-b952-5911365332c7/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/c4e2a975-d582-4fb9-b952-5911365332c7/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.
Projects
Projects group Note and repository context. Project states are active,
paused, and completed.
| Endpoint | Use |
|---|---|
GET /api/projects |
List active Projects |
GET /api/projects?includeArchived=true |
Include archived Projects |
GET /api/projects/:id |
Get one Project |
POST /api/projects |
Create a Project |
PATCH /api/projects/:id |
Update a Project |
DELETE /api/projects/:id |
Archive a Project |
Create a Project:
curl -X POST https://keep.md/api/projects \
-H "Authorization: Bearer $KEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "SEO CLI",
"description": "Project memory for the SEO command-line tool.",
"color": "#f97316",
"state": "active",
"repositories": ["https://github.com/iannuttall/seo"]
}'Project routes require projects.read or projects.write. Deleting a Project
archives it. It does not delete its Notes or Items.
GET /api/items/:id
Get a single Item by ID with its complete saved content. Use
GET /api/items/:id/read for an overview, search window, or line slice. If the
Item has saved highlights, this full response includes a highlights array.
Callers with notes.read also receive connected Notes.
curl https://keep.md/api/items/a1b2c3 \
-H "Authorization: Bearer $KEEP_API_KEY"Captured images are ordered in content.media. Web captures use their source
HTTPS URL. A direct image upload uses an authenticated Keep URL.
GET /api/items/:id/media/:mediaId
Returns one directly uploaded image owned by the Item. The same Bearer token
and items.read permission used for the Item are required.
GET /api/items/:id/highlights
List the highlights attached to one Item. Add limit and offset for a bounded
page. A bounded response includes hasMore and nextOffset.
curl "https://keep.md/api/items/a1b2c3/highlights?limit=25&offset=0" \
-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
}
}Change highlights
Create a highlight with POST /api/items/:id/highlights. Send text, plus
optional prefix, suffix, note, startOffset, and endOffset fields.
Update an existing highlight with PATCH /api/highlights/:id. You can change
the selected text, anchors, offsets, or note.
Delete a highlight with DELETE /api/highlights/:id. Deletion is permanent.
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" }
]
}Change tags
| Endpoint | Use |
|---|---|
POST /api/tags |
Create a tag with a name |
PATCH /api/tags/:slug |
Change the name or description |
DELETE /api/tags/:slug |
Remove the tag from Notes and Items |
Deleting a tag does not delete any Note or Item.
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" }'Collections can group Items. The current public API can list and create
collections. Add or remove collection IDs through POST /api/items/:id.
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://suganthan.com/blog/webmcp-implementation-guide/" }'{
"ok": true,
"id": "a1b2c3",
"url": "https://suganthan.com/blog/webmcp-implementation-guide/",
"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.
| Parameter | Use |
|---|---|
cursor |
Opaque nextCursor value from the previous response |
updated_since |
Return changes at or after a timestamp or ISO date |
tags |
Filter by comma-separated tag names or slugs |
collection |
Filter by collection ID, name, or slug |
limit |
Return 1-1000 events. The default is 200 |
include=content |
Add structured content and rendered Markdown to an 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://suganthan.com/blog/webmcp-implementation-guide/",
"title": "WebMCP implementation guide"
}
}
],
"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 sources and subscriptions. Source routes require a session, Keep service
key, or personal token with items.write. Read-only keys cannot view or change
source settings.
The default response is a compact status manifest. It omits configuration, tag rules, and internal cursors.
curl https://keep.md/api/sources \
-H "Authorization: Bearer $KEEP_API_KEY"Add include=settings to inspect redacted configuration and tag rules:
curl "https://keep.md/api/sources?include=settings" \
-H "Authorization: Bearer $KEEP_API_KEY"Add include=sensitive only while debugging a source you control:
curl "https://keep.md/api/sources?include=sensitive" \
-H "Authorization: Bearer $KEEP_API_KEY"This response includes complete source configuration and internal cursors. It
may contain credentials. Do not paste it into a chat, issue, log, or Note. The
MCP list_sources tool always uses the safe response and cannot request this
view.
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://simonwillison.net/atom/everything/" }'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"Manage sources
| Endpoint | Use |
|---|---|
POST /api/sources/:id/pause |
Pause automatic sync |
POST /api/sources/:id/resume |
Resume automatic sync |
POST /api/sources/:id/settings |
Update name, tags, rules, content, summaries, or rollup |
POST /api/sources/:id/default-tags |
Replace default tags |
POST /api/sources/:id/rollup |
Turn Feed rollup on or off |
GET /api/sources/:id/events |
List recent source events |
POST /api/sources/import-opml |
Import feed sources from OPML |
GET /api/sources/export-opml |
Download feed sources as OPML |
The settings request can contain name, defaultTags, smartTagRules,
expandFullContent, autoSummarize, and rollupEnabled. Available settings
depend on the source type and plan.
Email inbox sources also provide confirmation and recent-message routes. Use the app for provider confirmation links, raw message inspection, and private inbox administration.
Item export jobs
Use export jobs for saved Items. Use GET /api/notes/export for the direct
Notes ZIP.
| Endpoint | Use |
|---|---|
POST /api/exports |
Start an Item export job |
GET /api/exports |
List recent export jobs |
GET /api/exports/:jobId/download |
Download a complete export |
The create request chooses Markdown ZIP, JSON, or CSV. It can limit Items by tag, collection, stashed state, or archived state. It can include saved content and omit personal Item notes. See Export for the user-facing options.
Webhooks
Webhook routes let a personal key manage signed Item-change deliveries.
| Endpoint | Use |
|---|---|
GET /api/webhooks |
List endpoints |
POST /api/webhooks |
Create an endpoint |
GET /api/webhooks/:id |
Read one endpoint |
PATCH /api/webhooks/:id |
Change its URL, events, scope, name, or status |
POST /api/webhooks/:id/test |
Queue a test delivery |
POST /api/webhooks/:id/rotate-secret |
Replace its signing secret |
GET /api/webhooks/:id/deliveries |
List recent deliveries |
DELETE /api/webhooks/:id |
Delete the endpoint |
See Webhooks for events, signatures, retries, and resource scope.