Llevarte tus registros.
La gente de tu lista de espera son tus contactos, no los nuestros. Hay cuatro maneras de moverlos a tu propia aplicación, y no necesitas pedirnos que activemos ninguna.
Cuál te conviene
Descarga el CSV. Es la lista completa, con respuestas, conteos de referidos y parámetros UTM. En Starter, Everything y Studio.
Consulta la API REST con updated_since. Esta es la opción sobre la que construir. En Everything y Studio.
Apunta un webhook a tu propio endpoint. Firmado, con reintentos, y puedes ver cada entrega.
Zapier, para las herramientas cuya API no vale la pena llamar.
El CSV
En la pantalla de Registros de una lista, elige Exportar. Una fila por registro, una columna por pregunta, y una marca de orden de bytes para que Excel lea bien los nombres en UTF-8 en vez de destrozarlos. Los valores que empiezan con =, +, - o @ llevan un prefijo, para que la hoja de cálculo trate la respuesta como texto y no como fórmula.
Sirve para una migración que haces una vez. Para cualquier cosa continua, usa la API: un CSV es una foto fija y no puede decirte que alguien confirmó su dirección ayer.
Autenticarte en la API
Crea una clave en Ajustes → Claves de API. Se muestra una sola vez. Guárdala en una variable de entorno en vez de escribirla en la terminal — una clave pegada termina en tu historial — y envíala como bearer token:
export OSOKORO_API_KEY=… # la clave que acabas de ver curl https://osokoro.com/api/v1/signups?project=your-waitlist \ -H "Authorization: Bearer $OSOKORO_API_KEY"
Las claves pertenecen a la organización que las creó y no tienen alcance por proyecto: elige la lista con project. El plan se verifica en cada petición, no cuando se creó la clave, así que una clave deja de funcionar en cuanto una organización pierde el acceso a la API y vuelve a funcionar si lo recupera. Revocar una clave surte efecto de inmediato.
Cada respuesta trae x-ratelimit-limit, x-ratelimit-remaining y x-ratelimit-reset, así puedes bajar el ritmo antes de que te lo pidan. El cupo por hora es Everything: 1000, Studio: 10.000. Cada respuesta trae también x-request-id; cítalo si reportas un problema y podremos encontrar la petición.
GET /api/v1/signups
| Parámetro | Acepta | Qué hace |
|---|---|---|
| project | obligatorio | El slug de la lista, tal como aparece en la URL de su página. |
| limit | 1–500, por defecto 100 | Cuántas filas devolver. |
| cursor | opaco | El next_cursor de la página anterior. Omítelo en la primera. |
| sort | created_at | updated_at | Qué marca de tiempo ordena los resultados. Por defecto, created_at. |
| updated_since | ISO 8601 | Solo las filas cambiadas en ese instante o después. Esto es lo que hace posible una sincronización incremental. |
| verified | true | Solo los registros que confirmaron su dirección de correo. |
Una página se ve así:
{
"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 estable. Úsalo como tu propia clave foránea. |
| La dirección tal como se envió. | |
| verified | Si hicieron clic en el enlace de su correo de confirmación. |
| verified_at | Cuándo lo hicieron, o null. |
| position_code | Su código de referido — el que lleva su enlace para compartir. |
| referral_count | Cuántos registros confirmados llegaron por su enlace. |
| referred | Si llegaron por el enlace de otra persona. |
| answers | Un objeto con las claves de tus preguntas. |
| utm | Los parámetros UTM presentes cuando llegaron. |
| created_at | Cuándo se registraron. |
| updated_at | Cuándo cambió la fila por última vez. Sincroniza a partir de esto. |
Leer la lista completa
Sigue next_cursor hasta que has_more sea false. El cursor es un keyset sobre el campo de orden y el id de la fila, así que las filas que comparten marca de tiempo — cosa que una importación masiva produce por miles — nunca se saltan en un borde de página ni se devuelven dos veces.
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);Mantenerse al día después
Guarda el updated_at más alto que hayas visto y devuélvelo como updated_since, ordenando por updated_at. Eso devuelve tanto las filas que cambiaron como las nuevas — así te enteras de que alguien confirmó su dirección o se dio de baja, algo que una consulta por created_at nunca puede decirte.
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 8601Usa id como clave de tu lado y haz upsert. Los registros nunca se renumeran, así que un id que guardaste el mes pasado sigue apuntando a la misma persona.
Cuando algo sale mal
Todos los fallos tienen la misma forma — { "error": { "code", "message", "request_id" } } — así que puedes ramificar por code y nunca por el texto. Una lista que pertenece a otra organización responde exactamente igual que una que no existe; eso es deliberado, para que una clave no sirva para descubrir los slugs de otra persona.
| Estado | Código | Significado |
|---|---|---|
| 401 | unauthorized | La clave es desconocida, fue revocada, o es una de prueba enviada como real. |
| 402 | plan_without_api | El plan de la organización no incluye acceso a la API. |
| 429 | quota_exceeded | Se agotaron las peticiones de la hora. retry-after dice cuánto esperar. |
| 400 | project_required | Falta el parámetro `project`. |
| 404 | not_found | No hay ninguna lista con ese slug en esta cuenta. |
| 400 | invalid_limit · invalid_sort · invalid_updated_since · invalid_cursor | El parámetro indicado no era utilizable. El mensaje dice cómo debería ser. |
| 503 | unavailable | No pudimos verificar la clave. Reintenta; esto no es un rechazo. |
Webhooks, para el momento en que pasa
Agrega un endpoint en los ajustes de una lista y elige qué eventos debe recibir:
- signup.created
- signup.verified
- signup.unsubscribed
- perk.claimed
- broadcast.sent
El cuerpo es { "event", "data" }. Cada entrega lleva x-osokoro-event y x-osokoro-signature, que contiene una marca de tiempo y un HMAC-SHA256 de timestamp.body firmado con el secreto de tu endpoint. Verifícalo antes de confiar en una petición, y compara en tiempo constante. Una entrega fallida se reintenta con intervalos crecientes durante alrededor de medio día, y cada intento aparece con su estado para que veas qué pasó en vez de adivinarlo.
Los webhooks te avisan de los cambios. No son una forma de leer la lista que ya tienes — combínalos con una pasada completa por la API, o con el CSV.
Borrar tus datos
Llevarte una copia no te obliga a dejar otra atrás. Borrar una organización desde Ajustes elimina sus listas, registros y respuestas; lo que queda es un registro agregado con conteos y sin direcciones. Consulta el aviso de privacidad para saber qué guarda ese registro y por qué.