PromoPilot API
Una única API REST para todo el ecosistema de PromoPilot: lanza promoción de enlaces, rastrea posiciones, ejecuta auditorías SEO y escaneos de seguridad desde tu propio código: CRM, SaaS o un panel de agencia.
- URL base:
https://promopilot.link/api/v1
- El formato es JSON sobre HTTPS; cada respuesta contiene un campo ok.
- Los proyectos creados mediante la API aparecen en el panel en una sección aparte llamada “Proyectos de API” y llevan una insignia: no se mezclan con los personales.
- Especificación legible por máquina: openapi.json
curl https://promopilot.link/api/v1/ping
# {"ok":true,"pong":true,"time":"2026-07-20T12:00:00Z"}
Autorización y claves
Las claves de API se emiten en el panel del cliente: Panel → API para desarrolladores. Al crear una clave eliges a qué servicios puede acceder (scopes): promotion, rank, audit, shield. La clave se muestra completa solo una vez: no almacenamos nada salvo su huella SHA-256.
Authorization: Bearer ppk_your_key# или
X-Api-Key: ppk_your_key
Seguridad: guarda la clave en variables de entorno o en un almacén de secretos, y nunca la publiques en código del lado del cliente ni en repositorios. Revoca de inmediato en el panel una clave comprometida: la revocación surte efecto al instante. Usa claves distintas, con los scopes mínimos necesarios, para cada integración.
Facturación y saldo
- Todas las operaciones de pago se cargan al saldo personal de la cuenta, el mismo que ves en el panel. No existe un “saldo de API” aparte.
- Promoción: el precio proviene del plan, de la cascada flexible o del precio global, menos tu descuento personal; los suscriptores Pro obtienen automáticamente un 10% adicional de descuento.
- Puedes conocer el precio exacto antes de lanzar: POST /promotion/quote (sin cargo).
- Rank Tracker cobra por las comprobaciones realizadas realmente; SEO Audit completo se prepaga según la estimación de tamaño; Shield deep/comprehensive tiene un precio fijo por escaneo.
- Si no hay fondos suficientes recibes HTTP 402 insufficient_funds con los campos required, balance, shortfall y un enlace topup_url. La recarga se realiza en el panel.
Formato de errores
{
"ok": false,
"error": {
"code": "insufficient_funds",
"message": "No hay fondos suficientes en tu saldo. Recárgalo en el panel y repite la solicitud.",
"details": {"required": 44.1, "balance": 10.0, "shortfall": 34.1, "topup_url": "…"}
}
}
| Código | HTTP | Descripción |
unauthorized | 401 | La clave no se proporcionó, no se encontró o fue revocada |
forbidden_scope | 403 | La clave no tiene acceso a este servicio: emite una clave con el alcance necesario |
pro_required | 403 | La función requiere el plan Pro (por ejemplo, la marca blanca) |
forbidden | 403 | La acción está prohibida (por ejemplo, eliminar un proyecto personal mediante la API) |
not_found | 404 | El objeto no se encontró o pertenece a otra cuenta |
insufficient_funds | 402 | Fondos insuficientes: details contendrá required, balance, shortfall y topup_url |
validation_error | 422 | Parámetros no válidos; el campo se indica en details.field |
domain_mismatch | 422 | El enlace no apunta al dominio del proyecto |
already_running | 409 | El proceso ya está en ejecución (por ejemplo, una auditoría del proyecto) |
needs_verify | 409 | Shield: el dominio no está verificado |
need_estimate | 409 | Audit: primero ejecuta una auditoría exprés para estimar el tamaño |
rate_limited | 429 | Límite de solicitudes excedido; vuelve a intentarlo después de Retry-After segundos |
report_archived | 410 | El informe fue archivado por la política de retención; las cuentas Pro obtienen acceso automáticamente |
level1_disabled | 503 | La promoción está desactivada temporalmente por la plataforma |
api_disabled | 503 | La API pública está desactivada temporalmente |
server_error | 500 | Error interno: inténtalo de nuevo más tarde |
Límites
- 120 solicitudes por minuto por clave (el límite base).
- Lanzamientos de promoción: hasta 20 por minuto; creación de proyectos: hasta 30 por hora; lanzamientos de auditorías y escaneos: hasta 10 por minuto.
- Si superas el límite recibes HTTP 429 con un encabezado Retry-After. El intervalo recomendado para consultar el estado es una vez cada 30–60 segundos.
Localización
Los mensajes legibles (message) se devuelven en el idioma del parámetro ?lang= (ru, en, uk, pl) o del encabezado Accept-Language; por defecto, el idioma de tu cuenta. Los códigos de error y los estados para máquinas nunca se traducen: basa tu lógica en ellos.
Referencia de endpoints
Cuenta
GET
/me
Perfil: saldo, moneda, descuento personal, estado Pro, alcances de la clave.
Ejemplo de respuesta
{"ok":true,"user":{"id":2,"balance":124.50,"currency":"USD","promotion_discount_percent":10,"is_pro":true,...},"key":{"label":"CRM","scopes":["promotion"]}}
GET
/balance
El saldo actual y un enlace para recargar.
Ejemplo de respuesta
{"ok":true,"balance":124.50,"currency":"USD","topup_url":"…/client/balance.php"}
GET
/key
Información sobre la clave presentada: alcances y estadísticas de uso.
Ejemplo de respuesta
{"ok":true,"key":{"label":"CRM","scopes":["promotion","rank"],"requests_total":1520,...}}
Proyectos y enlaces scope: promotion
GET
/projects
Una lista de proyectos. De forma predeterminada solo los creados mediante la API; ?all=1 incluye también los personales.
| Parámetro | Entrada | Descripción |
all |
query |
“1”: devuelve tanto los proyectos personales como los de la API |
Ejemplo de respuesta
{"ok":true,"projects":[{"id":311,"name":"Client A","created_via":"api","links_count":3,"active_runs":1,...}]}
POST
/projects
Crea un proyecto. Se marca como created_via=api y aparece en el panel en «Proyectos API». Puedes pasar enlaces de inmediato: su dominio debe coincidir con el dominio del proyecto.
| Parámetro | Entrada | Descripción |
name * |
body |
Nombre del proyecto |
url * |
body |
Dirección del sitio (página principal) |
language |
body |
Idioma del contenido (ru/en/uk/pl/…), ru por defecto |
region |
body |
Región de la audiencia |
topic |
body |
Tema |
wishes |
body |
Notas para el texto |
links |
body |
Un array de objetos {url, anchor, language, wish} |
Ejemplo de respuesta
{"ok":true,"project":{"id":312,"created_via":"api",...},"links":{"added":2,"domain_errors":0}}
GET
/projects/{id}
El proyecto con sus enlaces y las últimas ejecuciones.
Ejemplo de respuesta
{"ok":true,"project":{"id":312,"links":[...],"runs":[...]}}
PATCH
/projects/{id}
Actualiza el nombre / la descripción / el idioma / los deseos / la región / la temática.
Ejemplo de respuesta
{"ok":true,"project":{...}}
DELETE
/projects/{id}
Elimina un proyecto. Solo se permite para proyectos creados mediante la API.
Ejemplo de respuesta
{"ok":true,"deleted":true}
POST
/projects/{id}/links
Añade un enlace para promocionar. El dominio del enlace debe coincidir con el dominio del proyecto (o ser un subdominio de este); de lo contrario, recibirás el error domain_mismatch.
| Parámetro | Entrada | Descripción |
url * |
body |
La URL completa de la página |
anchor |
body |
Anchor (déjalo vacío y elegiremos uno automáticamente) |
language |
body |
Idioma de la página |
wish |
body |
Notas del texto para este enlace en concreto |
Ejemplo de respuesta
{"ok":true,"link":{"id":915,"url":"https://site.com/page",...},"links_count":4}
DELETE
/projects/{id}/links/{linkId}
Elimina un enlace del proyecto.
Ejemplo de respuesta
{"ok":true,"deleted":true}
GET
/promotion/tariffs
Planes activos (para tariff_id), precios unitarios de la cascada flexible, el precio global y tu descuento personal.
Ejemplo de respuesta
{"ok":true,"tariffs":[{"id":3,"slug":"pro","name":"Pro","price_per_link":49.0,"cascade":{...}}],"your_discount_percent":10}
POST
/promotion/quote
Una estimación de precio para un lanzamiento sin realizar el cobro: los mismos parámetros que el propio lanzamiento.
| Parámetro | Entrada | Descripción |
tariff_id |
body |
ID del plan |
level1_count…crowd_per_article |
body |
Parámetros de la cascada flexible |
brand_anchors |
body |
true: incluir el recargo de anchors de marca |
Ejemplo de respuesta
{"ok":true,"pricing_mode":"tariff","base_price":49.0,"discount_percent":10,"brand_anchors_price":0,"total":44.1,"balance":124.5,"sufficient_funds":true}
GET
/promotion/brand
Nombre de la marca para la opción «Anchors de marca»: confirmado anteriormente o encontrado textualmente en el sitio web (el nombre del proyecto nunca se trata como marca). La primera llamada de un proyecto puede tardar unos segundos.
| Parámetro | Entrada | Descripción |
project_id * |
query |
ID del proyecto |
url |
query |
Página promocionada (por defecto, el primer enlace del proyecto) |
Ejemplo de respuesta
{"ok":true,"project_id":744,"brand":"Ventana V","source":"page","brand_anchors_price":5}
POST
/promotion/runs
Lanza la promoción de un enlace. El costo se cobra de tu saldo personal (los descuentos y el bono Pro se aplican automáticamente). Si lanzas el mismo enlace de nuevo mientras hay una cascada activa, se devuelve already_active.
| Parámetro | Entrada | Descripción |
project_id * |
body |
ID del proyecto |
url * |
body |
El enlace a promocionar (o pasa link_id) |
link_id |
body |
El ID del enlace del proyecto: una alternativa a url |
tariff_id |
body |
Lanzar con un plan |
level1_count |
body |
Cascada flexible: publicaciones L1 |
level2_per_level1 |
body |
L2 por cada L1 |
level3_per_level2 |
body |
L3 por cada L2 |
crowd_per_article |
body |
Enlaces crowd por artículo |
schedule_start_at |
body |
Inicio diferido (fecha y hora ISO) |
brand_anchors |
body |
true: anchors de marca (+$5 después del descuento): aproximadamente cada tercer artículo L1 y cada octavo artículo L2 incluye un enlace con el nombre de la marca a la página principal del sitio. Tus propios anchors se mantienen en todos los artículos. |
brand_name |
body |
Nombre de la marca para los anchors de marca. Si se omite, usamos el confirmado anteriormente o el encontrado en el sitio web; si no se encuentra ninguno, error 422 brand_name_required. |
Ejemplo de respuesta
{"ok":true,"run_id":501,"status":"queued","charged":49.1,"discount_percent":10,"balance_after":75.4,"currency":"USD","brand_anchors":true,"brand_name":"Ventana V"}
GET
/promotion/runs
Una lista paginada de ejecuciones. Filtros: project_id, status (active | queued | running | completed | failed | …), initiated_via (api | web).
| Parámetro | Entrada | Descripción |
page, per_page |
query |
Paginación (hasta 100 por página) |
Ejemplo de respuesta
{"ok":true,"runs":[{"id":501,"status":"level1_active","progress":{"done":3,"total":12,"pct":25},...}],"pagination":{...}}
GET
/promotion/runs/{id}
Estado de la ejecución: etapa, progreso y desglose por nivel (total / success / verified).
Ejemplo de respuesta
{"ok":true,"run":{"id":501,"status":"level2_active","levels":[{"level":1,"total":5,"verified":5},...]}}
GET
/promotion/runs/{id}/nodes
Todas las colocaciones de la cascada: red, URL de la publicación, estado de verificación. ?verified=1 devuelve solo los enlaces confirmados.
| Parámetro | Entrada | Descripción |
verified |
query |
«1»: solo colocaciones verificadas |
Ejemplo de respuesta
{"ok":true,"run_id":501,"nodes":[{"level":1,"network":"telegraph","url":"https://telegra.ph/…","verified":true,...}]}
Informes y marca blanca (Pro) scope: promotion
GET
/promotion/runs/{id}/report
Informe final (solo ubicaciones verificadas, como en el panel). ?format=pdf|xlsx|csv|csv_comma devuelve un enlace al archivo (PDF con gráficos, XLSX con las hojas “Resumen” y “Ubicaciones”, CSV para Excel o separado por comas para Google Sheets). En el plan Pro el informe lleva tu marca blanca (consulta /branding); los campos se pueden sobrescribir con los parámetros brand_*.
| Parámetro | Entrada | Descripción |
format |
query |
json (por defecto), pdf, xlsx, csv o csv_comma |
lang |
query |
Idioma del documento: ru, en, uk, pl (por defecto, el idioma de la solicitud) |
brand_company |
query |
Pro: nombre de la empresa en el encabezado |
brand_logo_url |
query |
Pro: URL del logotipo |
brand_accent_color |
query |
Pro: color de acento en HEX |
brand_footer_text |
query |
Pro: texto del pie de página |
Ejemplo de respuesta
{"ok":true,"run":{...},"summary":{"verified":12,...},"report":{"level1":[...],"level2":[...],"crowd":[...]},"whitelabel":{...}} — или {"ok":true,"pdf":{"url":"…/uploads/reports/api-cascade-501-….pdf"},"branded":true} — или {"ok":true,"file":{"url":"…/uploads/reports/acme-report-site.com-….xlsx","filename":"acme-report-site.com-2026-10-11.xlsx"},"format":"xlsx","branded":true}
GET
/branding
La marca blanca actual y el estado Pro.
Ejemplo de respuesta
{"ok":true,"is_pro":true,"branding_active":true,"branding":{"company_name":"Acme","logo_url":"…","accent_color":"#6366f1",...}}
PUT
/branding
Guarda la marca blanca (solo Pro). La misma marca se aplica en el panel (Configuración → Marca blanca).
| Parámetro | Entrada | Descripción |
company_name * |
body |
Nombre de la empresa (obligatorio) |
logo_url, website, email, phone, address, footer_text, accent_color |
body |
Otros campos de la marca. Los campos que no envíes conservan sus valores; una cadena vacía borra el campo. |
Ejemplo de respuesta
{"ok":true,"branding":{...}}
Rank Tracker scope: rank
GET
/rank/projects
Proyectos de monitoreo de posiciones.
Ejemplo de respuesta
{"ok":true,"projects":[{"id":7,"domain":"site.com","region":"UA","keywords_count":50,"top10":12,...}]}
POST
/rank/projects
Crea un proyecto: dominio, región (UA/US/PL/…), dispositivo (escritorio/móvil).
| Parámetro | Entrada | Descripción |
domain * |
body |
Dominio o URL del sitio |
name |
body |
Nombre (el dominio por defecto) |
region |
body |
Región de búsqueda, UA por defecto |
device |
body |
desktop | mobile |
Ejemplo de respuesta
{"ok":true,"project":{"id":8,...}}
GET
/rank/projects/{id}
El proyecto y todas las palabras clave con sus últimas posiciones.
Ejemplo de respuesta
{"ok":true,"project":{...},"keywords":[{"id":91,"keyword":"ventanas de compra","last_position":4,"last_checked_at":"…"}]}
POST
/rank/projects/{id}/keywords
Añade palabras clave: una cadena separada por saltos de línea o un arreglo.
| Parámetro | Entrada | Descripción |
keywords * |
body |
Una cadena “kw1\nkw2” o un arreglo de cadenas |
tag |
body |
Etiqueta del grupo |
target_url |
body |
Página de destino |
Ejemplo de respuesta
{"ok":true,"added":25,"skipped":0}
POST
/rank/projects/{id}/check
Pon en cola una verificación de posiciones (todas las palabras clave o keyword_ids[]). Se te cobran las verificaciones realmente realizadas según las tarifas internas del servicio. Obtén los resultados desde GET /rank/projects/{id}.
| Parámetro | Entrada | Descripción |
keyword_ids |
body |
Un arreglo de IDs de palabras clave (todas por defecto) |
Ejemplo de respuesta
{"ok":true,"queued":50,"note":"…"}
SEO Audit scope: audit
GET
/audit/projects
Proyectos de auditoría.
Ejemplo de respuesta
{"ok":true,"projects":[{"id":4,"domain":"site.com","start_url":"https://site.com/",...}]}
POST
/audit/projects
Crea un proyecto de auditoría.
| Parámetro | Entrada | Descripción |
url * |
body |
Dirección del sitio |
name |
body |
Nombre |
include_subdomains |
body |
true: rastrear subdominios |
Ejemplo de respuesta
{"ok":true,"project":{"id":5,...}}
GET
/audit/projects/{id}/estimate
La estimación del tamaño del sitio y el precio de una auditoría completa (para la estimación se necesita al menos una auditoría exprés).
Ejemplo de respuesta
{"ok":true,"has_estimate":true,"estimated_pages":430,"full_audit_price":5.0,"currency":"USD"}
POST
/audit/projects/{id}/crawls
Inicia una auditoría. La exprés es gratuita (límite diario); la completa es la auditoría integral, prepagada desde tu saldo según la estimación del tamaño.
| Parámetro | Entrada | Descripción |
mode |
body |
express (por defecto) | full |
Ejemplo de respuesta
{"ok":true,"crawl_id":88,"mode":"full","max_pages":500,"charged":5.0}
GET
/audit/crawls/{id}
Estado y resultados: Health Score, errores/advertencias y un resumen de problemas por categoría (al finalizar).
Ejemplo de respuesta
{"ok":true,"crawl":{"id":88,"status":"done","health_score":79,"pages_crawled":430,"issues":{...}}}
GET
/audit/projects/{id}/crawls
El historial de auditorías del proyecto.
Ejemplo de respuesta
{"ok":true,"crawls":[...]}
Security Shield scope: shield
GET
/shield/projects
Sitios protegidos.
Ejemplo de respuesta
{"ok":true,"projects":[{"id":3,"domain":"site.com","verified":true,...}]}
POST
/shield/projects
Crea o encuentra un proyecto por URL. La respuesta contiene un verify_token para la verificación del dominio.
| Parámetro | Entrada | Descripción |
url * |
body |
Dirección del sitio |
name |
body |
Nombre |
Ejemplo de respuesta
{"ok":true,"project":{"id":3,"verified":false,"verify_token":"pp-verify-…"},"verification_hint":"…"}
POST
/shield/projects/{id}/verify
Comprueba la verificación del dominio (un registro DNS TXT, un archivo .well-known o una etiqueta meta con verify_token). Obligatoria para los escaneos de pago.
Ejemplo de respuesta
{"ok":true,"verified":true,"method":"dns"}
POST
/shield/projects/{id}/scans
Inicia un escaneo: gratuito (básico, límite diario), profundo o completo; estos dos últimos se cobran de tu saldo.
| Parámetro | Entrada | Descripción |
mode |
body |
free | deep | comprehensive |
lang |
body |
Idioma del informe de IA (ru/uk/en/pl) |
Ejemplo de respuesta
{"ok":true,"scan_id":41,"mode":"deep","charged":19.0}
GET
/shield/scans/{id}
Estado, calificación (score/grade), el veredicto tipo semáforo, un resumen de hallazgos por gravedad y un enlace al informe público.
Ejemplo de respuesta
{"ok":true,"scan":{"id":41,"status":"done","score":86,"grade":"B","verdict":{...},"findings_summary":{"critical":0,"high":1,...},"share_report_url":"…"}}
Ejemplos de código
cURL — crear un proyecto y lanzar la promoción
curl -X POST https://promopilot.link/api/v1/projects \
-H "Authorization: Bearer ppk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Client Site",
"url": "https://client-site.com",
"language": "ru",
"links": [{"url": "https://client-site.com/services", "anchor": "servicios de la empresa"}]
}'
curl -X POST https://promopilot.link/api/v1/promotion/runs \
-H "Authorization: Bearer ppk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"project_id": 312, "url": "https://client-site.com/services"}'
PHP
<?php
$key = getenv('PROMOPILOT_API_KEY'); // nunca guardes la clave en tu código
function pp_api(string $method, string $path, array $data = null) {
global $key;
$ch = curl_init('https://promopilot.link/api/v1' . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $key,
'Content-Type: application/json',
'Accept-Language: ru',
],
CURLOPT_POSTFIELDS => $data !== null ? json_encode($data) : null,
]);
$res = json_decode(curl_exec($ch), true);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (($res['ok'] ?? false) !== true) {
throw new RuntimeException($res['error']['code'] ?? ('http_' . $code));
}
return $res;
}
$project = pp_api('POST', '/projects', ['name' => 'Client', 'url' => 'https://client-site.com']);
try {
$run = pp_api('POST', '/promotion/runs', [
'project_id' => $project['project']['id'],
'url' => 'https://client-site.com/services',
]);
echo "Iniciado, run_id={$run['run_id']}, cobrado {$run['charged']}";
} catch (RuntimeException $e) {
if ($e->getMessage() === 'insufficient_funds') {
// recarga el saldo y vuelve a intentarlo
}
}
Python
import os, requests
API = "https://promopilot.link/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PROMOPILOT_API_KEY']}"}
def api(method, path, **json_body):
r = requests.request(method, API + path, headers=HEADERS, json=json_body or None, timeout=30)
data = r.json()
if not data.get("ok"):
code = data.get("error", {}).get("code", f"http_{r.status_code}")
if code == "insufficient_funds":
details = data["error"]["details"]
raise RuntimeError(f"Recarga tu saldo: te faltan {details['shortfall']}")
raise RuntimeError(code)
return data
project = api("POST", "/projects", name="Client", url="https://client-site.com")
run = api("POST", "/promotion/runs", project_id=project["project"]["id"],
url="https://client-site.com/services")
print("run_id:", run["run_id"], "charged:", run["charged"])
# Consulta del estado (polling)
import time
while True:
st = api("GET", f"/promotion/runs/{run['run_id']}")["run"]
print(st["status"], st["progress"]["pct"], "%")
if st["status"] in ("completed", "failed", "cancelled"):
break
time.sleep(60)
report = api("GET", f"/promotion/runs/{run['run_id']}/report")
Node.js
const API = "https://promopilot.link/api/v1";
const KEY = process.env.PROMOPILOT_API_KEY;
async function api(method, path, body) {
const res = await fetch(API + path, {
method,
headers: {
"Authorization": `Bearer ${KEY}`,
"Content-Type": "application/json",
"Accept-Language": "ru",
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (!data.ok) {
const code = data.error?.code || `http_${res.status}`;
if (code === "insufficient_funds") {
console.error("Recarga tu saldo:", data.error.details.topup_url);
}
throw new Error(code);
}
return data;
}
const { project } = await api("POST", "/projects", { name: "Client", url: "https://client-site.com" });
const run = await api("POST", "/promotion/runs", { project_id: project.id, url: "https://client-site.com/services" });
console.log("run:", run.run_id, "charged:", run.charged);
OpenAPI
La especificación completa legible por máquina para generar clientes e importarla en Postman/Insomnia: https://promopilot.link/api/v1/openapi.json
Las preguntas y sugerencias sobre la API se gestionan a través del soporte en el panel o en Telegram. Compatibilidad hacia atrás: los cambios incompatibles se publican solo como una nueva versión (/api/v2), y los campos actuales nunca se eliminan sin un aviso en el registro de cambios.