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
Baixe o CSV. É a lista inteira, com respostas, contagem de indicações e parâmetros UTM. No Starter, Everything e Studio.
Consulte a API REST com updated_since. É esta que vale a pena construir em cima. No Everything e Studio.
Aponte um webhook para o seu próprio endpoint. Assinado, com novas tentativas, e você vê cada entrega.
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âmetro | Aceita | O que faz |
|---|---|---|
| project | obrigatório | O slug da lista, como aparece na URL da página dela. |
| limit | 1–500, padrão 100 | Quantas linhas devolver. |
| cursor | opaco | O next_cursor da página anterior. Omita na primeira. |
| sort | created_at | updated_at | Qual carimbo de tempo ordena os resultados. O padrão é created_at. |
| updated_since | ISO 8601 | Só as linhas alteradas nesse instante ou depois. É isso que torna possível uma sincronização incremental. |
| verified | true | Só 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…"
}| Campo | Significado |
|---|---|
| id | Identificador estável. Use como sua própria chave estrangeira. |
| O endereço como foi enviado. | |
| verified | Se clicaram no link do e-mail de confirmação. |
| verified_at | Quando clicaram, ou null. |
| position_code | O código de indicação — o que o link de compartilhamento carrega. |
| referral_count | Quantas inscrições confirmadas vieram do link dessa pessoa. |
| referred | Se chegaram pelo link de outra pessoa. |
| answers | Um objeto com as chaves das suas perguntas. |
| utm | Os parâmetros UTM presentes quando chegaram. |
| created_at | Quando se inscreveram. |
| updated_at | Quando 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 8601Use 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 HTTP | Código | Significado |
|---|---|---|
| 401 | unauthorized | A chave é desconhecida, foi revogada, ou é uma chave de teste enviada como real. |
| 402 | plan_without_api | O plano da organização não inclui acesso à API. |
| 429 | quota_exceeded | As requisições da hora acabaram. retry-after diz quanto esperar. |
| 400 | project_required | Faltou o parâmetro `project`. |
| 404 | not_found | Nenhuma lista com esse slug nesta conta. |
| 400 | invalid_limit · invalid_sort · invalid_updated_since · invalid_cursor | O parâmetro citado não era utilizável. A mensagem diz como ele deveria ser. |
| 503 | unavailable | Nã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ê.