Saltar al contenido principal
Volver al índice para desarrolladores

StudioMeyer API: leer datos públicos de la web

La URL base es https://studiomeyer.io/api/v1. Las peticiones GET de abajo no necesitan cuenta ni clave API. Leen servicios publicados y webs de demostración ficticias; no envían datos de contacto ni inician compras.

Inicio rápido: curl, JavaScript y Python

GET /api/v1/services?lang=es devuelve el catálogo de servicios en español. Usa lang=de o lang=en para alemán o inglés; si omites lang, el idioma predeterminado es inglés. GET /api/v1/portfolio lee el portfolio de demostración.

El ejemplo de JavaScript usa fetch y se ejecuta en un archivo .mjs con Node.js. El de Python utiliza solo urllib y json de la biblioteca estándar. Son ejemplos HTTP, no SDK publicados por StudioMeyer.

Python envía una identificación explícita de la aplicación, User-Agent: StudioMeyer-API-Example/1.0, en lugar de la predeterminada de la biblioteca.

curl: leer servicios y una demo
curl --fail-with-body \
  -H "Accept: application/json" \
  "https://studiomeyer.io/api/v1/services?lang=es"

curl --fail-with-body \
  -H "Accept: application/json" \
  "https://studiomeyer.io/api/v1/portfolio?limit=1"

JavaScript con fetch
const response = await fetch(
  "https://studiomeyer.io/api/v1/services?lang=es",
  { headers: { Accept: "application/json" } }
);
const data = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${data.message}`);
console.log(data.language, data.webdesign.packages);

Python con urllib
import json
from urllib.error import HTTPError
from urllib.request import Request, urlopen

request = Request(
    "https://studiomeyer.io/api/v1/services?lang=es",
    headers={
        "Accept": "application/json",
        "User-Agent": "StudioMeyer-API-Example/1.0",
    },
)
try:
    with urlopen(request, timeout=20) as response:
        data = json.load(response)
    print(data["language"], data["webdesign"]["packages"])
except HTTPError as error:
    problem = {}
    if "json" in error.headers.get("Content-Type", "").lower():
        try:
            problem = json.loads(error.read().decode("utf-8", errors="replace"))
        except json.JSONDecodeError:
            pass
    if not isinstance(problem, dict):
        problem = {}
    message = (problem.get("message") or problem.get("detail")
               or problem.get("title") or problem.get("error") or error.reason)
    raise RuntimeError(f"HTTP {error.code}: {message}") from error

Procesar respuestas como datos

La respuesta de servicios es un objeto JSON con agency, website, language y las áreas de servicio. webdesign contiene packages, payment, scope y care. Cada paquete incluye id, name, price, priceType y delivery. Lee los precios y condiciones de esta respuesta en lugar de mantener otra lista en el cliente.

El JSON de abajo es un fragmento de la respuesta de servicios. OpenAPI contiene el esquema completo. La respuesta del portfolio incluye projects, total, demos, filters y next_cursor. Cada entrada del portfolio tiene type: "demo"; estos ejemplos no son referencias de clientes.

Fragmento: GET /api/v1/services?lang=es
{
  "agency": "StudioMeyer",
  "language": "es"
}

Filtrar y paginar el portfolio de demostración

limit es un entero de 1 a 100 y su valor predeterminado es 20. Omite cursor en la primera petición. Después pasa next_cursor sin modificar como cursor de la página siguiente, hasta que next_cursor sea null. Mantén los mismos filtros y tamaño de página al paginar.

industry admite immobilien, gastronomie, hotellerie o alle; style admite premium, modern o alle. Usa los valores documentados: los valores desconocidos no filtran actualmente los resultados. Un cursor inválido o un limit fuera del rango permitido devuelve HTTP 400.

Leer todas las páginas de demos
const url = new URL("https://studiomeyer.io/api/v1/portfolio");
url.searchParams.set("limit", "2");
let cursor = null;

do {
  if (cursor === null) url.searchParams.delete("cursor");
  else url.searchParams.set("cursor", cursor);
  const response = await fetch(url);
  const page = await response.json();
  if (!response.ok) throw new Error(`${response.status}: ${page.message}`);
  console.log(page.projects);
  cursor = page.next_cursor;
} while (cursor !== null);

Comprobar el estado HTTP y los errores estructurados

Comprueba primero el estado HTTP. Los errores contienen success: false, code, message, resolution y el campo error existente. Los campos adicionales dependen del endpoint. Los mensajes de error están actualmente en inglés, también al leer servicios en español.

GET /api/v1/portfolio?limit=0 devuelve HTTP 400 con la respuesta siguiente. Un método HTTP no admitido devuelve 405; la cabecera Allow indica los métodos permitidos. Corrige la petición según el contrato y resolution, en vez de enviarla una y otra vez sin cambios.

HTTP 400: tamaño de página inválido
{
  "success": false,
  "code": "VALIDATION_ERROR",
  "message": "limit must be an integer between 1 and 100",
  "error": "Invalid pagination limit",
  "resolution": "Check the request parameters and JSON body against the API documentation."
}

Límites de peticiones y reintentos

La API pública de la web permite 60 peticiones por minuto y dirección IP. RateLimit, RateLimit-Policy y las cabeceras X-RateLimit indican la cuota actual. Ante HTTP 429, espera al menos los segundos indicados en Retry-After. Una respuesta 503 también puede incluir Retry-After si la comprobación del límite no está disponible temporalmente.

Distribuye las peticiones en el tiempo y evita los bucles de reintento inmediato. Los límites anónimos de la web son independientes de las cuotas diarias de una cuenta de Memory.

Las solicitudes de contacto necesitan aprobación

POST /api/v1/consultation envía una solicitud de contacto no vinculante. Una persona acuerda después la cita; la API no la reserva. Envía la petición solo si el usuario pide el contacto y aprueba compartir sus datos. Esta guía no realiza ninguna solicitud de contacto.

Para una solicitud aprobada, el endpoint admite una cabecera Idempotency-Key opcional. Usa un UUID aleatorio para una única solicitud y reutiliza la misma clave y el mismo cuerpo JSON al reintentar. Un cuerpo modificado devuelve 409 IDEMPOTENCY_CONFLICT. Ante 409 IDEMPOTENCY_IN_PROGRESS, respeta Retry-After y conserva la clave y el cuerpo. Si sigue incompleta, consulta con soporte; no cambies automáticamente a otra clave.

Esta regla de idempotencia corresponde al endpoint de solicitudes documentado, no a todas las API. Los flujos de cuenta y pago tienen contratos y límites de autorización separados.

Contratos y más documentación

OpenAPI describe parámetros, esquemas JSON, códigos de estado y cabeceras. La documentación interactiva de Swagger utiliza el mismo contrato público. Antes de una petición de escritura, comprueba sus efectos y la aprobación necesaria del usuario.

La API de Memory es un servicio independiente con su propio contrato OpenAPI. La organización de GitHub enlaza los repositorios disponibles; esto no implica que todos los componentes de StudioMeyer sean públicos.