Skip to main content

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"
}
ParameterTypeRequiredDescription
rival_refstringYesOpaque rival identifier
date_fromstringYesStart date in YYYY-MM-DD format
date_tostringYesEnd date in YYYY-MM-DD format
countrystringYesISO-2 (ES, AR, EU, etc.)

Optional Headers

HeaderDescription
Idempotency-KeyPrevents 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_to no puede estar en el futuro (debe ser ≤ ayer)
  • date_to debe ser ≥ date_from
  • Rango máximo de fechas: 60 días
  • date_from no 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 CodeError KeyDescription
400missing_parametersRequired fields are missing
400invalid_date_fromInvalid start date format
400invalid_date_toInvalid end date format
400invalid_date_rangeDate range exceeds limits
400invalid_countryInvalid country code
400rival_no_linkedin_idRival has no LinkedIn Ads ID
402insufficient_creditsNot enough credits
404rival_not_foundRival reference not found
429rate_limit_exceededToo 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"
}
ParameterTypeRequiredDescription
hashstringYesJob 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á DONE y api_jobs tiene json_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
CampoDescripción
kpis.countries_in_reportPaíses únicos que aparecen en la distribución por país de al menos un anuncio.
Referencia rápida — cada ítem de report.ads
CampoOrigen
total_impressions_estimateadStatistics.totalImpressions (from / to)
impressions_distribution_by_countryadStatistics.impressionsDistributionByCountry (ordenado por % desc.)
ad_targetingdetails.adTargeting
images, video_url, descriptionDerivados 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

KPIDescripción
ads_totalNúmero total de anuncios encontrados en el rango de fechas
formatsDesglose por formato de anuncio con sus respectivas cantidades
most_used_formatFormato de anuncio más utilizado
preferred_dayDía de la semana con mayor cantidad de lanzamientos de anuncios
day_distributionDistribución completa de lanzamientos de anuncios por día de la semana
earliest_adFecha de la primera impresión del anuncio más antiguo
latest_adFecha de la última impresión del anuncio más reciente
countries_in_reportPaíses únicos presentes en la distribución geográfica de los anuncios (country_code, country_name)

Ad Formats

Raw FormatLabel
SPONSORED_STATUS_UPDATESponsored Post
SPONSORED_VIDEOSponsored Video
SPONSORED_MESSAGESponsored Message
SPONSORED_CONTENTSponsored Content
SPONSORED_UPDATE_NATIVE_DOCUMENTSponsored Document
DYNAMIC_ADDynamic Ad
TEXT_ADText Ad
CAROUSEL_ADCarousel 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));
}
}