Pular para o conteúdo
OSOKORO
Para desenvolvedores

Levando suas inscrições com você.

As pessoas da sua lista de espera são seus contatos, não nossos. Existem quatro jeitos de movê-las para o seu próprio aplicativo, e você não precisa pedir para a gente liberar nenhum deles.

Qual deles você quer

Uma mudança de uma vez só

Baixe o CSV. É a lista inteira, com respostas, contagem de indicações e parâmetros UTM. No Starter, Everything e Studio.

Manter seu próprio banco de dados em dia

Consulte a API REST com updated_since. É esta que vale a pena construir em cima. No Everything e Studio.

Reagir no instante em que alguém se inscreve

Aponte um webhook para o seu próprio endpoint. Assinado, com novas tentativas, e você vê cada entrega.

Ligar isso a algo que você não escreveu

Zapier, para as ferramentas cuja API não vale a pena chamar.

O CSV

Na tela de Inscrições de uma lista, escolha Exportar. Uma linha por inscrição, uma coluna por pergunta, e uma marca de ordem de bytes para o Excel ler nomes em UTF-8 direito em vez de embaralhá-los. Valores que começam com =, +, - ou @ recebem um prefixo, para a planilha tratar a resposta como texto e não como fórmula.

Bom para uma migração feita uma vez. Para qualquer coisa contínua, use a API — um CSV é uma foto e não consegue dizer que alguém confirmou o endereço ontem.

Autenticando na API

Crie uma chave em Configurações → Chaves de API. Ela aparece uma única vez. Guarde em uma variável de ambiente em vez de digitar no terminal — uma chave colada acaba no seu histórico — e envie como bearer token:

export OSOKORO_API_KEY=…            # a chave que você acabou de ver

curl https://osokoro.com/api/v1/signups?project=your-waitlist \
  -H "Authorization: Bearer $OSOKORO_API_KEY"

As chaves pertencem à organização que as criou e não têm escopo por projeto — escolha a lista com project. O plano é verificado a cada requisição, não quando a chave foi criada, então uma chave para de funcionar no instante em que a organização perde acesso à API e volta a funcionar se ele retornar. Revogar uma chave tem efeito imediato.

Toda resposta traz x-ratelimit-limit, x-ratelimit-remaining e x-ratelimit-reset, então você pode desacelerar antes de ser avisado. A cota por hora é Everything: 1.000, Studio: 10.000. Toda resposta traz também x-request-id; cite esse valor ao relatar um problema e conseguimos achar a requisição.

GET /api/v1/signups

ParâmetroAceitaO que faz
projectobrigatórioO slug da lista, como aparece na URL da página dela.
limit1–500, padrão 100Quantas linhas devolver.
cursoropacoO next_cursor da página anterior. Omita na primeira.
sortcreated_at | updated_atQual carimbo de tempo ordena os resultados. O padrão é created_at.
updated_sinceISO 8601Só as linhas alteradas nesse instante ou depois. É isso que torna possível uma sincronização incremental.
verifiedtrueSó as inscrições que confirmaram o endereço de e-mail.

Uma página é assim:

{
  "data": [
    {
      "id": "8f0c…",
      "email": "someone@example.com",
      "verified": true,
      "verified_at": "2026-08-02T09:14:22.104Z",
      "position_code": "8F2QK",
      "referral_count": 3,
      "referred": false,
      "answers": { "price": "$10", "need": "Offline mode" },
      "utm": { "utm_source": "producthunt" },
      "created_at": "2026-08-01T18:02:11.882Z",
      "updated_at": "2026-08-02T09:14:22.104Z"
    }
  ],
  "has_more": true,
  "next_cursor": "eyJzb3J0Ijoi…",
  "request_id": "5c1f…"
}
CampoSignificado
idIdentificador estável. Use como sua própria chave estrangeira.
emailO endereço como foi enviado.
verifiedSe clicaram no link do e-mail de confirmação.
verified_atQuando clicaram, ou null.
position_codeO código de indicação — o que o link de compartilhamento carrega.
referral_countQuantas inscrições confirmadas vieram do link dessa pessoa.
referredSe chegaram pelo link de outra pessoa.
answersUm objeto com as chaves das suas perguntas.
utmOs parâmetros UTM presentes quando chegaram.
created_atQuando se inscreveram.
updated_atQuando a linha mudou pela última vez. Baseie sua sincronização nisso.

Lendo a lista inteira

Siga next_cursor até has_more ser false. O cursor é um keyset sobre o campo de ordenação e o id da linha, então linhas que dividem o mesmo carimbo de tempo — o que uma importação em massa produz aos milhares — nunca são puladas na virada de página nem devolvidas duas vezes.

let cursor = null;
const everyone = [];

do {
  const url = new URL("https://osokoro.com/api/v1/signups");
  url.searchParams.set("project", "your-waitlist");
  url.searchParams.set("limit", "500");
  if (cursor) url.searchParams.set("cursor", cursor);

  const response = await fetch(url, {
    headers: { authorization: `Bearer ${process.env.OSOKORO_API_KEY}` },
  });
  if (!response.ok) throw new Error((await response.json()).error.code);

  const page = await response.json();
  everyone.push(...page.data);
  cursor = page.next_cursor;
} while (cursor);

Ficando em dia depois

Guarde o maior updated_at que você viu e devolva como updated_since, ordenando por updated_at. Isso devolve tanto as linhas que mudaram quanto as novas — então você fica sabendo que alguém confirmou o endereço ou se descadastrou, o que uma consulta por created_at nunca consegue dizer.

const url = new URL("https://osokoro.com/api/v1/signups");
url.searchParams.set("project", "your-waitlist");
url.searchParams.set("sort", "updated_at");
url.searchParams.set("updated_since", lastSyncedAt);  // ISO 8601

Use id como chave do seu lado e faça upsert. As inscrições nunca são renumeradas, então um id guardado no mês passado ainda aponta para a mesma pessoa.

Quando algo dá errado

Toda falha tem o mesmo formato — { "error": { "code", "message", "request_id" } } — então você pode ramificar por code e nunca pelo texto. Uma lista que pertence a outra organização responde exatamente como uma que não existe; isso é proposital, para que uma chave não sirva para descobrir os slugs de outra pessoa.

Status HTTPCódigoSignificado
401unauthorizedA chave é desconhecida, foi revogada, ou é uma chave de teste enviada como real.
402plan_without_apiO plano da organização não inclui acesso à API.
429quota_exceededAs requisições da hora acabaram. retry-after diz quanto esperar.
400project_requiredFaltou o parâmetro `project`.
404not_foundNenhuma lista com esse slug nesta conta.
400invalid_limit · invalid_sort · invalid_updated_since · invalid_cursorO parâmetro citado não era utilizável. A mensagem diz como ele deveria ser.
503unavailableNão conseguimos verificar a chave. Tente de novo; isto não é uma recusa.

Webhooks, para o instante em que acontece

Adicione um endpoint nas configurações de uma lista e escolha quais eventos ele deve receber:

  • signup.created
  • signup.verified
  • signup.unsubscribed
  • perk.claimed
  • broadcast.sent

O corpo é { "event", "data" }. Cada entrega carrega x-osokoro-event e x-osokoro-signature, que guarda um carimbo de tempo e um HMAC-SHA256 de timestamp.body assinado com o segredo do seu endpoint. Verifique antes de confiar na requisição, e compare em tempo constante. Uma entrega que falha é repetida em intervalos crescentes por cerca de meio dia, e cada tentativa aparece com seu status para você ver o que aconteceu em vez de adivinhar.

Webhooks avisam sobre mudanças. Eles não são um jeito de ler a lista que você já tem — combine com uma passada completa pela API, ou com o CSV.

Apagando seus dados

Levar uma cópia não obriga você a deixar outra para trás. Apagar uma organização em Configurações remove suas listas, inscrições e respostas; o que fica é um registro agregado com contagens e nenhum endereço. Veja o aviso de privacidade para saber o que esse registro guarda e por quê.