Referencia de la API para desarrolladores
Usa la API REST de Krokanti Notes para leer y escribir notas de forma programática, automatizar flujos de trabajo o crear integraciones. Todos los endpoints aceptan y devuelven JSON.
https://notes.krokanti.com/api100 req/min per tokenSuscripción Pro requerida
El acceso mediante token de API requiere una suscripción Pro activa. Genera tu token en Configuración → Conexiones después de actualizar.
Actualizar a Pro →Autenticación
Todas las solicitudes de API deben incluir un token de API personal en el encabezado Authorization. Genera tokens en Configuración → Conexiones (se requiere plan Pro). Los tokens comienzan con kn_ y solo se muestran una vez al crearse — guárdalos de forma segura.
curl https://notes.krokanti.com/api/notes \
-H "Authorization: Bearer kn_your_token_here" \
-H "Content-Type: application/json"Notas
/api/notesLista todas las notas del usuario autenticado. Admite filtrado, ordenación y paginación.
| Parámetro | Tipo | Descripción |
|---|---|---|
search | string | Búsqueda de texto completo en título y contenido |
folderId | string | Filtrar por ID de carpeta |
tag | string | Filtrar por nombre de etiqueta (coincidencia exacta) |
trashed | boolean | Establece en "true" para listar notas en la papelera |
sortBy | updatedAt | createdAt | title | Campo de ordenación (por defecto: "updatedAt") |
sortOrder | asc | desc | Dirección de ordenación (por defecto: "desc") |
limit | number | Resultados por página — 1 a 100 (por defecto: 50) |
offset | number | Número de resultados a omitir (por defecto: 0) |
- →Respuesta: { notes: Note[], hasMore: boolean }
- →Las notas fijadas siempre se devuelven primero, luego ordenadas por el campo elegido
- →El contenido de notas seguras se devuelve como un blob cifrado (cadena JSON que comienza con {"v":1)
/api/notesCrea una nueva nota vacía. Devuelve el objeto de la nota creada.
- →La nueva nota tiene título y contenido vacíos — actualízala inmediatamente con PATCH
- →Estado de respuesta: 201 Created
/api/notes/:idObtiene una sola nota por ID.
| Parámetro | Tipo | Descripción |
|---|---|---|
idrequerido | string | UUID de la nota |
- →Devuelve 404 si la nota no existe o pertenece a otro usuario
- →El contenido de notas seguras se devuelve como un blob cifrado
/api/notes/:idActualiza el título, contenido, etiquetas, carpeta o metadatos de una nota. Admite detección de conflictos.
| Parámetro | Tipo | Descripción |
|---|---|---|
idrequerido | string | UUID de la nota (parámetro de ruta) |
title | string | Nuevo título |
content | string | Nuevo contenido HTML |
tags | string[] | Reemplaza el array completo de etiquetas |
folderId | string | null | Mover a carpeta (null para quitar de la carpeta) |
isPinned | boolean | Fijar o desfijar la nota (solo propietario) |
isPublic | boolean | Publicar o despublicar la nota (solo propietario) |
action | trash | restore | Mover a la papelera o restaurar (solo propietario) |
clientUpdatedAt | string (ISO 8601) | Último updatedAt conocido — usado para detección de conflictos |
force | boolean | Omitir la detección de conflictos y sobrescribir el estado del servidor |
- →Devuelve 409 con { conflict: true, serverNote } si clientUpdatedAt es más antiguo que la versión del servidor
- →Las notas seguras no se pueden editar mediante la API (devuelve 403)
- →Los colaboradores con permiso de edición solo pueden actualizar título y contenido — las etiquetas y carpetas son solo del propietario
/api/notes/:idElimina permanentemente una nota. Es irreversible — usa action: 'trash' mediante PATCH para borrado suave.
| Parámetro | Tipo | Descripción |
|---|---|---|
idrequerido | string | UUID de la nota |
Carpetas
/api/foldersLista todas las carpetas del usuario autenticado.
- →Respuesta: { folders: Folder[] }
/api/foldersCrea una nueva carpeta.
| Parámetro | Tipo | Descripción |
|---|---|---|
namerequerido | string | Nombre de la carpeta |
- →Estado de respuesta: 201 Created
/api/folders/:idRenombra una carpeta.
| Parámetro | Tipo | Descripción |
|---|---|---|
idrequerido | string | UUID de la carpeta (parámetro de ruta) |
namerequerido | string | Nuevo nombre de la carpeta |
/api/folders/:idElimina una carpeta. Las notas dentro no se eliminan — quedan sin carpeta.
| Parámetro | Tipo | Descripción |
|---|---|---|
idrequerido | string | UUID de la carpeta |
Paginación
El endpoint GET /api/notes usa paginación basada en offset. Contrólala con dos parámetros de consulta:
| Parámetro | Por defecto | Descripción |
|---|---|---|
limit | 50 | Resultados por página (1–100) |
offset | 0 | Número de resultados a omitir |
La respuesta incluye un booleano hasMore . Obtén la siguiente página incrementando offset:
// Fetch all notes (auto-pagination)
async function fetchAllNotes(token) {
const notes = [];
let offset = 0;
while (true) {
const res = await fetch(
`https://notes.krokanti.com/api/notes?limit=100&offset=${offset}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const { notes: page, hasMore } = await res.json();
notes.push(...page);
if (!hasMore) break;
offset += 100;
}
return notes;
}Detección de conflictos
La API usa control de concurrencia optimista para evitar actualizaciones perdidas cuando varios clientes editan la misma nota simultáneamente.
Incluye el campo clientUpdatedAt en cada solicitud PATCH — establécelo al valor updatedAt de la nota que obtuviste. Si otro cliente guardó una versión más reciente, la API devuelve 409 Conflict con la versión actual del servidor para que puedas fusionar:
// PATCH with conflict detection
const res = await fetch(`https://notes.krokanti.com/api/notes/${id}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Updated title",
content: "<p>New content</p>",
clientUpdatedAt: note.updatedAt, // ISO 8601 string
}),
});
if (res.status === 409) {
const { serverNote } = await res.json();
// Merge your changes with serverNote, then retry with force: true
await fetch(`https://notes.krokanti.com/api/notes/${id}`, {
method: "PATCH",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({ content: merged, force: true }),
});
}Pasa force: true para omitir la detección de conflictos y sobrescribir el estado del servidor incondicionalmente.
Notas seguras
Las notas seguras se cifran en el lado del cliente con AES-256-GCM antes de almacenarse. La API devuelve el blob cifrado sin procesar — no puede descifrarlo (el PIN nunca sale del navegador).
- →GET /api/notes/:id — devuelve el contenido como un blob JSON cifrado que comienza con {"v":1,"alg":"AES-256-GCM",...}
- →PATCH /api/notes/:id — bloqueado para notas seguras (devuelve 403). El cifrado solo se puede hacer desde la app web.
- →Las notas seguras no se pueden hacer públicas (devuelve 400 si se envía isPublic: true).
Códigos de error
Todas las respuestas de error incluyen un cuerpo JSON con una cadena error y a veces un campo code para subcódigos legibles por máquina.
| Estado | Significado | Causa común |
|---|---|---|
400 | Solicitud incorrecta | Cuerpo de solicitud inválido (ej. hacer pública una nota segura) |
401 | No autorizado | Token de API faltante o inválido |
402 | Pago requerido | La función requiere una suscripción Pro (code: pro_required) |
403 | Prohibido | La nota pertenece a otro usuario, o edición de nota segura mediante API |
404 | No encontrado | La nota o carpeta no existe |
409 | Conflicto | clientUpdatedAt está desactualizado — la respuesta incluye serverNote para fusionar |
429 | Demasiadas solicitudes | Límite de tasa superado — comprueba el encabezado de respuesta Retry-After |
// Error response body
{ "error": "Rate limit exceeded" }
// With machine-readable subcode
{ "error": "Secure notes require Pro", "code": "pro_required" }
// Conflict response
{ "conflict": true, "serverNote": { "id": "...", "title": "...", "updatedAt": "..." } }Límites de tasa
Las solicitudes con token de API están limitadas a 100 solicitudes por minuto por token (ventana fija). Las solicitudes basadas en sesión (navegador) no tienen límite.
Cuando se supera el límite, la API devuelve 429 Too Many Requests con un encabezado Retry-After que indica cuántos segundos esperar antes de reintentar.
# 429 response headers
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json
{ "error": "Rate limit exceeded" }Ejemplos de código
Reemplaza kn_your_token_here con tu token de API real de Configuración → Conexiones.
cURL
# List your 10 most recently updated notes
curl "https://notes.krokanti.com/api/notes?limit=10" \
-H "Authorization: Bearer kn_your_token_here"
# Create a new note
curl -X POST "https://notes.krokanti.com/api/notes" \
-H "Authorization: Bearer kn_your_token_here"
# Update a note's title and content
curl -X PATCH "https://notes.krokanti.com/api/notes/NOTE_ID" \
-H "Authorization: Bearer kn_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "title": "My Note", "content": "<p>Hello world</p>", "clientUpdatedAt": "2026-01-01T00:00:00.000Z" }'
# Search notes
curl "https://notes.krokanti.com/api/notes?search=meeting&limit=20" \
-H "Authorization: Bearer kn_your_token_here"
# Move a note to trash
curl -X PATCH "https://notes.krokanti.com/api/notes/NOTE_ID" \
-H "Authorization: Bearer kn_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "action": "trash" }'JavaScript (fetch)
const BASE = "https://notes.krokanti.com/api";
const TOKEN = "kn_your_token_here";
const headers = {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
};
// List notes
const { notes, hasMore } = await fetch(`${BASE}/notes?limit=50`, { headers }).then(r => r.json());
// Create a note
const note = await fetch(`${BASE}/notes`, { method: "POST", headers }).then(r => r.json());
// Update title + content
await fetch(`${BASE}/notes/${note.id}`, {
method: "PATCH",
headers,
body: JSON.stringify({
title: "Shopping list",
content: "<ul><li>Milk</li><li>Eggs</li></ul>",
clientUpdatedAt: note.updatedAt,
}),
});
// Add a tag
await fetch(`${BASE}/notes/${note.id}`, {
method: "PATCH",
headers,
body: JSON.stringify({ tags: ["shopping", "weekly"] }),
});
// Pin the note
await fetch(`${BASE}/notes/${note.id}`, {
method: "PATCH",
headers,
body: JSON.stringify({ isPinned: true }),
});
// List folders
const { folders } = await fetch(`${BASE}/folders`, { headers }).then(r => r.json());
// Create a folder and move the note to it
const folder = await fetch(`${BASE}/folders`, {
method: "POST",
headers,
body: JSON.stringify({ name: "Groceries" }),
}).then(r => r.json());
await fetch(`${BASE}/notes/${note.id}`, {
method: "PATCH",
headers,
body: JSON.stringify({ folderId: folder.id }),
});Python (requests)
import requests
BASE = "https://notes.krokanti.com/api"
TOKEN = "kn_your_token_here"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
# List notes
resp = requests.get(f"{BASE}/notes", headers=HEADERS, params={"limit": 50})
data = resp.json()
notes, has_more = data["notes"], data["hasMore"]
# Create a note
note = requests.post(f"{BASE}/notes", headers=HEADERS).json()
# Update it
requests.patch(
f"{BASE}/notes/{note['id']}",
headers=HEADERS,
json={
"title": "Meeting notes",
"content": "<p>Action items:</p><ul><li>Follow up with Alice</li></ul>",
"clientUpdatedAt": note["updatedAt"],
},
)
# Search and print titles
resp = requests.get(f"{BASE}/notes", headers=HEADERS, params={"search": "meeting"})
for n in resp.json()["notes"]:
print(n["title"])
# Delete a note permanently
requests.delete(f"{BASE}/notes/{note['id']}", headers=HEADERS)