JSON storage at the edge

Uma API mínima para guardar qualquer JSON.

Use um identificador como chave, envie um valor JSON e recupere-o de qualquer cliente HTTP. Cada alteração recebe uma versão e timestamps automáticos.

Serviço disponível
Base URL https://kv.helio.me
API pública e sem autenticação. Quem conhecer um ID poderá ler, substituir ou apagar seu conteúdo. Não armazene senhas, tokens, dados pessoais ou qualquer informação confidencial.
Quick start

Primeiros passos

Crie ou substitua o documento completo com PUT /:id. Para alterar somente um valor, use PUT /:id/value?path=.... O corpo pode ser objeto, array, string, número, booleano ou null. Estes métodos canônicos são a opção recomendada; aliases via GET existem somente para clientes com limitação de método HTTP.

1. Salvar

curl -X PUT https://kv.helio.me/minha_tarefa \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Comprar café","feito":false}'

2. Consultar

curl https://kv.helio.me/minha_tarefa

3. Apagar

curl -X DELETE https://kv.helio.me/minha_tarefa
HTTP reference

Endpoints

GET/:id

Retorna o valor atual, sua versão e os timestamps. Responde 404 quando o ID não existe.

GET/:id/version

Retorna somente id e version. Útil para verificar mudanças sem baixar o valor completo.

PUT/:id

Cria o ID com versão 1 ou substitui completamente o valor existente e incrementa a versão. Não faz merge de objetos.

PUT/:id/value?path=<JSON Pointer>

Cria ou substitui um único valor no caminho indicado. Cria objetos ancestrais ausentes, trata arrays com índices explícitos e retorna o item completo atualizado.

DELETE/:id

Apaga definitivamente o item. Se ele for recriado depois, começará novamente na versão 1.

OPTIONSqualquer caminho

Responde ao preflight CORS. São permitidos os headers Content-Type, Authorization, If-None-Match e If-Match.

Restricted-client compatibility

Compatibilidade via GET

Clientes que só conseguem emitir GET podem transportar o comando em method e o valor JSON em data. Continue preferindo PUT e DELETE reais sempre que possível.

GET com efeito colateral é perigoso. Prefetch, crawlers, previews, retries, caches e ferramentas de inspeção podem executar uma URL mutante sem intenção. Repetir a mesma URL executa uma nova mutação. Confirme a gravação por version, não pelo timestamp.

Contrato completo

# Leitura atual e alias explícito
GET /meu-id
GET /meu-id?method=GET

# Substituição completa
GET /meu-id?method=PUT&data=eyJub21lIjoiQW5hIn0

# Alteração por JSON Pointer
GET /meu-id/value?method=PUT&path=%2Fnome&data=IkJpYSI

# Exclusão
GET /meu-id?method=DELETE

# Versão, sempre somente leitura
GET /meu-id/version

data contém um valor JSON completo codificado em UTF-8 e depois em base64url canônico sem padding. eyJub21lIjoiQW5hIn0 representa {"nome":"Ana"}; IkJpYSI representa a string JSON "Bia".

Gerar data no Node.js

const valor = { nome: "Ana" };
const data = Buffer.from(JSON.stringify(valor), "utf8").toString("base64url");

console.log(data); // eyJub21lIjoiQW5hIn0

Gerar data no navegador

function base64urlJson(value) {
  const bytes = new TextEncoder().encode(JSON.stringify(value));
  let binary = "";
  for (const byte of bytes) binary += String.fromCharCode(byte);
  return btoa(binary)
    .replaceAll("+", "-")
    .replaceAll("/", "_")
    .replace(/=+$/, "");
}

const data = base64urlJson({ nome: "Ana" });

Gramática estrita

  • method, data e seus valores são case-sensitive. Aceite somente GET, PUT e DELETE em maiúsculas nas rotas documentadas.
  • Em GET /:id, method=GET aceita somente method; method=PUT exige exatamente um data; method=DELETE aceita somente method.
  • Em GET /:id/value, somente method=PUT é válido, com exatamente um method, um path e um data.
  • Parâmetros duplicados ou não documentados são rejeitados. GET /:id sem o nome exato method continua sendo leitura e tolera queries alheias.
  • method só altera uma requisição cujo método HTTP real é GET. Queries com esses nomes não reinterpretam um PUT ou DELETE real.
  • GET /:id/version é sempre somente leitura. GET /:id/value sem method continua respondendo 405.

Limites e exposição

  • O JSON decodificado de data aceita no máximo 10.000 bytes UTF-8; a forma codificada aceita no máximo 13.334 caracteres.
  • A URL absoluta de um alias mutante aceita no máximo 15.000 bytes. Host, ID, nomes de parâmetros, path percent-encoded e data compartilham esse orçamento; um caminho maior deixa menos espaço para data.
  • A plataforma Cloudflare aceita URLs de até 16 KB. Acima desse limite, a borda pode rejeitar a requisição antes do erro estruturado da API.
  • PUTs canônicos continuam aceitando corpos de até 1.900.000 bytes. O limite menor existe apenas para JSON transportado na URL.
  • Base64url é codificação, não criptografia. A URL pode aparecer em histórico, logs, analytics, proxies e referers. Não use aliases mutantes para senhas, tokens, dados pessoais ou qualquer segredo.
  • Cache-Control: no-store e Referrer-Policy: no-referrer reduzem alguns riscos, mas não impedem execução automática, histórico ou logs intermediários.
Set by JSON Pointer

Atualizar um valor por caminho

Envie um único valor JSON bruto para PUT /:id/value e informe o endereço no parâmetro path. Esta operação não é JSON Patch nem JSON Merge Patch: ela cria ou substitui exatamente um valor.

Alterar uma folha existente

curl -X PUT \
  "https://kv.helio.me/config/value?path=%2Finterface%2Ftema" \
  -H "Content-Type: application/json" \
  -d '"escuro"'

Criar ancestrais ausentes

curl -X PUT \
  "https://kv.helio.me/config/value?path=%2Fpreferencias%2Fnotificacoes%2Femail" \
  -H "Content-Type: application/json" \
  -d 'true'

Se o ID não existir, ele começa como {}. Objetos ausentes são criados recursivamente. Assim, /items/0/name em um item ausente cria chaves de objeto chamadas items, 0 e name; a API nunca infere um array a partir de um token numérico.

Objetos e arrays

SituaçãoComportamento
ObjetoTodo token é uma chave literal, inclusive 0, - e a chave vazia.
Array existenteUse 0 ou um inteiro positivo sem zeros à esquerda. Índices existentes são substituídos.
Índice = tamanhoAdiciona um elemento ao final. Repetir o mesmo índice substitui esse elemento, sem adicionar outro.
Índice > tamanhoRetorna ARRAY_INDEX_OUT_OF_BOUNDS; lacunas não são criadas.
/- em arrayRetorna INVALID_ARRAY_INDEX. Append por hífen não é suportado.
Folha escalar ou nullPode ser substituída normalmente.
Ancestral escalar ou nullRetorna PATH_TYPE_CONFLICT; a API não sobrescreve o bloqueador.

Escaping e codificação

O caminho usa JSON Pointer. Dentro de um segmento, escreva ~0 para a chave ~ e ~1 para a chave /. Primeiro monte o JSON Pointer e depois codifique-o como parâmetro de URL. path= é proibido porque apontaria para o documento completo; path=/ é válido e aponta para uma chave vazia.

const query = new URLSearchParams({ path: "/a~1b/tema" });
const response = await fetch(
  "https://kv.helio.me/config/value?" + query,
  {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify("escuro"),
  },
);

Semântica e limites

  • Cada gravação aceita incrementa version, mesmo quando o valor final não muda.
  • A resposta contém o item completo com timestamps e o novo valor.
  • O caminho decodificado aceita até 4.096 bytes UTF-8 e 64 segmentos.
  • O corpo e o documento final têm limite de 1.900.000 bytes UTF-8.
  • O documento final aceita até 1.000 níveis aninhados de objetos e arrays, limite do parser JSON1 usado pela operação.
  • PUT /:id pode armazenar JSON mais profundo, mas ele precisa ser substituído por um documento de até 1.000 níveis antes de aceitar atualizações por caminho.
  • Uma atualização por caminho pode normalizar espaços insignificantes, mas preserva literais numéricos não alterados, como 9007199254740993 e 1e400.
  • PUT /:id continua preservando o texto JSON bruto e substituindo o documento completo.
  • Não há remoção por caminho, múltiplas mutações, inserção no meio de arrays, JSON Patch, JSON Merge Patch ou precondições If-Match.
Data model

Formato das respostas

Uma criação ou consulta retorna o envelope abaixo. O campo json contém exatamente o tipo de valor enviado.

{
  "id": "minha_tarefa",
  "version": 1,
  "created_at": "2026-07-12T22:32:19.374Z",
  "updated_at": "2026-07-12T22:32:19.374Z",
  "json": {
    "titulo": "Comprar café",
    "feito": false
  }
}
version

Começa em 1 e aumenta a cada gravação bem-sucedida, inclusive aliases PUT via GET e valores que não mudam o resultado.

created_at

Instante UTC da criação. Registros antigos podem retornar null.

updated_at

Instante UTC da última gravação. Na criação, é igual a created_at. Registros antigos ainda não atualizados podem retornar null.

json

Qualquer valor JSON válido, sem campos obrigatórios como feito.

Consulta de versão

{
  "id": "minha_tarefa",
  "version": 1
}

Exclusão

{
  "ok": true,
  "id": "minha_tarefa"
}
Browser & Node.js

Uso com JavaScript

A API aceita CORS de qualquer origem e pode ser chamada diretamente pelo navegador.

Salvar um valor

const response = await fetch("https://kv.helio.me/preferencias", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ tema: "escuro", idioma: "pt-BR" }),
});

if (!response.ok) throw new Error("Falha ao salvar");
const item = await response.json();
console.log(item.version);

Consultar um valor

const response = await fetch("https://kv.helio.me/preferencias");

if (response.status === 404) {
  console.log("O ID ainda não existe");
} else if (response.ok) {
  const item = await response.json();
  console.log(item.json);
}

Atualizar só um valor

const query = new URLSearchParams({ path: "/interface/tema" });
const updated = await fetch(
  "https://kv.helio.me/preferencias/value?" + query,
  {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify("claro"),
  },
).then((response) => response.json());
Constraints

Regras e limites

RegraValor
IDDe 1 a 100 caracteres: letras ASCII, números, hífen e sublinhado.
Expressão^[A-Za-z0-9_-]{1,100}$
PayloadMáximo de 1.900.000 bytes em UTF-8.
Data via GETMáximo de 10.000 bytes UTF-8 após decodificar base64url; máximo codificado de 13.334 caracteres.
URL mutante via GETMáximo preventivo de 15.000 bytes na URL absoluta, abaixo do limite de 16 KB da plataforma.
ResultadoUma mutação por caminho também deve resultar em no máximo 1.900.000 bytes UTF-8.
JSON PointerMáximo de 4.096 bytes UTF-8 após decodificar a URL e 64 segmentos; o caminho raiz é proibido.
Aninhamento por caminhoO resultado aceita no máximo 1.000 níveis de objetos e arrays, conforme o limite do JSON1.
AtualizaçãoPUT /:id substitui tudo; PUT /:id/value cria ou substitui exatamente um valor.
Rate limit30 requisições por IP a cada 10 segundos.
CacheRespostas da API usam Cache-Control: no-store.
RefererRespostas usam Referrer-Policy: no-referrer como mitigação parcial para dados na URL.
CORSQualquer origem pode chamar a API; OPTIONS responde ao preflight.
AutenticaçãoNenhuma.

Exemplos de IDs válidos

tarefa_1
config-app
usuario123
01J2Y8N7R6K5M4

IDs inválidos

minha tarefa   # espaço
perfil.json    # ponto
ação           # caracteres fora de ASCII
pasta/item     # barra cria outro segmento de rota
Failures

Contrato de erros

Falhas da API retornam JSON estruturado. code é estável para automação, retryable informa se repetir pode resolver o problema e hint sugere a próxima ação. Campos de contexto variam por código e nunca incluem o documento armazenado, data, seus bytes decodificados ou o JSON recebido.

{
  "error": "O caminho atravessa um valor que não é objeto nem array.",
  "code": "PATH_TYPE_CONFLICT",
  "retryable": false,
  "hint": "Substitua primeiro o valor bloqueador por um objeto ou use PUT /:id para substituir o documento completo.",
  "path": "/perfil/tema",
  "blocked_at": "/perfil",
  "actual_type": "string",
  "required_type": "object_or_array"
}
StatuscodeContexto adicionalPróxima ação
400INVALID_IDid, regraUse de 1 a 100 letras ASCII, números, hífens ou sublinhados.
404INVALID_ROUTEpathConsulte GET / e corrija a rota.
404ITEM_NOT_FOUNDidConfira o ID ou crie o item com PUT /:id.
405METHOD_NOT_ALLOWEDCabeçalho AllowUse um dos métodos listados em Allow.
400INVALID_JSONNenhumEnvie exatamente um valor JSON válido.
400INVALID_UTF8NenhumCodifique o valor JSON como UTF-8 válido.
413PAYLOAD_TOO_LARGEmax_bytes e, quando conhecido, received_bytesReduza o corpo para no máximo 1.900.000 bytes.
400DUPLICATE_METHOD_PARAMETERmethod_countEnvie exatamente um parâmetro method.
400INVALID_METHOD_PARAMETERaccepted_methodsUse somente o valor method documentado para a rota.
400UNEXPECTED_QUERY_PARAMETERparameters, sem valoresRemova todos os parâmetros não documentados para o comando.
400MISSING_DATA_PARAMETERNenhumEnvie exatamente um data com JSON UTF-8 em base64url sem padding.
400DUPLICATE_DATA_PARAMETERdata_countRemova os parâmetros data extras.
400INVALID_DATA_ENCODINGreasonUse base64url canônico sem padding, whitespace ou alfabeto base64 padrão.
413QUERY_DATA_TOO_LARGEmax_bytes e, quando conhecido, received_bytesReduza o JSON decodificado de data para até 10.000 bytes.
414URI_TOO_LONGuri_bytes, max_uri_bytesReduza data, path ou o tamanho total da URL.
400MISSING_PATH_PARAMETERNenhumEnvie exatamente um parâmetro path.
400DUPLICATE_PATH_PARAMETERpath_countRemova os parâmetros path extras.
400INVALID_JSON_POINTERpath, reasonComece com / e use somente os escapes ~0 e ~1.
400ROOT_PATH_NOT_ALLOWEDNenhumUse PUT /:id para substituir o documento completo.
414PATH_TOO_LONGpath_bytes, max_path_bytesReduza o caminho decodificado para até 4.096 bytes UTF-8.
400PATH_TOO_DEEPsegments, max_segmentsReduza o caminho para até 64 segmentos.
409PATH_TYPE_CONFLICTpath, blocked_at, actual_type, required_typeTroque o ancestral bloqueador por objeto ou array antes da mutação.
409INVALID_ARRAY_INDEXpath, token e regra ou limite aceitoUse índice canônico entre zero e o tamanho atual do array.
409ARRAY_INDEX_OUT_OF_BOUNDSpath, index, array_lengthUse um índice existente ou o tamanho atual para adicionar ao final.
409AMBIGUOUS_PATHNenhumNormalize chaves duplicadas com substituição completa.
409STORED_JSON_INVALIDNenhumSubstitua o documento completo por JSON válido.
409STORED_JSON_TOO_DEEPdocument_depth, max_depthSubstitua o documento completo por JSON com até 1.000 níveis.
409WRITE_CONFLICTretryable: trueLeia a versão atual e tente novamente.
422RESULT_TOO_LARGEresult_bytes, max_bytesReduza o valor ou substitua o documento por uma versão menor.
422RESULT_TOO_DEEPresult_depth, max_depthReduza o resultado para até 1.000 níveis de objetos e arrays.
500STORE_FAILEDNenhumConsulte o item antes de repetir; o estado da gravação pode ser incerto.

O WAF pode retornar 429 antes de a requisição chegar à API. Nesse caso, aguarde o período de bloqueio e tente novamente; o corpo não segue necessariamente o contrato estruturado acima.