Keep REST API reference
The Keep API lets you save items, search them, retrieve structured content plus rendered markdown, manage sources, and inspect account usage. 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 and read-only scoped keys.
curl https://keep.md/api/me \ -H "Authorization: Bearer $KEEP_API_KEY"
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
}
GET /api/items/search
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"
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.
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 agents that consume 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"