Krokanti Notes
API REST

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.

URL base:https://notes.krokanti.com/api
Límite de tasa:100 req/min per token

Suscripció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"
Nota: La gestión de tokens (crear, listar, revocar) solo está disponible a través del navegador — no puedes gestionar tokens mediante la propia API.

Notas

GET/api/notes

Lista todas las notas del usuario autenticado. Admite filtrado, ordenación y paginación.

ParámetroTipoDescripción
searchstringBúsqueda de texto completo en título y contenido
folderIdstringFiltrar por ID de carpeta
tagstringFiltrar por nombre de etiqueta (coincidencia exacta)
trashedbooleanEstablece en "true" para listar notas en la papelera
sortByupdatedAt | createdAt | titleCampo de ordenación (por defecto: "updatedAt")
sortOrderasc | descDirección de ordenación (por defecto: "desc")
limitnumberResultados por página — 1 a 100 (por defecto: 50)
offsetnumberNú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)
POST/api/notes

Crea 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
GET/api/notes/:id

Obtiene una sola nota por ID.

ParámetroTipoDescripción
idrequeridostringUUID 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
PATCH/api/notes/:id

Actualiza el título, contenido, etiquetas, carpeta o metadatos de una nota. Admite detección de conflictos.

ParámetroTipoDescripción
idrequeridostringUUID de la nota (parámetro de ruta)
titlestringNuevo título
contentstringNuevo contenido HTML
tagsstring[]Reemplaza el array completo de etiquetas
folderIdstring | nullMover a carpeta (null para quitar de la carpeta)
isPinnedbooleanFijar o desfijar la nota (solo propietario)
isPublicbooleanPublicar o despublicar la nota (solo propietario)
actiontrash | restoreMover a la papelera o restaurar (solo propietario)
clientUpdatedAtstring (ISO 8601)Último updatedAt conocido — usado para detección de conflictos
forcebooleanOmitir 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
DELETE/api/notes/:id

Elimina permanentemente una nota. Es irreversible — usa action: 'trash' mediante PATCH para borrado suave.

ParámetroTipoDescripción
idrequeridostringUUID de la nota

Carpetas

GET/api/folders

Lista todas las carpetas del usuario autenticado.

  • Respuesta: { folders: Folder[] }
POST/api/folders

Crea una nueva carpeta.

ParámetroTipoDescripción
namerequeridostringNombre de la carpeta
  • Estado de respuesta: 201 Created
PATCH/api/folders/:id

Renombra una carpeta.

ParámetroTipoDescripción
idrequeridostringUUID de la carpeta (parámetro de ruta)
namerequeridostringNuevo nombre de la carpeta
DELETE/api/folders/:id

Elimina una carpeta. Las notas dentro no se eliminan — quedan sin carpeta.

ParámetroTipoDescripción
idrequeridostringUUID 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ámetroPor defectoDescripción
limit50Resultados por página (1–100)
offset0Nú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.

EstadoSignificadoCausa común
400Solicitud incorrectaCuerpo de solicitud inválido (ej. hacer pública una nota segura)
401No autorizadoToken de API faltante o inválido
402Pago requeridoLa función requiere una suscripción Pro (code: pro_required)
403ProhibidoLa nota pertenece a otro usuario, o edición de nota segura mediante API
404No encontradoLa nota o carpeta no existe
409ConflictoclientUpdatedAt está desactualizado — la respuesta incluye serverNote para fusionar
429Demasiadas solicitudesLí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)