API de Solta
Todo lo que hace la interfaz, lo hace a través de esto. No hay otro camino, y por eso también un agente conectado a tu cuenta no puede llegar a nada a lo que tú no podrías llegar.
Autenticación
Una clave creada en Ajustes → Claves y conexiones → Claves de API, enviada como bearer token. Se muestra una sola vez y se guarda como hash, así que una filtración de nuestra base de datos no es una lista de claves que funcionan.
curl https://solta.now/v1/me \
-H "Authorization: Bearer solta_…"Los conectores usan OAuth: consulta Conectar Claude o ChatGPT. Los dos llevan a la misma cuenta, con la misma autoridad.
Publicar un archivo
curl -X POST https://solta.now/v1/sites \
-H "Authorization: Bearer solta_…" \
-H "Content-Type: application/json" \
-d '{"source": "export default () => <h1>hi</h1>",
"filename": "App.tsx",
"title": "Hello",
"channel": "api"}'Devuelve el slug, el número de versión y el enlace. Envía un encabezado Idempotency-Key y, si repites la misma solicitud, no se publica dos veces.
Una persona en Free tiene 3 sitios en línea a la vez, entre su propia biblioteca y los espacios de trabajo Free de los que es dueña, y en Plus 15 sitios entre sus bibliotecas Plus. Pro y Team no tienen límite. En el límite, un sitio nuevo (un archivo, una carpeta o un remix) recibe un 402 con el código site_limit: no se publica nada, y la respuesta lista los sites en línea, cada uno con su library, cuántos tienen que salir (toUnpublish), y la libraryUrl y la upgradeUrl. En su lugar, agrega una versión a uno de los sitios, lo que conserva su enlace, o haz espacio antes: POST /v1/sites/{id}/offline desconecta un sitio sin perder nada, y POST /v1/sites/{id}/online lo vuelve a poner en línea, algo que en el límite se rechaza de la misma forma. Una versión nueva de un sitio nunca se rechaza por esto.
Publicar una carpeta
Un endpoint aparte, porque el cuerpo es un archivo y no JSON. Envía el archivo comprimido como archive, o una url desde la que debamos leerlo.
curl -X POST https://solta.now/v1/sites/archive \
-H "Authorization: Bearer solta_…" \
-F "archive=@dist.zip" \
-F "title=My site"Los archivos que un sitio no usa quedan fuera y el resto se publica: todo lo que puede contener secretos (.env y cada .env.*, .npmrc, claves privadas, credentials.json), los archivos y carpetas ocultos como .git, y los tipos que no servimos, como el código de servidor. La respuesta los lista en skipped, cada uno con su path y un reason que es secret, hidden o unsupported, y dice lo mismo en warnings, para que lo lea una persona. Una carpeta con el código fuente de una app, y no con su build, recibe un 422 con el código needs_build.
Actualizar sin romper el enlace
Una versión nueva es un borrador. El enlace sigue mostrando lo que mostraba hasta que la publiques, y eso lleva dos llamadas a propósito, para que nada se publique por accidente.
POST /v1/sites/{id}/versions → { number: 2, state: "draft" }
POST /v1/sites/{id}/versions/2/publish → { number: 2, state: "live" }O una sola llamada: "publish": true en la primera la pone en línea de inmediato, a menos que el sitio necesite aprobación. En ese caso, la respuesta trae awaitsApproval: true y la versión espera como borrador.
De vez en cuando, nuestras revisiones piden que una persona vea una página antes de publicarla, normalmente en un día como máximo. La respuesta trae entonces held: { url }: el enlace del sitio dice que la página se está revisando hasta que se apruebe, y url es una página que dice cómo va la revisión, donde quien publicó sin cuenta puede dejar un correo para que le avisen. Publicarla otra vez no cambia nada.
Una carpeta cambia por sus archivos. POST /v1/sites/{id}/versions/files recibe multipart form data: files para agregar (un .zip entre ellos se abre, y un archivo con un nombre que ya existe reemplaza al anterior), un remove por cada ruta que quieras quitar, y replace=true cuando lo que subes es el sitio entero. Con publish=true, la versión se pone en línea en la misma llamada, a menos que el sitio necesite aprobación. En ese caso, la respuesta trae awaitsApproval: true y la versión espera como borrador. Entre varios archivos, o en un .zip, lo que una carpeta deja fuera también queda fuera aquí, y aparece en skipped. En cambio, un archivo enviado solo que no publicaríamos se rechaza.
El resto
- GET /v1/library
- Tus sitios.
- GET /v1/sites/{slug}
- Un sitio, su versión en línea y su historial.
- PATCH /v1/sites/{id}/visibility
- Quién puede abrirlo.
- PATCH /v1/sites/{id}
- Su título, o su dirección con
{ "slug": "…" }. La dirección anterior sigue llevando a la gente al sitio. Una dirección corta, de 4 caracteres o menos, o una dirección de una sola palabra, una palabra común sola, necesita Pro o Team: en Free es un402concode: "needs_pro_short_address", y pasadas las direcciones cortas o de una palabra que permite el plan,short_address_limit. Una dirección con el nombre de una empresa o de un banco es un422conslug_brand, en cualquier plan. - GET, POST /v1/sites/{id}/addresses
- Sus direcciones, y otra más con
{ "slug": "…" }que lleva a los visitantes a la principal (Pro y Team).POST …/addresses/{slug}/mainconvierte una en la dirección principal, yDELETE …/addresses/{slug}la convierte en una dirección anterior. Se rechaza conneeds_pro_extra_address,extra_address_limit,needs_pro_short_address,short_address_limit,slug_brandoslug_taken. - GET /v1/sites/{id}/compare?from=1&to=2
- Qué cambió entre dos versiones.
- DELETE /v1/sites/{id}
- El sitio deja de servirse de inmediato. La dirección queda reservada al menos un año, y mientras se use. Después de 6 meses sin visitas te enviamos un correo, y un mes después queda libre.
Errores
Un cuerpo JSON con error, y detail cuando hay algo útil que agregar. Un archivo que rechazamos (una clave dejada en el archivo, un tipo no compatible) es un 422 con el motivo en palabras, no un stack trace.

