Krokanti Notes
API REST

Referência da API para desenvolvedores

Usa a API REST do Krokanti Notes para ler e escrever notas programaticamente, automatizar fluxos de trabalho ou criar integrações. Todos os endpoints aceitam e retornam JSON.

URL base:https://notes.krokanti.com/api
Limite de taxa:100 req/min per token

Subscrição Pro necessária

O acesso por token de API requer uma subscrição Pro ativa. Gere seu token em Configurações → Conexões após a atualização.

Atualizar para Pro →

Autenticação

Todos os pedidos de API devem incluir um token de API pessoal no cabeçalho Authorization. Gere tokens em Configurações → Conexões (plano Pro necessário). Os tokens começam com kn_ e são mostrados apenas uma vez na criação — guarde-os em segurança.

curl https://notes.krokanti.com/api/notes \
  -H "Authorization: Bearer kn_your_token_here" \
  -H "Content-Type: application/json"
Nota: A gestão de tokens (criar, listar, revogar) só está disponível via browser — você não pode gerir tokens através da própria API.

Notas

GET/api/notes

Lista todas as notas do utilizador autenticado. Suporta filtragem, ordenação e paginação.

ParâmetroTipoDescrição
searchstringPesquisa de texto completo no título e conteúdo
folderIdstringFiltrar por ID de pasta
tagstringFiltrar por nome de etiqueta (correspondência exata)
trashedbooleanDefine como "true" para listar notas no lixo
sortByupdatedAt | createdAt | titleCampo de ordenação (padrão: "updatedAt")
sortOrderasc | descDireção de ordenação (padrão: "desc")
limitnumberResultados por página — 1 a 100 (padrão: 50)
offsetnumberNúmero de resultados a ignorar (padrão: 0)
  • Resposta: { notes: Note[], hasMore: boolean }
  • As notas fixadas são sempre retornadas primeiro, depois ordenadas pelo campo escolhido
  • O conteúdo de notas seguras é retornado como um blob cifrado (string JSON começando com {"v":1)
POST/api/notes

Cria uma nova nota vazia. Retorna o objeto de nota criado.

  • A nova nota tem título e conteúdo vazios — atualize-a imediatamente com PATCH
  • Estado de resposta: 201 Created
GET/api/notes/:id

Obtém uma única nota por ID.

ParâmetroTipoDescrição
idobrigatóriostringUUID da nota
  • Retorna 404 se a nota não existe ou pertence a outro utilizador
  • O conteúdo de notas seguras é retornado como um blob cifrado
PATCH/api/notes/:id

Atualize o título, conteúdo, etiquetas, pasta ou metadados de uma nota. Suporta deteção de conflitos.

ParâmetroTipoDescrição
idobrigatóriostringUUID da nota (parâmetro de caminho)
titlestringNovo título
contentstringNovo conteúdo HTML
tagsstring[]Substitua o array completo de etiquetas
folderIdstring | nullMover para pasta (null para remover da pasta)
isPinnedbooleanFixar ou desafixar a nota (apenas proprietário)
isPublicbooleanPublicar ou despublicar a nota (apenas proprietário)
actiontrash | restoreMover para o lixo ou restaurar (apenas proprietário)
clientUpdatedAtstring (ISO 8601)Último updatedAt conhecido — usado para deteção de conflitos
forcebooleanIgnorar a deteção de conflitos e sobrescrever o estado do servidor
  • Retorna 409 com { conflict: true, serverNote } se clientUpdatedAt for mais antigo que a versão do servidor
  • Notas seguras não podem ser editadas via API (retorna 403)
  • Colaboradores com permissão de edição só podem atualizar título e conteúdo — etiquetas e pasta são apenas do proprietário
DELETE/api/notes/:id

Elimina permanentemente uma nota. É irreversível — usa action: 'trash' via PATCH para eliminação suave.

ParâmetroTipoDescrição
idobrigatóriostringUUID da nota

Pastas

GET/api/folders

Lista todas as pastas do utilizador autenticado.

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

Cria uma nova pasta.

ParâmetroTipoDescrição
nameobrigatóriostringNome da pasta
  • Estado de resposta: 201 Created
PATCH/api/folders/:id

Renomeia uma pasta.

ParâmetroTipoDescrição
idobrigatóriostringUUID da pasta (parâmetro de caminho)
nameobrigatóriostringNovo nome da pasta
DELETE/api/folders/:id

Elimina uma pasta. As notas dentro não são eliminadas — ficam sem pasta.

ParâmetroTipoDescrição
idobrigatóriostringUUID da pasta

Paginação

O endpoint GET /api/notes usa paginação baseada em offset. Controla-a com dois parâmetros de consulta:

ParâmetroPadrãoDescrição
limit50Resultados por página (1–100)
offset0Número de resultados a ignorar

A resposta inclui um booleano hasMore . Obtém a próxima 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;
}

Deteção de conflitos

A API usa controlo de concorrência otimista para evitar atualizações perdidas quando múltiplos clientes editam a mesma nota simultaneamente.

Inclui o campo clientUpdatedAt em cada pedido PATCH — define-o com o valor updatedAt da nota que obtiveste. Se outro cliente guardou uma versão mais recente, a API retorna 409 Conflict com a versão atual do servidor para que possa fundir:

// 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 }),
  });
}

Passa force: true para ignorar a deteção de conflitos e sobrescrever o estado do servidor incondicionalmente.

Notas seguras

As notas seguras são cifradas do lado do cliente com AES-256-GCM antes de serem armazenadas. A API retorna o blob cifrado bruto — não o pode decifrar (o PIN nunca sai do browser).

  • GET /api/notes/:id — retorna conteúdo como um blob JSON cifrado começando com {"v":1,"alg":"AES-256-GCM",...}
  • PATCH /api/notes/:id — bloqueado para notas seguras (retorna 403). A cifragem só pode ser feita através da app web.
  • Notas seguras não podem ser tornadas públicas (retorna 400 se isPublic: true for enviado).

Códigos de erro

Todas as respostas de erro incluem um corpo JSON com uma string error e às vezes um campo code para subcódigos legíveis por máquina.

EstadoSignificadoCausa comum
400Pedido inválidoCorpo do pedido inválido (ex. tornar uma nota segura pública)
401Não autorizadoToken de API em falta ou inválido
402Pagamento necessárioA funcionalidade requer uma subscrição Pro (code: pro_required)
403ProibidoA nota pertence a outro utilizador, ou edição de nota segura via API
404Não encontradoA nota ou pasta não existe
409ConflitoclientUpdatedAt está desatualizado — a resposta inclui serverNote para fundir
429Pedidos a maisLimite de taxa excedido — verifica o cabeçalho de resposta 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": "..." } }

Limites de taxa

Os pedidos com token de API estão limitados a 100 pedidos por minuto por token (janela fixa). Os pedidos baseados em sessão (browser) não têm limite.

Quando o limite é excedido, a API retorna 429 Too Many Requests com um cabeçalho Retry-After indicando quantos segundos aguardar antes de tentar novamente.

# 429 response headers
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json

{ "error": "Rate limit exceeded" }

Exemplos de código

Substitua kn_your_token_here pelo seu token de API real de Configurações → Conexões.

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)