Diseño práctico de API pública para creadores de SaaS primerizos: elige versionado, paginación, límites de tasa, documentación y un SDK pequeño que puedas enviar rápido.

Una API pública no es solo un endpoint que expone tu app. Es una promesa a personas fuera de tu equipo de que el contrato seguirá funcionando, incluso cuando cambies el producto.
Lo difícil no es escribir la v1. Es mantenerla estable mientras arreglas bugs, añades funciones y aprendes lo que los clientes realmente necesitan.
Las decisiones tempranas aparecen después como tickets de soporte. Si las respuestas cambian de forma sin aviso, si la nomenclatura es inconsistente, o si los clientes no pueden saber si una petición tuvo éxito, creas fricción. Esa fricción se convierte en desconfianza, y la desconfianza hace que la gente deje de construir sobre ti.
La velocidad también importa. La mayoría de creadores de SaaS primerizos necesitan lanzar algo útil rápido y luego mejorar. El trade-off es simple: cuanto más rápido lances sin reglas, más tiempo pasarás deshaciendo esas decisiones cuando lleguen usuarios reales.
Suficientemente bueno para v1 suele significar un pequeño conjunto de endpoints que mapean acciones reales de usuario, nombres y formas de respuesta consistentes, una estrategia de cambios clara (aunque sea solo v1), paginación predecible y límites de tasa sensatos, y docs que muestren exactamente qué enviar y qué recibirás de vuelta.
Un ejemplo concreto: imagina que un cliente crea una integración que genera facturas cada noche. Si luego renombras un campo, cambias formatos de fecha o empiezas a devolver resultados parciales sin avisar, su trabajo falla a las 2 a. m. Ellos culparán a tu API, no a su código.
Si construyes con una herramienta guiada por chat como Koder.ai, es tentador generar muchos endpoints rápido. Está bien, pero mantén la superficie pública pequeña. Puedes mantener endpoints internos privados mientras aprendes qué debe ser parte del contrato a largo plazo.
Un buen diseño de API pública comienza escogiendo un pequeño conjunto de sustantivos (recursos) que coincidan con cómo los clientes hablan de tu producto. Mantén los nombres de recursos estables aunque tu base de datos interna cambie. Cuando añadas funciones, prefiere agregar campos o nuevos endpoints en vez de renombrar recursos centrales.
Un conjunto práctico inicial para muchos productos SaaS es: users, organizations, projects y events. Si no puedes explicar un recurso en una frase, probablemente no está listo para ser público.
Mantén el uso de HTTP aburrido y predecible:
La autenticación no necesita ser sofisticada el primer día. Si tu API es principalmente servidor a servidor (clientes llamando desde su backend), las API keys suelen ser suficientes. Si los clientes actúan como usuarios finales individuales, o esperas integraciones de terceros donde los usuarios conceden acceso, OAuth suele encajar mejor. Escribe la decisión en lenguaje claro: ¿quién es el llamador y sobre qué datos puede actuar?
Define expectativas desde el inicio. Sé explícito sobre qué está soportado versus esfuerzo puntual. Por ejemplo: los endpoints de listado son estables y retrocompatibles, pero los filtros de búsqueda pueden ampliarse y no garantizan exhaustividad. Esto reduce tickets de soporte y te deja libertad para mejorar.
Si construyes sobre una plataforma de programación por vibra como Koder.ai, trata la API como un contrato de producto: mantenlo pequeño al principio y crece basado en uso real, no en suposiciones.
El versionado trata principalmente sobre expectativas. Los clientes quieren saber: ¿mi integración se romperá la semana que viene? Tú quieres espacio para mejorar sin temores.
El versionado por cabeceras puede verse limpio, pero es fácil ocultarlo en logs, caches y capturas de soporte. El versionado en la URL suele ser la opción más simple: /v1/.... Cuando un cliente te envía una petición fallida, puedes ver la versión de inmediato. También facilita ejecutar v1 y v2 lado a lado.
Un cambio es rompedor si un cliente bien comportado podría dejar de funcionar sin cambiar su código. Ejemplos comunes:
customer_id a customerId)Un cambio seguro es aquel que los clientes antiguos pueden ignorar. Añadir un campo opcional nuevo suele ser seguro. Por ejemplo, agregar plan_name a una respuesta de GET /v1/subscriptions no romperá clientes que solo lean status.
Una regla práctica: no elimines ni reutilices campos dentro de la misma versión mayor. Agrega campos nuevos, conserva los antiguos y retíralos solo cuando estés listo para desaprobar toda la versión.
Mantenlo simple: anuncia deprecaciones con antelación, devuelve un mensaje de advertencia claro en las respuestas y fija una fecha final. Para una primera API, una ventana de 90 días suele ser realista. Durante ese tiempo, mantén v1 funcionando, publica una nota breve de migración y asegúrate de que soporte pueda señalar una frase: v1 funciona hasta esta fecha; esto es lo que cambió en v2.
Si construyes sobre una plataforma como Koder.ai, trata las versiones de API como snapshots: lanza mejoras en una nueva versión, mantiene la antigua estable y córtala solo después de dar tiempo a los clientes para migrar.
La paginación es donde se gana o pierde la confianza. Si los resultados saltan entre peticiones, la gente deja de confiar en tu API.
Usa page/limit cuando el conjunto de datos sea pequeño, la consulta sea simple y los usuarios pidan páginas concretas. Usa paginación basada en cursor cuando las listas puedan crecer mucho, lleguen elementos nuevos con frecuencia o el usuario pueda ordenar y filtrar mucho. La paginación por cursor mantiene la secuencia estable aunque lleguen registros nuevos.
Algunas reglas para mantener la paginación fiable:
Los totales son complicados. Un total_count puede ser costoso en tablas grandes, especialmente con filtros. Si puedes proporcionarlo barato, inclúyelo. Si no, omítelo o hazlo opcional mediante una bandera de consulta.
Aquí hay formas simples de request/response.
// Page/limit
GET /v1/invoices?page=2&limit=25&sort=created_at_desc
{
"items": [{"id":"inv_1"},{"id":"inv_2"}],
"page": 2,
"limit": 25,
"total_count": 142
}
// Cursor-based
GET /v1/invoices?limit=25&cursor=eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDozMDowMFoiLCJpZCI6Imludl8xMDAifQ==
{
"items": [{"id":"inv_101"},{"id":"inv_102"}],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMS0wOVQxMDoyNTowMFoiLCJpZCI6Imludl8xMjUifQ=="
}
Los rate limits no se tratan tanto de ser estrictos como de mantenerse en línea. Protegen tu app de picos de tráfico, tu base de datos de consultas caras repetidas y tu bolsillo de facturas de infraestructura sorpresa. Un límite también es un contrato: los clientes saben cómo es el uso normal.
Empieza simple y ajusta después. Escoge algo que cubra el uso típico con margen para ráfagas cortas, luego observa el tráfico real. Si no tienes datos, un valor seguro es un límite por API key como 60 requests por minuto más una pequeña tolerancia a bursts. Si un endpoint es mucho más pesado (como búsqueda o exportaciones), dale un límite más estricto o una regla de coste separada en vez de penalizar todas las peticiones.
Cuando apliques límites, facilita que los clientes hagan lo correcto. Devuelve un 429 Too Many Requests e incluye unas cabeceras estándar:
X-RateLimit-Limit: el máximo permitido en la ventanaX-RateLimit-Remaining: cuántos quedanX-RateLimit-Reset: cuándo se reinicia la ventana (timestamp o segundos)Retry-After: cuánto esperar antes de reintentarLos clientes deberían tratar el 429 como una condición normal, no un error a pelear. Un patrón de reintentos educado mantiene contentos a ambos lados:
Retry-After cuando esté presenteEjemplo: si un cliente ejecuta un sync nocturno que golpea fuerte tu API, su trabajo puede repartir peticiones a lo largo de un minuto y ralentizarse automáticamente ante 429s en vez de fallar toda la ejecución.
Si los errores de tu API son difíciles de leer, los tickets de soporte se acumulan rápido. Escoge una forma de error y úsala en todas partes, incluso en 500s. Un estándar simple es: code, message, details y un request_id que el usuario pueda pegar en un chat de soporte.
Aquí tienes un formato pequeño y predecible:
{
"error": {
"code": "validation_error",
"message": "Some fields are invalid.",
"details": {
"fields": [
{"name": "email", "issue": "must be a valid email"},
{"name": "plan", "issue": "must be one of: free, pro, business"}
]
},
"request_id": "req_01HT..."
}
}
Usa los códigos de estado HTTP siempre igual: 400 para input inválido, 401 cuando falta auth o es inválida, 403 cuando el usuario está autenticado pero no tiene permiso, 404 cuando un recurso no existe, 409 para conflictos (como un valor único duplicado o estado incorrecto), 429 para límites de tasa y 500 para errores de servidor. La consistencia vence a la originalidad.
Haz que los errores de validación sean fáciles de corregir. Las pistas a nivel de campo deben apuntar al nombre exacto del parámetro que usan tus docs, no a una columna interna. Si hay un requisito de formato (fecha, moneda, enum), di lo que aceptas y muestra un ejemplo.
Los reintentos son donde muchas APIs crean duplicados por accidente. Para POST importantes (pagos, creación de facturas, envío de correos), soporta claves de idempotencia para que los clientes puedan reintentar con seguridad.
Idempotency-Key en endpoints POST seleccionados.Esa única cabecera evita muchos casos límite dolorosos cuando las redes son inestables o los clientes experimentan timeouts.
Imagina que gestionas un SaaS sencillo con tres objetos principales: projects, users e invoices. Un project tiene muchos users y cada project recibe facturas mensuales. Los clientes quieren sincronizar facturas en su herramienta contable y mostrar facturación básica dentro de su propia app.
Una v1 limpia podría verse así:
GET /v1/projects/{project_id}
GET /v1/projects/{project_id}/invoices
POST /v1/projects/{project_id}/invoices
Ahora ocurre un cambio rompedor. En v1 almacenas montos de factura como un entero en centavos: amount_cents: 1299. Más tarde necesitas multi-moneda y decimales, así que quieres amount: "12.99" y currency: "USD". Si sobrescribes el campo antiguo, todas las integraciones existentes se rompen. El versionado evita el pánico: mantén v1 estable, lanza /v2/... con los nuevos campos y soporta ambos hasta que los clientes migren.
Para listar facturas, usa una forma de paginación predecible. Por ejemplo:
GET /v1/projects/p_123/invoices?limit=50&cursor=eyJpZCI6Imludl85OTkifQ==
200 OK
{
"data": [ {"id":"inv_1001"}, {"id":"inv_1000"} ],
"next_cursor": "eyJpZCI6Imludl8xMDAwIn0="
}
Un día un cliente importa facturas en un bucle y alcanza tu límite de tasa. En vez de fallos aleatorios, recibe una respuesta clara:
429 Too Many RequestsRetry-After: 20{ "error": { "code": "rate_limited" } }En su lado, el cliente puede pausar 20 segundos y luego continuar desde el mismo cursor sin volver a descargar todo ni crear facturas duplicadas.
Un lanzamiento v1 va mejor si lo tratas como un pequeño lanzamiento de producto, no como un montón de endpoints. El objetivo es simple: la gente puede construir sobre él y tú puedes seguir mejorándolo sin sorpresas.
Comienza escribiendo una página que explique para qué sirve tu API y para qué no. Mantén la superficie lo suficientemente pequeña como para explicarla en voz alta en un minuto.
Sigue esta secuencia y no pases al siguiente paso hasta que cada uno esté lo suficientemente bueno:
Si trabajas con un flujo que genera código (por ejemplo, usar Koder.ai para esbozar endpoints y respuestas), aun así haz la prueba del cliente falso. El código generado puede parecer correcto y aun así ser incómodo de usar.
La recompensa son menos emails de soporte, menos hotfixes y una v1 que realmente puedas mantener.
Un primer SDK no es un segundo producto. Piénsalo como una capa fina y amigable sobre tu API HTTP. Debe facilitar las llamadas comunes, pero no ocultar cómo funciona la API. Si alguien necesita una característica que no has envuelto, debe poder hacer peticiones crudas.
Elige un lenguaje para empezar, según lo que realmente usan tus clientes. Para muchas APIs B2B SaaS suele ser JavaScript/TypeScript o Python. Enviar un SDK sólido en un lenguaje supera a enviar tres a medias.
Un buen conjunto inicial es:
Puedes hacerlo a mano o generarlo a partir de un spec OpenAPI. La generación es estupenda cuando tu spec es precisa y quieres tipado consistente, pero a menudo produce mucho código. Al principio, un cliente mínimo hecho a mano más un archivo OpenAPI para docs suele ser suficiente. Puedes cambiar a clientes generados más adelante sin romper usuarios, siempre que la interfaz pública del SDK se mantenga estable.
Tu versión de API debe seguir tus reglas de compatibilidad. La versión del SDK debe seguir reglas de empaquetado.
Si agregas parámetros opcionales o endpoints nuevos, eso suele ser un bump menor del SDK. Reserva releases mayores del SDK para cambios rompedores en el propio SDK (renombrar métodos, cambiar defaults), incluso si la API no cambió. Esa separación mantiene las actualizaciones tranquilas y los tickets bajos.
La mayoría de tickets de API no son bugs. Son sorpresas. El diseño público de API se trata sobre todo de ser aburrido y predecible para que el código cliente siga funcionando mes tras mes.
La forma más rápida de perder confianza es cambiar respuestas sin avisar. Si renombras un campo, cambias un tipo o empiezas a devolver null donde antes había un valor, romperás clientes de formas difíciles de diagnosticar. Si debes cambiar comportamiento, versiona o añade un campo nuevo y conserva el antiguo con un plan claro de retirada.
La paginación es otro recurrente. Los problemas aparecen cuando un endpoint usa page/pageSize, otro usa offset/limit y un tercero usa cursores, todos con defaults distintos. Elige un patrón para v1 y úsalo en todas partes. Mantén también el orden estable para que la siguiente página no salte o repita items cuando lleguen registros nuevos.
Los errores generan mucho ida y vuelta cuando son inconsistentes. Un fallo común es un servicio devolviendo { "error":"..." } y otro { "message":"..." }, con códigos de estado distintos para el mismo problema. Los clientes entonces construyen handlers desordenados y específicos por endpoint.
Aquí hay cinco errores que generan los hilos de email más largos:
Un hábito simple ayuda: cada respuesta debería incluir un request_id, y cada 429 debería explicar cuándo reintentar.
Antes de publicar cualquier cosa, haz una pasada final enfocada en la consistencia. La mayoría de tickets de soporte ocurren porque pequeños detalles no coinciden entre endpoints, docs y ejemplos.
Chequeos rápidos que atrapan la mayoría de problemas:
Tras el lanzamiento, observa lo que la gente realmente usa, no lo que esperabas que usaran. Un pequeño dashboard y una revisión semanal son suficientes al principio.
Monitorea estas señales primero:
Recoge feedback sin reescribirlo todo. Añade un camino corto para reportar un problema en tus docs, y etiqueta cada reporte con endpoint, request id y versión del cliente. Cuando arregles algo, prefiere cambios aditivos: nuevos campos, parámetros opcionales o un endpoint nuevo, en lugar de romper comportamiento existente.
Siguientes pasos: escribe una especificación de una página con tus recursos, plan de versionado, reglas de paginación y formato de errores. Luego produce docs y un SDK inicial que cubra autenticación más 2–3 endpoints centrales. Si quieres moverte más rápido, puedes redactar la spec, las docs y un SDK inicial desde un plan basado en chat usando herramientas como Koder.ai (su modo de planificación es útil para mapear endpoints y ejemplos antes de generar código).
Comienza con 5–10 endpoints que reflejen acciones reales de los clientes.
Una buena regla: si no puedes explicar un recurso en una frase (qué es, quién lo posee, cómo se usa), mantenlo privado hasta aprender más por uso.
Elige un pequeño conjunto de sustantivos estables (recursos) que los clientes ya usan al hablar de tu producto, y mantén esos nombres estables aunque cambie tu base de datos.
Arranques comunes para SaaS son users, organizations, projects y events, y agrega más solo cuando haya demanda clara.
Usa los significados estándar y sé consistente:
GET = leer (sin efectos secundarios)POST = crear o iniciar una acciónPATCH = actualización parcialDELETE = eliminar o deshabilitarLa principal ventaja es la previsibilidad: los clientes no deben adivinar qué hace un método.
Por defecto, usa versionado en la URL como /v1/....
Es más fácil verlo en logs y capturas de soporte, y permite ejecutar v1 y v2 lado a lado cuando necesitas un cambio que rompe compatibilidad.
Un cambio es rompedor si un cliente correcto puede fallar sin cambiar su código. Ejemplos comunes:
Agregar un campo opcional nuevo suele ser seguro.
Manténlo simple:
Un valor práctico por defecto para una primera API es una ventana de 90 días para que los clientes tengan tiempo de migrar sin pánico.
Elige un patrón y úsalo en todos los endpoints de listado.
Siempre define un orden por defecto y un desempate (por ejemplo + ) para que los resultados no salten.
Comienza con un límite por clave claro (por ejemplo 60 requests/minuto) más un pequeño burst, y ajusta según tráfico real.
Al limitar, devuelve 429 e incluye:
X-RateLimit-LimitX-RateLimit-RemainingUsa un único formato de error en todas partes (incluso en 500s). Una forma práctica es:
code (identificador estable)message (legible para humanos)details (problemas a nivel de campo)request_id (para soporte)Mantén los códigos de estado consistentes (400/401/403/404/409/429/500) para que los clientes manejen errores limpiamente.
Si generas muchos endpoints rápidamente (por ejemplo con Koder.ai), mantén la superficie pública pequeña y trátala como un contrato a largo plazo.
Antes del lanzamiento:
POST importantescreated_atidX-RateLimit-ResetRetry-AfterEsto hace que los reintentos sean previsibles y reduce tickets de soporte.
Luego publica un SDK pequeño que facilite auth, timeouts, reintentos en requests seguras y paginación, sin ocultar cómo funciona la API HTTP.