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-222222222222

El 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 -3

La 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.

AlcanceQué permite
read:metadatosLeer los archivos legibles por máquina: especificación, llms.txt y metadatos de recurso protegido.
read:sandboxLeer el entorno de pruebas con datos sintéticos. No toca datos reales de nadie.
write:eventosRegistrar eventos de embudo anónimos.
write:resenasEnviar una reseña externa por el enlace de autoservicio del instalador.
read:cotizacionesLeer las cotizaciones y el hilo de preguntas de un proyecto propio.
write:licitacionSeleccionar al instalador ganador y publicar preguntas en la licitación.
read:acuerdosLeer y descargar el acuerdo de adjudicación.
write:acuerdosGenerar, firmar o rechazar un acuerdo de adjudicación.
write:instalacionReportar 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, y rel="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 ejemplo limit=60, remaining=59, reset=57.
  • RateLimit-Policy: la política aplicada, por ejemplo 60;w=60.
  • Retry-After: solo en un 429, 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.md

Funciona 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
404

Endpoints

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étodoRutaQué haceAlcance
GET/openapi.json públicoDescarga esta especificaciónread:metadatos
GET/llms.txt públicoGuía para agentesread:metadatos
GET/openapi.yaml públicoDescarga esta especificación en YAMLread:metadatos
GET/.well-known/oauth-protected-resource públicoMetadatos de recurso protegido (RFC 9728)read:metadatos
GET/api/v1/sandbox públicoÍndice del entorno de pruebasread:sandbox
GET/api/v1/sandbox/proyectos/{id}/cotizaciones públicoEspejo de las cotizaciones de un proyecto, con datos sintéticosread:sandbox
GET/api/v1/sandbox/acuerdos/{id} públicoEspejo de un acuerdo de adjudicación, con datos sintéticosread:sandbox
POST/api/v1/eventos públicoRegistra un evento de embudowrite:eventos
POST/api/v1/review/{slug} públicoEnvía una reseña de un proyecto anterior a MercadoSolarwrite:resenas
POST/api/v1/vendedores/aplicarEnvía una solicitud para vender MercadoSolar
GET/api/v1/client/proyectos/{id}/cotizacionesCotizaciones y preguntas de un proyectoread:cotizaciones
POST/api/v1/client/proyectos/{id}/ganadorSelecciona la cotización ganadorawrite:licitacion
POST/api/v1/client/proyectos/{id}/preguntasPublica una pregunta sobre el proyectowrite:licitacion
POST/api/v1/compare-summaryResume y compara las cotizaciones recibidasread:cotizaciones
POST/api/v1/agreementsGenera el acuerdo de adjudicaciónwrite:acuerdos
POST/api/v1/agreements/{id}/signFirma el acuerdo de adjudicaciónwrite:acuerdos
POST/api/v1/agreements/{id}/rejectRechaza el acuerdo de adjudicaciónwrite:acuerdos
GET/api/v1/agreements/{id}/pdfDescarga el PDF del acuerdoread:acuerdos
POST/api/v1/projects/{id}/report-installationEl instalador reporta la instalación terminadawrite:instalacion
POST/api/v1/projects/{id}/reject-installationEl cliente rechaza la instalación reportadawrite: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.