Desarrolladores y agentes
API de MercadoSolar
MercadoSolar es un marketplace de energía solar en Guatemala: el cliente publica su proyecto y los instaladores certificados compiten en una licitación. Esta página documenta la superficie HTTP del portal, cómo se autentica, qué límites tiene y qué partes puedes leer sin cuenta.
Lo primero, si eres un agente: /llms.txt te dice en una página cuándo tiene sentido traer a MercadoSolar a una conversación y cuándo no.
Archivos legibles por máquina
- /llms.txt
Qué es MercadoSolar, cuándo conviene usarlo, qué no hacemos y el índice de páginas. Escrito para agentes.
- /openapi.json
Especificación OpenAPI 3.1 de la API del portal, con esquemas y códigos de error.
- /openapi.yaml
La misma especificación, en YAML.
- /.well-known/oauth-protected-resource
Metadatos de recurso protegido (RFC 9728): el catálogo de alcances con nombre y el servidor de autorización que emite la sesión.
- /api/v1/sandbox
Índice del entorno de pruebas: qué hay, con qué identificadores probarlo y qué operación real espeja cada uno.
- /sitemap.xml
Todas las páginas públicas rastreables.
- /robots.txt
Reglas de rastreo. Las áreas con sesión están bloqueadas.
Cómo empezar sin cuenta
No hay formulario de contacto entre tú y la primera llamada. Tres cosas se pueden usar hoy mismo, sin registro y sin llave:
- El entorno de pruebas. /api/v1/sandbox devuelve la misma forma que las operaciones reales, con datos inventados. Sin sesión, sin llave y de solo lectura.
- Las rutas públicas de producción. La ruta de eventos y la de reseñas externas funcionan sin credencial, con su cuota publicada.
- Todo el contenido. Las páginas públicas se sirven en Markdown, y la especificación completa está en /openapi.json.
curl -s https://mercadosolar.com.gt/api/v1/sandbox
curl -s https://mercadosolar.com.gt/api/v1/sandbox/proyectos/11111111-1111-4111-8111-111111111111/cotizaciones
curl -s https://mercadosolar.com.gt/api/v1/sandbox/acuerdos/22222222-2222-4222-8222-222222222222El sandbox espeja la FORMA, no el contenido: mismos nombres de campo, mismos tipos, mismos alias y mismos códigos de error. Un identificador que no sea el suyo devuelve el mismo 404 que devolvería la operación real, para que los errores no se descubran en producción.
Lo que no hay, dicho sin rodeos: no emitimos llaves de API para terceros y no hay alta automática. Para operar sobre datos reales hace falta la sesión de un usuario del portal. Si tu integración necesita otra cosa, escribe a hola@mercadosolar.com.gt.
Permisos con nombre
Cada operación declara el permiso que necesita, y esos permisos tienen nombre. Si tu agente solo va a leer cotizaciones, puede decirlo y el portal lo obliga a quedarse ahí: manda la cabecera MS-SCOPES con la lista de alcances a los que te limitas, separados por espacios.
curl -s https://mercadosolar.com.gt/api/v1/sandbox/proyectos/11111111-1111-4111-8111-111111111111/cotizaciones \
-H "MS-SCOPES: read:sandbox"
# Fuera de lo declarado: 403 con el alcance que faltó en el WWW-Authenticate
curl -si -X POST https://mercadosolar.com.gt/api/v1/eventos \
-H "MS-SCOPES: read:sandbox" \
-H "content-type: application/json" -d '{}' | head -3La cabecera solo puede restringir: no concede nada que la sesión no concediera ya, y sin ella todo se comporta como siempre. El catálogo, legible por máquina, está en /.well-known/oauth-protected-resource (RFC 9728), y es el mismo que apunta el WWW-Authenticate de cualquier 401.
| Alcance | Qué permite |
|---|---|
| read:metadatos | Leer los archivos legibles por máquina: especificación, llms.txt y metadatos de recurso protegido. |
| read:sandbox | Leer el entorno de pruebas con datos sintéticos. No toca datos reales de nadie. |
| write:eventos | Registrar eventos de embudo anónimos. |
| write:resenas | Enviar una reseña externa por el enlace de autoservicio del instalador. |
| read:cotizaciones | Leer las cotizaciones y el hilo de preguntas de un proyecto propio. |
| write:licitacion | Seleccionar al instalador ganador y publicar preguntas en la licitación. |
| read:acuerdos | Leer y descargar el acuerdo de adjudicación. |
| write:acuerdos | Generar, firmar o rechazar un acuerdo de adjudicación. |
| write:instalacion | Reportar la instalación terminada o abrir una disputa. |
Versionado y obsolescencia
La forma canónica de cada ruta lleva la versión: /api/v1/…. La forma sin versión (/api/…) sigue viva como alias y resuelve a lo mismo, porque es la que llama el propio portal desde el navegador. Toda respuesta de la API lleva la cabecera MS-API-Version, así que sabes contra qué hablaste aunque hayas pedido la forma corta.
Qué garantiza v1: no cambia de forma de manera incompatible. Agregar un campo a una respuesta o un parámetro opcional no es incompatible, y tu cliente debe ignorar lo que no conoce. Quitar un campo, cambiar su tipo, cambiar un código de error o mover una ruta sí lo es, y eso estrena una versión nueva.
Cómo avisamos antes de romper: entre el aviso y el apagón hay al menos 180 días, y el aviso viaja en la propia respuesta, no en un blog que tendrías que vigilar.
Deprecation(RFC 9745): cuándo se marcó.Sunset(RFC 8594): la fecha del apagón.Link; rel="deprecation": la explicación, yrel="successor-version"cuando hay reemplazo.- Una versión ya apagada responde
410, nunca un 404 ni un silencio.
Hoy no hay ninguna ruta marcada como obsoleta. Cuando la haya, aparece en esta lista y en la especificación.
Autenticación
No emitimos llaves de API para terceros y no hay entorno de pruebas público. La autorización viaja en la cookie de sesión que el portal emite al iniciar sesión con Supabase Auth, y cada ruta comprueba el rol y la propiedad del recurso del lado del servidor. En la práctica: un agente puede leer todo el contenido público sin cuenta, y para operar necesita la sesión de un usuario real del portal.
Las operaciones marcadas como públicas en la tabla de abajo no llevan sesión. Las demás responden 401 con {"error":"no_autenticado"} cuando falta.
Si necesitas una integración, escribe a hola@mercadosolar.com.gt y cuéntanos qué quieres construir.
Límites de uso
Las rutas públicas responden con las cabeceras de límite de la RFC 9331, para que no tengas que descubrir la cuota chocando contra ella:
RateLimit: estado de la ventana actual, por ejemplolimit=60, remaining=59, reset=57.RateLimit-Policy: la política aplicada, por ejemplo60;w=60.Retry-After: solo en un429, con los segundos que faltan.
Cuotas vigentes por IP: 60 peticiones por 60 segundos en la ruta de eventos, 10 por 600 segundos al enviar una reseña, 5 por 900 segundos al enviar una solicitud de vendedor, y 120 por 60 segundos en las variantes Markdown. El contador vive en memoria de cada instancia del servidor, así que el límite efectivo puede ser algo más alto que el publicado. Trátalo como un piso, no como un permiso.
Contenido en Markdown
Las páginas públicas se sirven en Markdown si lo pides, siguiendo la convención de acceptmarkdown.com. Misma URL, otra representación, con Vary: Accept para que ningún caché mezcle las dos.
curl -H "Accept: text/markdown" https://mercadosolar.com.gt/guia
curl https://mercadosolar.com.gt/guia.mdFunciona en la portada, la guía, las preguntas frecuentes, el blog y sus artículos, el directorio de instaladores, la página para instaladores, contacto y esta misma página. Si rechazas los dos tipos que producimos, la respuesta es 406, no un HTML que no pediste.
Rutas que no existen
Una ruta inexistente devuelve 404 de verdad, y en Markdown si lo pides. Puedes sondear rutas sin que el sitio te conteste que todas existen.
curl -s -o /dev/null -w "%{http_code}\n" https://mercadosolar.com.gt/no-existe
404Endpoints
La lista completa, con esquemas de entrada y salida y todos los códigos de error, está en /openapi.json. Las rutas bajo /api/admin no se documentan: son la consola interna, cambian sin aviso y no son una superficie estable.
| Método | Ruta | Qué hace | Alcance |
|---|---|---|---|
| GET | /openapi.json público | Descarga esta especificación | read:metadatos |
| GET | /llms.txt público | Guía para agentes | read:metadatos |
| GET | /openapi.yaml público | Descarga esta especificación en YAML | read:metadatos |
| GET | /.well-known/oauth-protected-resource público | Metadatos de recurso protegido (RFC 9728) | read:metadatos |
| GET | /api/v1/sandbox público | Índice del entorno de pruebas | read:sandbox |
| GET | /api/v1/sandbox/proyectos/{id}/cotizaciones público | Espejo de las cotizaciones de un proyecto, con datos sintéticos | read:sandbox |
| GET | /api/v1/sandbox/acuerdos/{id} público | Espejo de un acuerdo de adjudicación, con datos sintéticos | read:sandbox |
| POST | /api/v1/eventos público | Registra un evento de embudo | write:eventos |
| POST | /api/v1/review/{slug} público | Envía una reseña de un proyecto anterior a MercadoSolar | write:resenas |
| POST | /api/v1/vendedores/aplicar | Envía una solicitud para vender MercadoSolar | — |
| GET | /api/v1/client/proyectos/{id}/cotizaciones | Cotizaciones y preguntas de un proyecto | read:cotizaciones |
| POST | /api/v1/client/proyectos/{id}/ganador | Selecciona la cotización ganadora | write:licitacion |
| POST | /api/v1/client/proyectos/{id}/preguntas | Publica una pregunta sobre el proyecto | write:licitacion |
| POST | /api/v1/compare-summary | Resume y compara las cotizaciones recibidas | read:cotizaciones |
| POST | /api/v1/agreements | Genera el acuerdo de adjudicación | write:acuerdos |
| POST | /api/v1/agreements/{id}/sign | Firma el acuerdo de adjudicación | write:acuerdos |
| POST | /api/v1/agreements/{id}/reject | Rechaza el acuerdo de adjudicación | write:acuerdos |
| GET | /api/v1/agreements/{id}/pdf | Descarga el PDF del acuerdo | read:acuerdos |
| POST | /api/v1/projects/{id}/report-installation | El instalador reporta la instalación terminada | write:instalacion |
| POST | /api/v1/projects/{id}/reject-installation | El cliente rechaza la instalación reportada | write:instalacion |
Ejemplos
Leer la especificación y comprobar las cabeceras de límite:
curl -s https://mercadosolar.com.gt/openapi.json | head -20
curl -si -X POST https://mercadosolar.com.gt/api/eventos \
-H "content-type: application/json" \
-d '{"event":"page_view","session_id":"demo","path":"/"}' \
| grep -i "^ratelimit\|^http"Una llamada con sesión, desde un navegador ya autenticado en el portal:
fetch('/api/client/proyectos/<id-del-proyecto>/cotizaciones')
.then(r => r.json())
.then(({ quotations }) => console.log(quotations.length))Los errores siempre tienen la misma forma: {"error":"codigo_estable","detalle":"..."}. El código de error es lo que debes leer; el detalle es para humanos y puede cambiar.
Cómo seguir
Si vas a construir algo encima de MercadoSolar, escríbenos antes: te decimos si lo que necesitas ya existe y qué está por cambiar. Contacto.