API REST de Rankscale
Rankscale es una herramienta de seguimiento de visibilidad de marca impulsada por IA. Solicita a varios modelos de IA (ChatGPT, Gemini, Claude, Perplexity, etc.) los términos de búsqueda rastreados por el usuario, analiza las respuestas e informa con qué frecuencia, dónde y con qué favorabilidad se mencionan las marcas rastreadas.
Esta habilidad llama a la API REST directamente para que el usuario (un profesional de SEO, no un desarrollador) pueda extraer datos en informes, gráficos y análisis personalizados sin tener que navegar por el panel de control.
Configuración
- Clave API: Se lee de la variable de entorno
RANKSCALE_API_KEY. El formato de la clave esrk_<hash>_<idEspacioTrabajo>— el sufijo es un identificador de espacio de trabajo, no un ID de marca, por lo que una clave alcanza cada marca en su espacio de trabajo (peculiaridades §15). Si no está configurada, pide al usuario que la configure una vez: PowerShellsetx RANKSCALE_API_KEY "rk_..."(se requiere un nuevo shell para que surta efecto) o pásala por sesión con$env:RANKSCALE_API_KEY = "rk_...". Nunca imprimas la clave en las respuestas ni la escribas en archivos. - URL base:
https://rankscale.ai(nota: sin prefijo/api— la ruta es/v1/...directamente en el dominio de marketing). - Encabezado de autenticación:
Authorization: Bearer $RANKSCALE_API_KEY. - Límite de velocidad: 200 solicitudes por minuto por clave API. La respuesta lleva
X-RateLimit-Limit,X-RateLimit-RemainingyX-RateLimit-Reset(segundos hasta que se reinicie la ventana) — léelos concurl -isi estás haciendo trabajo por lotes. Almacena en caché las respuestas de informes durante 5–10 minutos cuando sea posible. - Herramientas disponibles:
curlpara solicitudes. Para el análisis JSON, usa lo que ofrezca el entorno —node -e "...",jq, opython -m json.tool. Verifica qué hay en PATH antes de asumir; no dependas estrictamente de ninguno de ellos. - Sintaxis del shell: los ejemplos a continuación usan expansión de variables estilo bash (
$RANKSCALE_API_KEY). En Windows PowerShell eso se convierte en$env:RANKSCALE_API_KEY. Elige el shell que llames — ambos funcionan, pero ten en cuenta la sintaxis.
Patrón de llamada
Cada endpoint devuelve {success: true, data: {...}} en caso de éxito, o {success: false, error: {code, message, details}} en caso de error. Siempre verifica .success antes de leer .data.
Verifica también warnings. Una respuesta exitosa puede llevar una tercera clave de nivel superior, warnings[], que enumera los campos de solicitud que la API no reconoció e ignoró — a menudo con un "quiso decir" ("Ignored unrecognized field 'startDate'. Did you mean 'isoStartDate'? …"). Está ausente cuando la solicitud fue limpia, por lo que if (json.warnings) es una protección confiable. Una advertencia significa que los números responden a una pregunta diferente a la que se hizo — la API recurre a valores predeterminados para el campo eliminado. Preséntalo al usuario en lugar de tragártelo; esta es la forma más económica de detectar la trampa de camelCase (peculiaridades §1).
Guarda las respuestas JSON sin procesar en un archivo junto al trabajo del usuario (normalmente Rankscale/<endpoint>_<contexto>.json) para que puedan reanalizarse sin volver a llamar a la API. Muestra al usuario un resumen limpio, no el JSON sin procesar, a menos que lo soliciten.
Eficiencia de tokens: la palanca más grande. Para cualquier cosa masiva (multitema, multi-motor, historial completo, o /search-terms-report con includeAnswerTexts), escribe la respuesta directamente en el disco con -o y extrae solo los valores que necesites con node -e. No leas cargas útiles completas de /report en el contexto — un informe filtrado ocupa ~40–50 KB y un barrido de 7 temas es ~300 KB. Extraer escalares/series del lado del cliente los mantiene fuera del contexto y, en extracciones masivas, reduce aproximadamente a la mitad el uso de tokens en comparación con leer las cargas útiles en línea. (Para una métrica única y estrecha, la sobrecarga no vale la pena — simplemente lee la respuesta pequeña.)
Los tamaños de las cargas útiles son extremadamente desiguales: planifica en torno a ellos. Medido en una sola marca:
| Endpoint | Ventana | Tamaño |
|---|---|---|
/report (filtrado) | 30d | ~40–50 KB |
/report (filtrado) | 1 mes calendario | ~105–110 KB |
/citations | 3m | ~3.9 MB |
/citations uncapped: true | 1 mes calendario | ~4.4–5.7 MB |
/sentiment | 30d | ~9 MB |
/sentiment | 3m | ~17 MB |
El tamaño no escala con la longitud de la ventana — uncapped: true importa mucho más. Una sola solicitud sin límite de un mes resultó ser más grande que una extracción de tres meses con límite, así que no extrapoles el tamaño de 1 mes a partir de la fila de 3m.
/sentiment y /citations nunca son seguros para leer en línea — siempre usa -o para guardar en un archivo y extraer. En llamadas tan pesadas, captura también el estado HTTP (curl -w '%{http_code}'), porque un 502 transitorio llega como un cuerpo HTML que parece una ruta rota pero solo necesita un reintento (peculiaridades §6).
# Bash (Git Bash / WSL / macOS / Linux)
curl -s -H "Authorization: Bearer $RANKSCALE_API_KEY" \
"https://rankscale.ai/v1/metrics/brands" \
-o brands.json
curl -s -X POST -H "Authorization: Bearer $RANKSCALE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"brandId":"<id>"}' \
"https://rankscale.ai/v1/metrics/report" \
-o report.json
# Equivalente en PowerShell
curl.exe -s -H "Authorization: Bearer $env:RANKSCALE_API_KEY" `
"https://rankscale.ai/v1/metrics/brands" `
-o brands.json
curl.exe -s -X POST -H "Authorization: Bearer $env:RANKSCALE_API_KEY" `
-H "Content-Type: application/json" `
-d '{\"brandId\":\"<id>\"}' `
"https://rankscale.ai/v1/metrics/report" `
-o report.json
Nota en PowerShell: usa curl.exe (el alias curl apunta a Invoke-WebRequest, que se comporta de manera diferente), escapa las comillas internas en los cuerpos JSON -d con \", y usa el acento grave ` para la continuación de línea. Invoke-RestMethod también funciona y analiza JSON automáticamente, pero la forma curl mantiene las respuestas consistentes en todos los shells.
El inventario completo de endpoints y las formas de las solicitudes se encuentran en references/endpoints.md. Léelo antes de cualquier llamada de la que no estés seguro. Las sorpresas y advertencias están en references/quirks.md — léelo antes de informar métricas al usuario, porque varias de ellas afectan cómo deben interpretarse los números.
Fuera del alcance: la API de enlaces compartidos. Rankscale publica una segunda API REST para enlaces compartidos públicos de paneles. Utiliza una clave diferente (Configuración → Compartir, no Configuración → Integraciones), por lo que RANKSCALE_API_KEY no se autenticará contra ella. Si el usuario solicita crear o revocar un enlace compartido, dile que es una API separada que esta habilidad no cubre — no adivines sus rutas.
Recetas de flujo de trabajo
1. Identificar la marca
La mayoría de los endpoints de informes necesitan un brandId. Cuando el usuario nombre una marca ("muéstrame las métricas de Acme"), resuelve el ID primero:
- Si
Rankscale/brands.jsonexiste en el directorio de trabajo del usuario, léelo. - De lo contrario, llama a
GET /v1/metrics/brandsy guarda el resultado. - Compara el nombre de la marca del usuario con
brands[].name(sin distinción de mayúsculas y minúsculas) y conbrands[].brandInfo.names(alias). Si hay múltiples coincidencias, pide al usuario que las desambigüe.
2. Extraer métricas para una marca durante un período
El usuario casi siempre solicita un período de tiempo. Usa timeFrame (camelCase, F mayúscula — la API ignora silenciosamente cualquier otra ortografía) con uno de estos valores preestablecidos documentados: 24h, 7d, 30d, 3m, 1y. Combínalo con aggregation (hourly, daily, weekly o monthly) para el tamaño del bucket. Para ventanas personalizadas, usa isoStartDate + isoEndDate (cadenas de fecha ISO emparejadas); estas anulan timeFrame.
{
"brandId": "<id>",
"timeFrame": "30d",
"aggregation": "daily",
"includeNotFoundExecutions": true
}
Filtros de informes opcionales (todos aceptan una sola cadena, un array para OR dentro del campo, o "all" para ningún filtro; los filtros se combinan con AND entre campos):
selectedTopic— ID de tema, nombre de tema,_orphanedo arrayselectedTags— etiqueta,__UNTAGGED__o arrayselectedEngine— ID de motor (preferido — consultareferences/endpoints.mdpara el catálogo) o nombre descriptivoselectedQuery— cadena de consulta de término de búsqueda exacta o arraysearchTermId— ID de término de búsqueda individualperiodOffset— entero, desplaza la ventana preestablecida hacia atrás N períodos (0 = actual, 1 = anterior, …)userTimezone— zona horaria IANA comoEurope/Berlinpara los límites diarios locales del usuarioincludeNotFoundExecutions— booleano, controla si las ejecuciones de "marca no encontrada en la respuesta" cuentan para las métricas (consulta quirks.md)
Lee la respuesta de data.ownBrandMetrics.historicalData.<aggregation> (por ejemplo, .daily cuando solicitaste aggregation: "daily"). Cada array de bucket — visibilityScore, sentiment, mentions, sources, citations, citationCounts, avgPosition, detectionRate, top3, brandNotFound — se ejecuta en paralelo con timestamps.
Tres series de tiempo paralelas se sientan junto a él:
data.ownBrandMetrics.topicMetricsData.<aggregation>— por temadata.ownBrandMetrics.engineMetricsData.<aggregation>— por motor; no documentado upstream, y generalmente el corte de mayor valor cuando una ventana agregada parece plana, porque los movimientos a nivel de motor se cancelan entre sí en el total (peculiaridades §25)data.competitorTimeSeriesData.<aggregation>— por competidor, propia marca excluida (peculiaridades §16)
brandNotFound[] es una no señal — se lee true para esencialmente cualquier marca con detección inferior al 100%. Usa detectionRate en su lugar y nunca informes "marca no encontrada" basándote en la bandera (peculiaridades §2).
Presenta una tabla de métricas con estas columnas: Fecha, Visibilidad, Sentimiento, Menciones, Citas, Pos. Promedio, % de Detección, % Top-3. Encima, muestra el resumen instantáneo (valores instantáneos actuales de ownBrandMetrics + el delta de trends).
Rango de marca (por Visibilidad) — vs. competidores. Calcula esto a partir de data.competitorMetrics[] (que incluye la propia marca, marcada isOwnBrand: true): rango = 1 + (recuento de entradas con visibilityScore estrictamente mayor) — los empates no bajan la marca. Esto reproduce el rango del panel. La respuesta también lleva latestRank/avgRank por entrada — estos no son rangos. Verificado el 2026-08-07: devuelven valores no enteros (latestRank: 2.7, avgRank: 2.9) para una marca cuyo rango calculado era 1, por lo que rastrean algo similar a una posición, no un ordinal. Ignóralos. El equivalente own_brand_rank de MCP es igualmente siempre 1 (un pin de visualización). El cálculo ordenado por visibilidad anterior es el método que coincidió con el panel en las pruebas. Los filtros compartidos (selectedEngine, selectedTopic, selectedTags, selectedQuery) funcionan por corte, por lo que puedes clasificar dentro de un solo motor, tema, etiqueta o consulta volviendo a ejecutar filtrado. Ten en cuenta que las entradas de competitorMetrics pueden tener un validMetricsCount diferente al de la propia marca (las líneas base de los competidores usan una ventana más amplia) — el orden aún coincide con la UI. Receta completa — incluido el rango diario a lo largo del tiempo y sus advertencias — en la receta §6 a continuación.
3. Análisis de citas
/v1/metrics/citations devuelve qué fuentes (sitios, artículos) citan los modelos de IA al responder a los términos de búsqueda del usuario. Útil para comprender qué dominios de terceros influyen en las respuestas de IA sobre una marca.
Campos clave: data.totalCitations, data.uniqueCitations (URL distintas), data.uniqueDomains y data.citationsByDomain[] (cada dominio). En cada entrada de dominio, occurrences es el recuento; el campo citations es el array de URL, no un número — por lo tanto, la participación en citas = occurrences / totalCitations, y las URL principales provienen de aplanar el array de URL de cada dominio y ordenar por occurrences.
Cada objeto URL es {url, normalizedUrl, occurrences, engineAppearances: {<idMotor>: count}, firstSeenAt, lastSeenAt, category, searchTerms[]}. engineAppearances es el único desglose de citas por motor en la API — súmalo en todas las URL de un dominio para responder "qué motores citan nuestro sitio". firstSeenAt/lastSeenAt son el único eco de la ventana que realmente obtuviste. Normaliza normalizedUrl aún más antes del análisis a nivel de página (deja variantes de www./barra diagonal final/consulta distintas), y ten en cuenta que category: "owned" significa "el propio sitio de una empresa", no propiedad de la marca rastreada — los competidores también lo llevan. Consulta quirks.md §13b. data.domainSummary tiene vistas precortadas (topDomainsOverall, topDomainsByEngine, topDomainsByQuery, topDomainsByOwnBrandCitations, topDomainsByCompetitor) — pero estos están limitados (≈20/10 entradas) y topDomainsByCompetitor enumera dominios donde se mencionó a un competidor (incluye sitios neutrales de terceros como Reddit/Booking), así que no lo uses para identificar sitios propiedad del competidor. Consulta quirks.md §13b.
Verifica los límites antes de considerar una lista completa. La respuesta tiene un límite de 5,000 URL únicas y 50 marcas de forma predeterminada. data.paginationInfo informa la verdad — citationsCapped, brandsCapped, responseTrimmed, capBypassed, maxCitations, maxBrands, totalCount vs returnedCount. Pasa uncapped: true (cuerpo o parámetro de consulta) para levantar el límite de URL; no levanta el límite de 50 marcas ni el protector de tamaño de respuesta. deduplicated: true deduplica las rutas de respuesta y agrega data.searchTermsById. Si alguna bandera de límite es true, informa al usuario y reduce la ventana o filtra en lugar de presentar una lista truncada como la imagen completa. Consulta quirks.md §13c.
4. Sentimiento
/v1/metrics/sentiment devuelve agregados de sentimiento a nivel de ventana más nubes de palabras clave para la marca rastreada y cada competidor — no una serie temporal (usa el historicalData.<agg>.sentiment[] de /report para el sentimiento a lo largo del tiempo). Mismos filtros compartidos, más deduplicated: true.
Dos arrays con diferentes alcances: data.brandSentiments[] es solo la marca rastreada; data.companySentiments[] es cada marca detectada, incluida la propia (isOwnBrand: true) — usa este para comparaciones de competidores. brandSentimentsBySearchTerm[] / companySentimentsBySearchTerm[] dan los mismos cortes por término de búsqueda.
Por entrada: avgSentiment (escala 0–100, = totalSentimentScore / sentimentCount), sentimentCount, executionCount, y positiveCount / neutralCount / negativeCount. El sentimiento se divide por fuente — bloques webGrounding* (motores con base web) vs trainingData*, controlado por los booleanos hasWebGrounding / hasTrainingData, con webGroundingSentimentByEngine[<idMotor>] = {sum, count, avg} para puntuaciones por motor.
Dos trampas — lee quirks.md §19 antes de informar algo de esto. (1) `p … [truncado para la traducción]