API
Save, search, and manage your bookmarks programmatically or via AI agent. Use API keys to authenticate and build your own integrations.
Authentication
All requests require a Bearer token. Generate an API key from Settings → API Keys in your dashboard.
Authorization: Bearer runa_sk_...
Base URL
https://api.onruna.com
Endpoints
/v1/enrich30 req/minEnrich a URL with metadata without saving it. Returns OG data, link type, and type-specific enrichment.
Request body
{
"url": "https://example.com/article"
}Response
{
"title": "Example Article",
"description": "...",
"imageUrl": "https://...",
"favicon": "https://...",
"domain": "example.com",
"linkType": "article",
"content": "...",
"typeMeta": null,
"attachments": null,
"isNsfw": false
}/v1/links30 req/minCreate a new bookmark. Automatically enriches the URL with metadata, AI summary, and tags.
Request body
{
"url": "https://example.com/article",
"source": "manual" // optional: manual | x-bookmark | email | feed
}Response
{
"id": "k57abc...",
"url": "https://example.com/article",
"title": "Example Article",
"domain": "example.com",
"linkType": "article",
"status": "inbox",
"enrichmentStatus": "enriching"
}/v1/links60 req/minList bookmarks filtered by status. Defaults to inbox.
Parameters
Query params: ?status=inbox // inbox | archived | trash
Response
{
"links": [
{ "id": "...", "url": "...", "title": "...", "status": "inbox", ... }
]
}/v1/links/:id60 req/minGet a single bookmark by ID.
Response
{
"id": "...",
"url": "...",
"title": "...",
"description": "...",
"domain": "...",
"status": "inbox",
...
}/v1/links/search30 req/minFull-text + semantic hybrid search powered by Meilisearch. Supports filtering, sorting, and pagination.
Request body
{
"q": "react server components", // optional, empty for browse
"filter": "status = \"inbox\"", // optional, Meilisearch filter
"sort": ["createdAt:desc"], // optional
"limit": 20, // optional, 1-100 (default 20)
"offset": 0 // optional, for pagination
}Response
{
"hits": [
{
"id": "j97...",
"url": "https://...",
"title": "...",
"description": "...",
"domain": "example.com",
"source": "manual",
"itemType": "article",
"status": "inbox",
"isNsfw": false,
"createdAt": 1774697115919,
"summary": "...",
"tags": ["typescript", "react"]
}
],
"estimatedTotalHits": 42,
"processingTimeMs": 3,
"offset": 0,
"limit": 20
}Search reference
Filterable attributes
status "inbox" | "archived" | "trash" itemType "article" | "tweet" | "reddit" | "youtube" | "github" | "pdf" | "image" isNsfw true | false source "manual" | "x-bookmark" | "email" | "feed" | "browser-import" createdAt unix timestamp in milliseconds tags array of tag name strings
Filter operators
=, !=, >, >=, <, <= Comparison TO Range (inclusive): createdAt 1711900800000 TO 1712505600000 IN Set membership: status IN ["inbox", "archived"] EXISTS, NOT EXISTS Field presence: tags EXISTS AND, OR, NOT Logical combinators ( ) Grouping
Filter examples
status = "inbox" itemType = "tweet" AND status != "trash" status IN ["inbox", "archived"] createdAt > 1711900800000 tags = "typescript" source = "x-bookmark" OR source = "browser-import" (itemType = "article" OR itemType = "youtube") AND status = "inbox" NOT tags EXISTS
Sortable attributes
["createdAt:desc"] // newest first ["createdAt:asc"] // oldest first
/v1/files10 req/minUpload a PDF or image file. Accepts multipart form data. Supported types: PDF, JPEG, PNG, WebP, GIF, HEIC. Max 50MB.
Request body
Content-Type: multipart/form-data file: <binary>
Response
{
"id": "k57abc...",
"filename": "document.pdf",
"mimeType": "application/pdf",
"size": 1048576,
"itemType": "pdf",
"status": "inbox",
"enrichmentStatus": "enriching"
}/v1/links/:id30 req/minUpdate bookmark metadata or status. Supports partial updates.
Request body
{
"title": "New title", // optional
"description": "...", // optional
"status": "archived", // optional: inbox | archived | trash
"isNsfw": false // optional
}Response
{ "success": true }/v1/links/:id20 req/minPermanently delete a bookmark.
Response
{ "success": true }Rate limits
All endpoints are rate limited per API key using a sliding window. When you exceed the limit, the API returns a 429 Too Many Requests response with these headers:
X-RateLimit-Limit: 60 // max requests in window X-RateLimit-Remaining: 42 // requests left X-RateLimit-Reset: 1711... // window reset (unix ms) Retry-After: 23 // seconds until retry
Quick example
# Save a link
curl -X POST https://api.onruna.com/v1/links \
-H "Authorization: Bearer runa_sk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/article"}'
# Search links
curl -X POST https://api.onruna.com/v1/links/search \
-H "Authorization: Bearer runa_sk_..." \
-H "Content-Type: application/json" \
-d '{"q": "react", "filter": "status = \"inbox\"", "limit": 10}'
# Browse recent inbox (no query, just filter + sort)
curl -X POST https://api.onruna.com/v1/links/search \
-H "Authorization: Bearer runa_sk_..." \
-H "Content-Type: application/json" \
-d '{"filter": "status = \"inbox\"", "sort": ["createdAt:desc"], "limit": 20}'
# Upload a file
curl -X POST https://api.onruna.com/v1/files \
-H "Authorization: Bearer runa_sk_..." \
-F "file=@document.pdf"Interactive docs
Full Swagger/OpenAPI docs are available at https://api.onruna.com/swagger