Rivalwin API — LinkedIn Ads Commercial
Descripción general
La API de LinkedIn Ads Commercial permite generar informes de publicidad comercial en LinkedIn de forma programática. Los informes analizan los anuncios de un competidor durante un período determinado, proporcionando datos de creatividades, formatos, impresiones estimadas, segmentación geográfica y análisis de IA.
Base URL: https://rivalwin.com/api/v1/linkedin/companies/
Autenticación
Todas las peticiones requieren una API Key válida enviada en el header X-API-Key.
X-API-Key: tu_api_key_aqui
Rate Limiting
La API aplica dos tipos de limitación:
Rate limit por hora
Cada API Key tiene un límite de peticiones por hora configurado. Los headers de respuesta indican el estado:
X-RateLimit-Limit-Hour: 600
X-RateLimit-Remaining-Hour: 598
Si se excede el límite: 429 Too Many Requests con header Retry-After.
Cooldown entre informes
Existe un tiempo mínimo de espera entre la creación de informes consecutivos (por defecto 10 segundos, configurable por plan/empresa). Si intentas crear un informe antes de que transcurra este tiempo:
{
"error": "cooldown_active",
"message": "Please wait 7 seconds before creating another report.",
"cooldown_seconds": 10,
"retry_after": 7
}
Requisitos de la cuenta
- Su cuenta de Rivalwin debe estar activa.
- El plan contratado debe estar activo y no vencido (contrato o trial).
- El plan debe incluir acceso a la API.
Si alguna de estas condiciones no se cumple, recibirás un error 403 Forbidden con el motivo específico.
Recepción de resultados
Existen dos formas de recibir los datos de un informe completado:
Polling
Consulta el endpoint report_status periódicamente (cada 15-30 segundos) hasta que el estado sea DONE. Es el método más simple y no requiere configuración adicional.
Webhook
Configura una URL de webhook en el módulo Integraciones & API Keys del sistema. Cuando un informe pase al estado DONE, la API enviará automáticamente un POST a la URL configurada con el payload completo del informe (mismo contenido que devuelve report_status en estado DONE).
De esta forma no necesitas hacer polling: simplemente esperas a recibir la notificación en tu servidor.
Endpoints
1. Crear un nuevo informe de LinkedIn Ads (new_report)
Creates a new LinkedIn Ads commercial report asynchronously. Returns a hash identifier immediately.
Request
{
"rival_ref": "rvl_0c526d93",
"date_from": "2026-03-01",
"date_to": "2026-03-31",
"country": "ES"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
| rival_ref | string | Yes | Opaque rival identifier |
| date_from | string | Yes | Start date in YYYY-MM-DD format |
| date_to | string | Yes | End date in YYYY-MM-DD format |
| country | string | Yes | ISO-2 (ES, AR, EU, etc.) |
Optional Headers
| Header | Description |
|---|---|
| Idempotency-Key | Prevents duplicate report creation for identical requests |
Ejemplo de petición
curl -X POST https://rivalwin.com/api/v1/linkedin/companies/new_report \
-H "X-API-Key: tu_api_key" \
-H "Content-Type: application/json" \
-d '{
"rival_ref": "rvl_0c526d93",
"date_from": "2026-03-01",
"date_to": "2026-03-31",
"country": "ES"
}'
Validaciones
date_tono puede estar en el futuro (debe ser ≤ ayer)date_todebe ser ≥date_from- Rango máximo de fechas: 60 días
date_fromno puede ser anterior a 1 año- Debes tener créditos suficientes
Respuesta (HTTP 202)
{
"hash": "a1b2c3d4e5f6789012345678abcdef01",
"status": "PENDING",
"created_at": "2026-03-15 10:30:00"
}
Error Responses
| HTTP Code | Error Key | Description |
|---|---|---|
| 400 | missing_parameters | Required fields are missing |
| 400 | invalid_date_from | Invalid start date format |
| 400 | invalid_date_to | Invalid end date format |
| 400 | invalid_date_range | Date range exceeds limits |
| 400 | invalid_country | Invalid country code |
| 400 | rival_no_linkedin_id | Rival has no LinkedIn Ads ID |
| 402 | insufficient_credits | Not enough credits |
| 404 | rival_not_found | Rival reference not found |
| 429 | rate_limit_exceeded | Too many requests |
2. Consultar estado del informe (report_status)
Verificar el estado de un informe o recuperar el informe completo cuando esté finalizado.
Request
{
"hash": "a1b2c3d4e5f6789012345678abcdef01"
}
| Parameter | Type | Required | Description |
|---|---|---|---|
| hash | string | Yes | Job hash returned by new_report |
Ejemplo de petición
curl -X POST https://rivalwin.com/api/v1/linkedin/companies/report_status \
-H "X-API-Key: tu_api_key" \
-H "Content-Type: application/json" \
-d '{"hash": "a1b2c3d4e5f6789012345678abcdef01"}'
Respuesta - PENDING/PROCESSING
{
"hash": "a1b2c3d4...",
"status": "PENDING",
"created_at": "2026-03-15 10:30:00",
"message": "The report is queued and will start processing shortly."
}
Respuesta - DONE (HTTP 200)
El objeto report alinea los datos con el informe LinkedIn en la app: API oficial (estadísticas, impressionsDistributionByCountry, adTargeting, totalImpressions) y creatividades cuando el ID coincide.
Caché: Si el job ya está
DONEyapi_jobstienejson_response, la API puede devolver esa copia hasta que se genere un informe nuevo.
{
"hash": "a1b2c3d4...",
"status": "DONE",
"created_at": "2026-03-15 10:30:00",
"started_at": "2026-03-15 10:30:05",
"completed_at": "2026-03-15 10:32:15",
"report": {
"report_id": 4567,
"platform": "LinkedIn",
"rival": {
"ref": "rvl_0c526d93",
"name": "Empresa XYZ",
"logo": "https://...",
"web": "https://empresa-xyz.com",
"description": "Competidor directo en el sector tech",
"linkedin_id": "empresa-xyz"
},
"sector": "Tecnología",
"country": "es",
"campaign_start": "2026-03-01",
"campaign_end": "2026-03-31",
"inform_created": "2026-03-15 10:30:00",
"kpis": {
"ads_total": 25,
"formats": {
"SPONSORED_STATUS_UPDATE": 12,
"SPONSORED_VIDEO": 8,
"CAROUSEL_AD": 5
},
"most_used_format": "Sponsored Post",
"preferred_day": "Tuesday",
"day_distribution": {
"Tuesday": 7,
"Wednesday": 5,
"Monday": 4,
"Thursday": 4,
"Friday": 3,
"Saturday": 1,
"Sunday": 1
},
"earliest_ad": "2026-03-02",
"latest_ad": "2026-03-29",
"countries_in_report": [
{ "country_code": "ES", "country_name": "España" },
{ "country_code": "PT", "country_name": "Portugal" }
]
},
"insights": {
"gpt_analysis": "Análisis de la estrategia publicitaria..."
},
"ads": [
{
"linkedin_ad_library_id": "728824033",
"ad_url": "https://..../detail/728824033",
"advertiser_name": "Empresa XYZ",
"first_impression": "2026-03-02",
"last_impression": "2026-03-15",
"first_impression_ts": 1740873600000,
"last_impression_ts": 1741996800000,
"format": "Sponsored Post",
"format_raw": "SPONSORED_STATUS_UPDATE",
"creative": null,
"description": "Headline o texto si hubo match",
"images": ["https://cdn.../preview.jpg"],
"video_url": null,
"total_impressions_estimate": { "from": 10000, "to": 50000 },
"impressions_distribution_by_country": [
{
"country_urn": "urn:li:country:es",
"country_code": "ES",
"country_name": "España",
"impression_percentage": 62.5
},
{
"country_urn": "urn:li:country:pt",
"country_code": "PT",
"country_name": "Portugal",
"impression_percentage": 12.3
}
],
"ad_targeting": [
{
"facetName": "Language",
"includedSegments": ["Spanish"],
"excludedSegments": []
}
],
}
]
}
}
Referencia rápida — report
| Campo | Descripción |
|---|---|
kpis.countries_in_report | Países únicos que aparecen en la distribución por país de al menos un anuncio. |
Referencia rápida — cada ítem de report.ads
| Campo | Origen |
|---|---|
total_impressions_estimate | adStatistics.totalImpressions (from / to) |
impressions_distribution_by_country | adStatistics.impressionsDistributionByCountry (ordenado por % desc.) |
ad_targeting | details.adTargeting |
images, video_url, description | Derivados cuando hay match |
Response - ERROR
{
"hash": "a1b2c3d4...",
"status": "ERROR",
"created_at": "2026-03-15 10:30:00",
"completed_at": "2026-03-15 10:31:00",
"error_message": "Report failed during processing"
}
KPIs Explained
| KPI | Descripción |
|---|---|
| ads_total | Número total de anuncios encontrados en el rango de fechas |
| formats | Desglose por formato de anuncio con sus respectivas cantidades |
| most_used_format | Formato de anuncio más utilizado |
| preferred_day | Día de la semana con mayor cantidad de lanzamientos de anuncios |
| day_distribution | Distribución completa de lanzamientos de anuncios por día de la semana |
| earliest_ad | Fecha de la primera impresión del anuncio más antiguo |
| latest_ad | Fecha de la última impresión del anuncio más reciente |
| countries_in_report | Países únicos presentes en la distribución geográfica de los anuncios (country_code, country_name) |
Ad Formats
| Raw Format | Label |
|---|---|
| SPONSORED_STATUS_UPDATE | Sponsored Post |
| SPONSORED_VIDEO | Sponsored Video |
| SPONSORED_MESSAGE | Sponsored Message |
| SPONSORED_CONTENT | Sponsored Content |
| SPONSORED_UPDATE_NATIVE_DOCUMENT | Sponsored Document |
| DYNAMIC_AD | Dynamic Ad |
| TEXT_AD | Text Ad |
| CAROUSEL_AD | Carousel Ad |
Code Examples
PHP
<?php
$apiUrl = "https://rivalwin.com/api/v1/linkedin/companies/new_report";
$apiKey = "your_api_key";
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'rival_ref' => 'rvl_0c526d93',
'date_from' => '2026-03-01',
'date_to' => '2026-03-31',
'country' => 'ES',
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-API-Key: ' . $apiKey,
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
$hash = $response['hash'];
// Poll report_status with the hash...
Python
import requests, time
API_URL = "https://rivalwin.com/api/v1/linkedin/companies"
HEADERS = {"X-API-Key": "your_api_key", "Content-Type": "application/json"}
# Create report
r = requests.post(f"{API_URL}/new_report", json={
"rival_ref": "rvl_0c526d93",
"date_from": "2026-03-01",
"date_to": "2026-03-31",
"country": "ES"
}, headers=HEADERS)
job = r.json()
hash_id = job["hash"]
# Poll for completion
while True:
r = requests.post(f"{API_URL}/report_status", json={"hash": hash_id}, headers=HEADERS)
data = r.json()
if data["status"] == "DONE":
report = data["report"]
print(f"Total ads: {report['kpis']['ads_total']}")
for ad in report.get("ads", [])[:1]:
print("Geo:", ad.get("impressions_distribution_by_country"))
break
elif data["status"] == "ERROR":
print(f"Error: {data['error_message']}")
break
time.sleep(10)
Node.js
const API_URL = "https://rivalwin.com/api/v1/linkedin/companies";
const HEADERS = { "X-API-Key": "your_api_key", "Content-Type": "application/json" };
async function createLinkedInReport() {
const res = await fetch(`${API_URL}/new_report`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
rival_ref: "rvl_0c526d93",
date_from: "2026-03-01",
date_to: "2026-03-31",
country: "ES",
}),
});
const { hash } = await res.json();
// Poll
while (true) {
const poll = await fetch(`${API_URL}/report_status`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({ hash }),
});
const data = await poll.json();
if (data.status === "DONE") {
const r = data.report;
console.log(`Total ads: ${r.kpis.ads_total}`);
const ad = r.ads?.[0];
if (ad) {
console.log("Geo:", ad.impressions_distribution_by_country);
}
return r;
}
if (data.status === "ERROR") throw new Error(data.error_message);
await new Promise((r) => setTimeout(r, 10000));
}
}