Iniciar sesiónCrear servidor
API

API de revendedor de SolumeCore: guía, referencia y ejemplos

Compra, opera y renueva servidores VPS y VDS por API: quickstart con curl, referencia de endpoints, errores, límites, facturación, webhooks y OpenAPI.

56 min de lectura#reseller api#vps api#vds api#rest api#openapi#webhooks#api rate limits

Revende servidores VPS y VDS desde tus propios sistemas. Con la API de revendedor consultas el catálogo, cotizas y compras servidores, los operas (energía, reinstalación, contraseñas, red, llaves SSH, firewall), pagas renovaciones, lees facturas y recibes eventos, todo por HTTPS y JSON.

Esta es la guía completa: conceptos, guía rápida, una referencia de cada endpoint, errores, límites, reglas de facturación, webhooks, buenas prácticas y el registro de cambios. El contrato legible por máquinas es el archivo OpenAPI (producción) y el archivo OpenAPI del sandbox. Una guía más corta del entorno de pruebas gratuito está en la guía del sandbox.

Resumen

  • Para quién es. Revendedores de hosting, paneles, sistemas de facturación y automatizaciones que venden o administran servidores VPS y VDS. Los servidores de juegos, el hosting web compartido y las aplicaciones no se pueden revender con esta API: un plan de otro tipo se rechaza con product_not_resellable.
  • Usarla no cuesta nada. No hay cargo por la API, por las llamadas ni por las llaves. Pagas el precio normal de cada servicio que compras o renuevas, menos el descuento acordado para tu llave, más el recargo de tarjeta acordado para tu llave cuando se cobra una tarjeta. Ambos valen 0% por defecto y se acuerdan con cada revendedor; cada cotización y cada factura muestra cada línea.
  • URL base.
Entorno URL base Prefijo de la llave
Producción https://api.solumecore.com/v1 sk_live_
Sandbox https://api.solumecore.com/sandbox/v1 sk_test_
  • La misma API en ambos. El sandbox tiene las mismas rutas, cuerpos, errores y cabeceras sobre una cuenta virtual privada (consulta Sandbox). Pasar a producción es cambiar la URL base y la llave.
  • Legible por máquinas. Toda respuesta es JSON en UTF-8 con nombres de campo en inglés y snake_case. El dinero siempre va en centavos enteros.

Primeros pasos

  1. Crea una llave. En el panel del cliente entra en API y webhooks, abre Claves de API y pulsa Nueva clave. El secreto (sk_live_...) se muestra una sola vez; guárdalo en un gestor de secretos. Una llave nueva solo puede leer y administrar servicios hasta que el personal conceda más permisos. Puedes tener hasta 10 llaves reales activas; revoca una para crear otra.

  2. Pide el acceso que necesitas. Abre un ticket desde el panel (el botón Pedir que autoricen IPs lo prepara) e indica al equipo:

    • las direcciones IP públicas desde las que llamará tu código. Una llave solo funciona desde las direcciones IP que el personal registró para ella; cualquier otra dirección recibe 403 ip_not_allowed;
    • si quieres comprar y renovar por API (permiso purchase) y con qué topes de gasto: máximo por orden, máximo por 24 horas móviles y órdenes por hora móvil;
    • si quieres administrar el saldo y la renovación automática por API (permiso billing);
    • el descuento y el recargo de tarjeta que has acordado.

    Solo el personal puede conceder permisos, topes e IPs. Puedes ver lo que se concedió a tu llave en el panel (Qué pueden hacer tus llaves) y en GET /v1/me.

  3. Configura el pago en el sitio web. La API cobra primero tu saldo y después tu tarjeta guardada predeterminada, fuera de sesión. Guarda la tarjeta y acepta el mandato de cobro automático vigente (versión 2026-11-v2) en Facturación, dentro del panel; no se puede hacer por la API. Recarga el saldo en el panel o con POST /v1/balance/top-ups. La lista completa está en Requisitos antes de comprar por API.

  4. Prueba todo primero en el sandbox. Crea una llave de pruebas (sk_test_...) en la misma página; no necesita aprobación ni IPs. Consulta Sandbox.

  5. Haz tu primera llamada.

bash
export SOLUME_KEY=sk_live_xxxxxxxx
curl https://api.solumecore.com/v1/me -H "Authorization: Bearer $SOLUME_KEY"

Permisos (scopes)

Cada llave tiene un conjunto de permisos concedidos por el personal.

Scope Permite
read Catálogo, cotizaciones, servicios, tareas, métricas, facturas, órdenes, eventos
manage Energía, reinstalación, restablecer contraseña, leer las credenciales de un solo uso, hostname, rDNS, llaves SSH, firewall, cancelar y retirar la cancelación
purchase Todo lo que gasta dinero: crear órdenes, renovar, pagar facturas
billing Saldo, recargas, información del método de pago, modo de renovación automática

Una llamada sin el permiso necesario responde 403 insufficient_scope con details.required_scope, antes de que ocurra cualquier otra cosa. Crear una orden con auto_renew distinto de off también necesita billing.

Topes de gasto

purchase solo se concede junto con topes. Están en centavos de la moneda de la cuenta y se comprueban antes de cobrar (el importe se reserva y se libera si el pago falla, de modo que las llamadas en paralelo no pueden pasarse del tope):

Tope Significado Error al excederlo
per_order_cents Máximo por una orden, renovación o pago 403 spending_cap_exceeded, details.cap = "per_order"
daily_cents Máximo en 24 horas móviles 403 spending_cap_exceeded, details.cap = "daily"
orders_per_hour Órdenes en una hora móvil (10 si no se define) 403 spending_cap_exceeded, details.cap = "hourly_orders"

Una llave con purchase y sin topes cargados no puede comprar: 403 spending_caps_not_set. Como máximo puede haber 3 órdenes en curso por llave; si hay más, responde 429 too_many_in_flight. GET /v1/me muestra los topes y tu consumo (spent_today_cents, orders_last_hour), y POST /v1/quotes te indica si una orden excedería un tope (would_exceed_cap).

Requisitos antes de comprar por API

Comprar, renovar o pagar una factura por API exige todo lo siguiente. Revisa cada punto antes de salir a producción; GET /v1/me y GET /v1/payment-method muestran casi todos.

Requisito Quién lo hace Cómo comprobarlo Qué recibes si falta
Una dirección IP autorizada para la llave El personal de Solume, tras tu ticket GET /me: allowed_ips_count mayor que 0 403 ip_not_allowed en cada llamada
El permiso purchase (y billing para cambiar la renovación automática o recargar) El personal de Solume GET /me: scopes 403 insufficient_scope
Topes de gasto cargados: por orden y por día (las órdenes por hora son 10 por defecto) El personal de Solume GET /me: can_purchase: true y caps 403 spending_caps_not_set o 403 spending_cap_exceeded
Una tarjeta predeterminada guardada con el mandato de cobro automático, versión 2026-11-v2, aceptado para esa tarjeta Tú, en el panel (Facturación) GET /payment-method: chargeable_off_session: true 402 requires_action con details.reason no_card o no_mandate
Saldo para el total, si pagas con payment: "balance" o quieres evitar la tarjeta Tú, en el panel o con POST /balance/top-ups GET /balance 402 insufficient_balance
La orden pasa las comprobaciones de confianza y antifraude del sitio web Automático n/a 403 order_rejected

Sobre el mandato: es la autorización que da el titular de la cuenta, en el sitio web, para cobrar una tarjeta guardada sin estar presente. La versión 2026-11-v2 cubre renovaciones, pagos de facturas y compras iniciadas por tus llaves de API, dentro de los topes; un mandato anterior (versión 2026-10) solo cubría renovaciones y recargas automáticas, así que una tarjeta guardada con él no se puede cobrar en compras por API hasta que el titular acepte el texto nuevo. mandate_accepted en GET /payment-method indica que hay aceptado, para esa tarjeta, un mandato de una versión que cubre las compras por API (2026-11-v2; uno anterior no cuenta aunque exista); chargeable_off_session indica que la API puede usarlo de verdad (esa versión, cobros no pausados por el titular y cobros automáticos habilitados en toda la plataforma). Los dos reflejan lo que exigen POST /orders, POST /invoices/{id}/pay y POST /services/{id}/renew con payment: "card" o "auto". Una diferencia: auto_renew: "card" / "balance_and_card" solo pide un mandato válido para renovaciones (cualquier versión aceptada), así que una cuenta que aún tiene el mandato anterior puede activarlo aunque mandate_accepted sea false; esas renovaciones las cobra el proceso de renovación automática, no una compra por API. Con solo saldo no necesitas la tarjeta, pero toda orden con payment: "auto" que el saldo no cubra fallará con 402.

Dos casos más: auto_renew: "card" o "balance_and_card" (en POST /orders o PUT /services/{id}/auto-renew) también comprueba que existan una tarjeta predeterminada y un mandato aceptado, y responde 409 conflict con details.reason no_default_card o mandate_not_accepted; y payment: "card" mientras los pagos con tarjeta están desactivados responde 402 card_declined con details.reason: "stripe_disabled".

Autenticación y entornos

Envía la llave en la cabecera Authorization en cada petición:

texto
Authorization: Bearer sk_live_xxxxxxxx
  • Las llaves reales (sk_live_) funcionan en /v1 solo desde IPs registradas. Las llaves del sandbox (sk_test_) funcionan solo en /sandbox/v1, desde cualquier IP.
  • Una llave en el entorno equivocado responde 403 wrong_environment.
  • Una llave desconocida o revocada responde 401 unauthorized (ambos casos se ven igual, a propósito). Una cuenta suspendida o cerrada responde 403 unauthorized.
  • Las autenticaciones fallidas se limitan por IP (20 por minuto por defecto) antes incluso de buscar la llave; pasado ese límite recibes 429 rate_limited.
  • Las llaves son solo para el titular de la cuenta; no hay delegación. Una llave solo ve los servicios, órdenes y facturas de su propia cuenta: un id de servicio que pertenece a otra persona responde el mismo 404 not_found que uno que no existe.

Convenciones

Tema Regla
Formato JSON de entrada y de salida, UTF-8. Content-Type: application/json en POST y PUT con cuerpo. Cuerpo máximo de 32 KB.
Entrada estricta Los campos desconocidos en un cuerpo o en la cadena de consulta se rechazan con 400 invalid_request.
Ids El id de un servicio es su código público (por ejemplo K3F8QX2P). Las órdenes, facturas, tareas, eventos y llaves SSH usan ids con prefijo: ord_..., inv_..., tsk_..., evt_..., key_.... Los ids internos nunca se exponen.
Dinero { "amount_cents": 720, "currency": "USD" }: un entero en unidades menores, nunca un decimal.
Fechas ISO 8601 en UTC, por ejemplo 2026-11-05T12:00:00.000Z. Una fecha que no aplica es null.
Paginación ?limit= (1 a 100, por defecto 25) y ?cursor=. Las listas responden { "data": [...], "next_cursor": "..." | null }. Los cursores son opacos: devuelve lo que recibiste. Un cursor null es la última página.
Id de la petición Toda respuesta lleva la cabecera X-Request-Id (también en error.request_id). Regístralo y cítalo al contactar con soporte.
Cabeceras de límite Toda respuesta incluye X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (segundos Unix) de la cubeta general. Un 429 incluye además Retry-After (segundos).
Trabajo asíncrono Todo lo que se ejecuta en la plataforma de servidores responde 202 con { "task": { "id": "tsk_...", "status": "queued" } }. Consulta GET /v1/tasks/{id}.
Idempotencia Mira abajo.

Idempotencia

Envía Idempotency-Key (de 8 a 64 caracteres: letras, dígitos, _ o -) en toda llamada que mueva dinero o sea destructiva. Es obligatoria en:

POST /orders, POST /services/{id}/renew, POST /services/{id}/reinstall, POST /services/{id}/reset-password, POST /services/{id}/cancel, POST /invoices/{id}/pay y POST /balance/top-ups.

Cómo funciona:

  • Misma clave y misma petición dentro de 24 horas: se repite la respuesta guardada con la cabecera Idempotent-Replay: true y no se ejecuta nada de nuevo.
  • Misma clave con una petición distinta: 422 idempotency_key_reused. Esto también vale cuando la primera orden se recuperó tras una llamada interrumpida: la clave sigue ligada a la petición original.
  • Misma clave mientras la primera llamada sigue ejecutándose: 409 request_in_progress (reintenta en breve).
  • Clave ausente: 400 idempotency_key_required. Formato incorrecto: 400 invalid_request.
  • Las claves son por llave de API. Genera una por intención (por ejemplo order-<id-de-tu-orden>), reutilízala al reintentar y nunca la reutilices para otra compra.
  • Si la llamada falla con un error (por ejemplo card_declined) o con un 5xx, la clave se libera, de modo que puedes reintentar con la misma clave tras corregir la causa.
  • Las respuestas guardadas nunca contienen contraseñas ni otros secretos.
  • Las rutas que devuelven contraseñas (GET /credentials) no usan idempotencia.

Errores

Toda respuesta que no sea 2xx tiene esta forma. code es estable y está documentado; message es texto para personas y puede cambiar, así que nunca tomes decisiones según él.

json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not have the \"purchase\" scope.",
    "request_id": "req_5b1c0e7d2a9f4c3b8e16",
    "details": { "required_scope": "purchase" }
  }
}

Códigos de error

Código HTTP Causa Qué hacer
invalid_request 400 (también 413, 4xx de validación) Un campo mal formado o desconocido, un cursor incorrecto, un cuerpo de más de 32 KB, un formato incorrecto de Idempotency-Key, un valor erróneo. details puede listar los problemas. Corrige la petición. No la reintentes sin cambios.
idempotency_key_required 400 Se llamó a una ruta que necesita Idempotency-Key sin ella. Añade la cabecera.
unauthorized 401 (403 para una cuenta inactiva) Llave ausente, desconocida o revocada; o la cuenta está suspendida o cerrada. Revisa la llave; contacta con soporte si la cuenta está inactiva.
ip_not_allowed 403 La IP que llama no está registrada para esta llave (details.ip), o la IP está bloqueada. Pide a soporte que autorice la IP. Comprueba que no llamas a través de un proxy o desde otra IP de salida.
wrong_environment 403 Una llave del sandbox en /v1, o una llave real en /sandbox/v1. Usa la URL base que corresponde.
insufficient_scope 403 A la llave le falta un permiso (details.required_scope). Leer las credenciales necesita manage, no read. Pide el permiso a soporte.
spending_caps_not_set 403 La llave tiene purchase pero no tiene topes cargados. Pide a soporte que configure los topes.
spending_cap_exceeded 403 Se excedió una orden, el total de 24 h móviles, el número de órdenes por hora o el límite mensual que el titular de la cuenta fijó en ella (details.cap: per_order, daily, hourly_orders, account_monthly; y remaining_cents donde aplica). Espera a que pase la ventana, compra menos o pide topes más altos.
product_not_resellable 403 El plan no es un plan VPS/VDS. Usa ids de plan de GET /catalog/plans.
order_rejected 403 La orden no pasó las comprobaciones de confianza, antifraude y antiabuso que también se aplican en el sitio web. El mensaje es genérico a propósito. Contacta con soporte; no reintentes en bucle.
not_found 404 El recurso no existe o no es tuyo; también un plan, ubicación o plantilla desconocidos. Revisa el id.
credentials_not_available 404 Esta llave no compró ni restableció el servicio, o pasó la ventana de 24 h (details.reason). Llama a reset-password para obtener una contraseña nueva.
conflict 409 La petición entra en conflicto con el estado actual. details.reason indica el motivo (consulta abajo). Lee details.reason.
request_in_progress 409 La misma Idempotency-Key sigue ejecutándose, o una orden asociada a ella aún se está procesando. Espera y reintenta con la misma clave; para órdenes, consulta GET /orders/{id} si details.order_id está presente.
out_of_stock 409 No hay stock en esa ubicación, o la orden se pagó pero no se pudo entregar y se reembolsó (details.refunded_to). Elige otra ubicación u otro plan.
service_not_ready 409 El servicio todavía se está aprovisionando, o una reinstalación no ha terminado. Cancelar con at: "now" en ese estado también lo responde. Consulta el servicio y reintenta cuando esté active.
service_suspended 409 El servicio está suspendido. Cancelar con at: "now" un servicio suspendido, en pausa o con error también lo responde: usa at: "period_end" o contacta con soporte. Paga la factura abierta (POST /services/{id}/renew).
firewall_unavailable 409 / 503 409: el servicio no tiene firewall (motor none), no tiene IP, o es una entrega simulada. 503: el proveedor del firewall falló (una lectura fallida nunca se devuelve como lista vacía), está caído o sin configurar. 409: no hay nada que hacer. 503: reintenta más tarde.
idempotency_key_reused 422 La clave se usó con una petición distinta. Usa una clave nueva para una petición nueva.
invalid_option 422 Una opción desconocida, una opción repetida, o una opción sin precio para ese ciclo. Revisa GET /catalog/options?plan=.
feature_not_supported 422 La ruta pertenece a otro motor de firewall (details.engine es el motor del servicio). Lee primero GET /services/{id}/firewall.
credentials_consumed 410 La contraseña de un solo uso ya se leyó. Llama a POST /services/{id}/reset-password.
requires_action 402 La tarjeta necesita al cliente. details.reason: authentication_required (3-D Secure), no_card (no hay tarjeta predeterminada guardada) o no_mandate (el mandato versión 2026-11-v2 no está aceptado para esa tarjeta, o los cobros automáticos están pausados o desactivados). No se creó ni se cobró nada. Pide al cliente que lo corrija en el panel (Facturación: guardar la tarjeta, aceptar el mandato) o que se autentique, o recarga el saldo y usa payment: "balance".
card_declined 402 La tarjeta fue rechazada (details.reason: declined o insufficient_funds), o los pagos con tarjeta están desactivados (stripe_disabled). No se creó ni se cobró nada. Pide al cliente que actualice la tarjeta; reintenta con balance.
insufficient_balance 402 El saldo no cubre el total (details.required_cents). Con payment: "auto" también aparece cuando el saldo no alcanza y no se puede usar ninguna tarjeta (details.card indica el motivo). Recarga el saldo.
rate_limited 429 Una cubeta está llena (details.bucket, details.retry_after). Espera Retry-After segundos y reintenta. Usa retroceso exponencial.
too_many_in_flight 429 Tres órdenes de la llave todavía se están creando. Espera a que terminen (Retry-After: 15).
maintenance 503 El nodo bloquea ahora mismo las acciones del panel. Reintenta más tarde.
internal_error 500 (502 al leer la tarjeta; 503 cuando la plataforma no está disponible temporalmente) Un error inesperado. Tras un cobro con tarjeta en una renovación o un pago, puede significar que no se pudo confirmar el resultado. Un 503 (mensaje "The service is temporarily unavailable") llega con Retry-After: 2 cuando una dependencia de la plataforma no responde. Cita el request_id. Ante un 503 reintenta tras Retry-After (conserva la misma Idempotency-Key). Antes de reintentar un pago tras un 500, lee la factura (GET /invoices/{id}) o la orden.

409 conflict incluye details.reason en los casos que no tienen código propio: service_cancelled, service_error, service_paused, cancellation_scheduled, no_billing_cycle, grouped_invoice (págala con POST /invoices/{id}/pay), invoice_not_payable, payment_in_progress (hay un cobro automático con tarjeta o un pago sin confirmar de esa factura pendiente; reintenta más tarde), no_default_card, mandate_not_accepted. Un 409 al cancelar significa que el servicio ya está cancelado, es un subservidor, o es un servicio permanente o de cortesía (con at: "now").

Límites de tasa

Los límites usan una ventana deslizante y se aplican además del límite general. Todos los valores los define Solume (y Solume puede cambiar los límites general y de energía para una llave concreta); GET /v1/me devuelve en rate_limits los límites exactos que se aplican a tu llave. Valores por defecto:

Cubeta Por defecto Se cuenta por
general (todas las llamadas) 120 / minuto llave e IP
cotizaciones (POST /quotes) 30 / minuto llave
órdenes (intentos de POST /orders) 30 / hora llave
energía 6 / minuto y 40 / hora servicio, más 60 / minuto por llave
reinstalación 1 / 10 minutos y 5 / día servicio, más 20 / día por llave
reset-password 3 / hora servicio, más 20 / hora por llave
renovar y pagar factura 5 / hora servicio
ajustes del servicio (hostname, rDNS, llaves SSH, cambios de firewall) 10 / hora servicio
cancelar (y retirar la cancelación) 3 / día servicio
credenciales 10 / hora servicio
service-reads (GET /services/{id}, /status, /metrics, GET /firewall y toda lectura de gsk) 60 / minuto servicio, más 300 / minuto por llave
autenticaciones fallidas 20 / minuto IP de origen

Los límites por servicio cuentan todas las llaves e IPs de tu cuenta sobre ese servicio, así que añadir IPs no los aumenta. La cubeta service-reads protege los servidores y al proveedor del firewall del sondeo constante; se cuenta además de la cubeta general, y esas llamadas también caducan a los 8 segundos. GET /me todavía no lista service-reads. Las compras por hora también están limitadas por el tope orders_per_hour de la llave (10 por defecto). Superar un límite responde 429 rate_limited con Retry-After.

Guía rápida

Toda la vida de un servidor con curl. Sustituye los ids por los de tu propio catálogo. Los ejemplos están recortados; tienen las mismas formas que documenta el archivo OpenAPI, y cada ejemplo de ese archivo se valida contra el esquema de la respuesta real mediante una prueba automatizada. Puedes ejecutar el mismo flujo en el sandbox cambiando la URL base y la llave.

bash
export API=https://api.solumecore.com/v1
export SOLUME_KEY=sk_live_xxxxxxxx
alias solume='curl -sS -H "Authorization: Bearer $SOLUME_KEY" -H "Content-Type: application/json"'

1. Comprueba tu llave, tu IP y tu método de pago

bash
solume $API/me
solume $API/payment-method
solume $API/balance

/me muestra scopes, caps, usage, discount_percent y card_fee_percent. /payment-method debe mostrar "chargeable_off_session": true si quieres pagar con tarjeta:

json
{
  "payment_method": {
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2029,
    "mandate_accepted": true,
    "chargeable_off_session": true
  }
}

Si es null o mandate_accepted es false, guarda la tarjeta y acepta los cobros automáticos en Facturación, dentro del panel.

bash
solume "$API/catalog/plans?type=vps"
solume $API/catalog/locations
solume "$API/catalog/os-templates?plan=cm3x7k2p00001vps2gb"
solume "$API/catalog/options?plan=cm3x7k2p00001vps2gb"

Un plan muestra sus specs, los prices por ciclo (monthly, quarterly, semiannual, annual) con list_price (sitio web) y your_price (con tu descuento), y available.

3. Cotiza

Una cotización no tiene efectos secundarios y usa el mismo cuerpo que la orden:

bash
solume -X POST $API/quotes -d '{
  "plan_id": "cm3x7k2p00001vps2gb",
  "location_id": "us-mia-1",
  "billing_cycle": "monthly",
  "os_template_id": "cm3x7k2p00002ubuntu2404",
  "hostname": "web-1",
  "options": [{ "slug": "anti-ddos", "answer": "yes" }]
}'
json
{
  "quote": {
    "lines": [
      {
        "kind": "plan",
        "description": "VPS Small - monthly",
        "amount": { "amount_cents": 800, "currency": "USD" }
      },
      {
        "kind": "option",
        "slug": "anti-ddos",
        "description": "Anti-DDoS",
        "amount": { "amount_cents": 300, "currency": "USD" }
      },
      {
        "kind": "reseller_discount",
        "description": "Reseller discount (10%)",
        "amount": { "amount_cents": -110, "currency": "USD" }
      }
    ],
    "total": { "amount_cents": 990, "currency": "USD" },
    "card_fee": { "percent": 3, "amount": { "amount_cents": 30, "currency": "USD" } },
    "total_with_card": { "amount_cents": 1020, "currency": "USD" },
    "payable_with": ["balance", "card"],
    "would_exceed_cap": false
  }
}

(Otros campos: plan, location, hostname, billing_cycle, currency, list_total, reseller_discount, subtotal, tax, cap, cap_remaining_cents.) total es el precio pagando con saldo; el recargo de tarjeta es una línea aparte que se añade solo cuando el pago termina en la tarjeta.

4. Compra

bash
solume -X POST $API/orders -H "Idempotency-Key: order-acme-42-001" -d '{
  "plan_id": "cm3x7k2p00001vps2gb",
  "location_id": "us-mia-1",
  "billing_cycle": "monthly",
  "os_template_id": "cm3x7k2p00002ubuntu2404",
  "hostname": "web-1",
  "ssh_public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl me@laptop",
  "options": [{ "slug": "anti-ddos", "answer": "yes" }],
  "payment": "auto",
  "metadata": { "customer": "acme-42" }
}'

201 responde la orden y el servicio nuevo en provisioning:

json
{
  "order": {
    "id": "ord_9173eb967115608b2f",
    "status": "provisioning",
    "total": { "amount_cents": 720, "currency": "USD" },
    "payment_method": "balance",
    "service_id": "K3F8QX2P",
    "auto_renew": "off"
  },
  "service": {
    "id": "K3F8QX2P",
    "hostname": "web-1",
    "status": "provisioning",
    "power_state": null,
    "ips": []
  }
}

(Recortado. Las órdenes también incluyen lines, subtotal, tax, billing_cycle, invoice_number, invoice_id (el id inv_… que usan GET /invoices/:id y POST /invoices/:id/pay), metadata...) La respuesta nunca contiene credenciales. Si reintentas con la misma Idempotency-Key obtienes la misma respuesta con Idempotent-Replay: true, y no se cobra dos veces. metadata (hasta 10 claves, 200 caracteres por valor) se guarda en la orden y en el servicio y se devuelve en todas partes, de modo que puedes asociarla a tus propios registros.

Los errores de pago no crean nada: 402 requires_action, 402 card_declined, 402 insufficient_balance.

5. Espera a que el servicio esté active

bash
solume $API/services/K3F8QX2P

Consulta cada pocos segundos (espaciando hasta 10 a 30 segundos) hasta que status sea active (error significa que el aprovisionamiento falló y la orden se reembolsa; revisa GET /orders/{id}). Una vez activo, power_state es running e ips contiene las direcciones.

6. Lee las credenciales una sola vez

bash
solume $API/services/K3F8QX2P/credentials
json
{ "username": "root", "password": "wm^j&Xt&=L!bkZEK&M2A", "expires_at": "2026-11-06T12:00:11.000Z" }

La contraseña está disponible durante 24 horas tras el aprovisionamiento y se consume con la primera lectura correcta: una segunda lectura responde 410 credentials_consumed. Guárdala de inmediato en tu almacén de secretos y no la registres en logs. La respuesta lleva Cache-Control: no-store. Solo la llave que compró el servicio (o que pidió un restablecimiento o una reinstalación) puede leerla, y necesita el permiso manage (una llave solo con read recibe 403 insufficient_scope). Esta ruta no tiene versión HEAD, así que nada puede consumir la contraseña por accidente. Los servidores Windows usan el usuario Administrator.

7. Opera el servidor

bash
# reiniciar: 202 + tarea
solume -X POST $API/services/K3F8QX2P/power/restart
solume $API/tasks/tsk_883e0ff8c42d09b420

# estado en vivo y métricas
solume $API/services/K3F8QX2P/status
solume "$API/services/K3F8QX2P/metrics?range=24h"

Una tarea pasa por queued, running y luego succeeded o failed (con error.code).

8. Renueva

bash
solume -X POST $API/services/K3F8QX2P/renew -H "Idempotency-Key: renew-K3F8QX2P-2026-12" -d '{"payment":"auto"}'

Paga la factura de renovación abierta (creándola si la ventana de renovación ya abrió) con los mismos topes y el mismo orden de pago que las órdenes. Consulta Facturación y ciclo de vida.

9. Reinstala o cambia el sistema operativo

bash
solume -X POST $API/services/K3F8QX2P/reinstall -H "Idempotency-Key: reinstall-K3F8QX2P-001" \
  -d '{"os_template_id":"cm3x7k2p00002ubuntu2404","confirm":true}'

Esto borra el disco, por lo que se exige confirm: true. Devuelve una tarea; cuando termine con éxito, lee la contraseña nueva con GET /credentials (una reinstalación genera una nueva).

10. Restablece la contraseña

bash
solume -X POST $API/services/K3F8QX2P/reset-password -H "Idempotency-Key: reset-K3F8QX2P-001"
# cuando la tarea termine con éxito:
solume $API/services/K3F8QX2P/credentials

11. Firewall

bash
solume $API/services/K3F8QX2P/firewall
solume -X POST $API/services/K3F8QX2P/firewall/geoblock -d '{"country":"CN"}'

Las operaciones disponibles dependen del motor de firewall del servicio; consulta Firewall.

12. Cancela

bash
# al final del periodo pagado (se puede retirar)
solume -X POST $API/services/K3F8QX2P/cancel -H "Idempotency-Key: cancel-K3F8QX2P-001" -d '{"at":"period_end","confirm":true}'
solume -X POST $API/services/K3F8QX2P/cancel/withdraw

at: "now" es destructivo y no se puede retirar, y solo funciona en un servicio active: mientras se aprovisiona o se reinstala recibes 409 service_not_ready, y con un servicio suspendido, en pausa o con error 409 service_suspended (una suspensión por impago o del personal no se evita cancelando). at: "period_end" siempre es posible. confirm: true es obligatorio en ambos casos.

El mismo flujo en Node.js y Python

Compra, espera a que esté active y lee las credenciales una sola vez.

js
const API = 'https://api.solumecore.com/v1';
const KEY = process.env.SOLUME_KEY; // sk_live_... (o sk_test_... con /sandbox/v1)

async function call(method, path, body, idempotencyKey) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(API + path, {
      method,
      headers: {
        Authorization: `Bearer ${KEY}`,
        ...(body ? { 'Content-Type': 'application/json' } : {}),
        ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (res.status === 429 || res.status >= 500) {
      const wait = Number(res.headers.get('retry-after')) || 2 ** attempt;
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue; // seguro: misma Idempotency-Key
    }
    const json = await res.json();
    if (!res.ok)
      throw Object.assign(new Error(json.error.code), {
        response: json,
        requestId: res.headers.get('x-request-id'),
      });
    return json;
  }
  throw new Error('too many retries');
}

const { service } = await call(
  'POST',
  '/orders',
  {
    plan_id: 'cm3x7k2p00001vps2gb',
    location_id: 'us-mia-1',
    billing_cycle: 'monthly',
    os_template_id: 'cm3x7k2p00002ubuntu2404',
    hostname: 'web-1',
    payment: 'auto',
  },
  'order-acme-42-001',
);

let current = service;
while (current.status === 'provisioning') {
  await new Promise((r) => setTimeout(r, 5000));
  current = await call('GET', `/services/${service.id}`);
}
if (current.status !== 'active') throw new Error(`provisioning ended as ${current.status}`);

const { username, password } = await call('GET', `/services/${service.id}/credentials`);
// Guarda la contraseña en tu gestor de secretos AHORA: una segunda lectura responde 410.
console.log(username, current.ips[0].address);
python
import os, time, requests

API = "https://api.solumecore.com/v1"
KEY = os.environ["SOLUME_KEY"]  # sk_live_... (o sk_test_... con /sandbox/v1)


def call(method, path, body=None, idempotency_key=None):
    headers = {"Authorization": f"Bearer {KEY}"}
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    for attempt in range(5):
        res = requests.request(method, API + path, json=body, headers=headers, timeout=30)
        if res.status_code == 429 or res.status_code >= 500:
            time.sleep(int(res.headers.get("Retry-After", 2 ** attempt)))
            continue  # seguro: misma Idempotency-Key
        if not res.ok:
            err = res.json()["error"]
            raise RuntimeError(f'{err["code"]} (request {err["request_id"]})')
        return res.json()
    raise RuntimeError("too many retries")


service = call("POST", "/orders", {
    "plan_id": "cm3x7k2p00001vps2gb",
    "location_id": "us-mia-1",
    "billing_cycle": "monthly",
    "os_template_id": "cm3x7k2p00002ubuntu2404",
    "hostname": "web-1",
    "payment": "auto",
}, "order-acme-42-001")["service"]

while service["status"] == "provisioning":
    time.sleep(5)
    service = call("GET", f'/services/{service["id"]}')
if service["status"] != "active":
    raise RuntimeError(f'provisioning ended as {service["status"]}')

creds = call("GET", f'/services/{service["id"]}/credentials')
# Guarda creds["password"] en tu gestor de secretos AHORA: una segunda lectura responde 410.
print(creds["username"], service["ips"][0]["address"])

Referencia de endpoints

R lectura, M administración, P compra, B facturación. "Idem" significa que Idempotency-Key es obligatoria. Toda ruta también puede responder 400, 401, 403, 429 y 500 como se describe en Errores; una ruta con {id} responde 404 not_found para un servicio que no es tuyo o que no existe. La columna de límite es la cubeta que cuenta además de la general (consulta Límites de tasa).

Cuenta

Método y ruta Scope Límite Notas
GET /me any La llave (name, key_prefix, tag), scopes, can_purchase, caps, usage, discount_percent, card_fee_percent, currency, allowed_ips_count y los rate_limits que se aplican.
GET /balance B { "balance": { "amount_cents": 2500, "currency": "USD" } }
POST /balance/top-ups B, Idem Cuerpo { "amount_cents": 5000, "method": "card" | "paypal" | "crypto" }. 201 con una checkout_url alojada y expires_at; el cliente paga allí. Mismo mínimo, máximo y límites que la recarga del sitio web (un 400 los muestra). 409 conflict cuando ese método o las recargas no están disponibles.
GET /payment-method B La tarjeta predeterminada y si se puede cobrar fuera de sesión: { "payment_method": { brand, last4, exp_month, exp_year, mandate_accepted, chargeable_off_session } | null }. Solo lectura.
GET /events?since=&type=&limit=&cursor= R Alternativa de consulta a los webhooks. Consulta Webhooks y eventos.

Catálogo (solo VPS y VDS)

Método y ruta Scope Límite Notas
GET /catalog/plans?type=vps|vds&location= R Planes con specs, prices por ciclo (list_price, your_price), available y availability (in_stock, out_of_stock, coming_soon).
GET /catalog/locations R id (úsalo como location_id), name, country, city (null por ahora), in_stock, coming_soon.
GET /catalog/os-templates?plan= R Plantillas válidas para ese plan: id, name, family (linux o windows), distro, version, min_ram_mb, default_user. plan es obligatorio.
GET /catalog/options?plan= R Opciones de compra (por ejemplo Anti-DDoS) con sus precios por ciclo: recurring, one_time y first_invoice, o null para un ciclo sin precio.
POST /quotes R cotizaciones Mismo cuerpo que POST /orders. Devuelve el desglose completo sin efectos secundarios. Errores: 403 product_not_resellable, 404, 422 invalid_option.

Órdenes

Método y ruta Scope Límite Notas
POST /orders P, Idem órdenes Consulta el cuerpo más abajo. 201 { order, service }.
GET /orders/{id} R { order, service }; el servicio es null hasta que existe.
GET /orders?limit=&cursor= R Tus órdenes por API, las más nuevas primero.

Cuerpo de POST /orders y POST /quotes:

Campo Tipo Notas
plan_id string De GET /catalog/plans.
location_id string El código de la ubicación, de GET /catalog/locations. Debe corresponder al plan.
billing_cycle monthly, quarterly, semiannual, annual
os_template_id string De GET /catalog/os-templates?plan=.
hostname string, 3 a 63 Letras minúsculas, dígitos y guiones; no puede empezar ni terminar con -.
ssh_public_key string, opcional Una línea de llave pública. No junto con ssh_key_ids.
ssh_key_ids array, opcional Hasta 5 ids de llaves SSH que ya estén en tu cuenta.
options array, opcional [{ "slug": "anti-ddos", "answer": "yes" | "no" }], hasta 20.
payment auto (por defecto), balance, card Consulta Pagos.
auto_renew off (por defecto), balance, balance_and_card, card Cualquier valor distinto de off necesita billing.
metadata object, opcional Hasta 10 claves (letras, dígitos, _, ., -), valores de hasta 200 caracteres. Se devuelve tal cual.

El status de una orden es pending, paid, provisioning, completed, failed o refunded. Errores de POST /orders: 402 (requires_action, card_declined, insufficient_balance), 403 (spending_cap_exceeded, spending_caps_not_set, order_rejected, product_not_resellable), 409 (out_of_stock, request_in_progress), 422 invalid_option, 429 too_many_in_flight.

Servicios

El objeto de servicio: id, hostname, status (provisioning, active, suspended, paused, cancelled, error), power_state (running, stopped, paused, unknown), plan (id, name, cores, ram_mb, disk_gb), location (id, name, country), os, ips (address, version, primary, rdns), created_at, origin (api o web), metadata y billing (consulta Facturación y ciclo de vida). Los elementos de las listas omiten power_state y billing.

Método y ruta Scope Límite Notas
GET /services?status=&limit=&cursor= R Tus servicios VPS y VDS.
GET /services/{id} R service-reads El objeto completo.
GET /services/{id}/status R service-reads Instantánea en vivo y barata: power_state, uptime_seconds, last_seen, cpu_percent, ram (used_bytes, total_bytes), network (in_bytes, out_bytes).
GET /services/{id}/metrics?range=1h|24h|7d R service-reads { range, available, points: [{ time, cpu_percent, ram_percent, disk_read_bytes_per_sec, ... }] }.
GET /services/{id}/credentials M credenciales { username, password, expires_at } de un solo uso. Consulta la guía rápida. Errores: 404 credentials_not_available, 409 service_not_ready, 410 credentials_consumed.
POST /services/{id}/power/{action} M energía start, stop (apagado forzado), restart (reinicio ordenado) o shutdown (apagado ordenado). Tarea 202. Errores: 409 service_suspended o service_not_ready, 503 maintenance.
POST /services/{id}/reinstall M, Idem reinstalación { "os_template_id": "...", "ssh_key_ids": [...]?, "confirm": true }. Borra el disco. Tarea 202.
POST /services/{id}/reset-password M, Idem reset-password Tarea 202; lee la contraseña nueva con GET /credentials.
GET /tasks/{id} R { id, type, status, service_id, created_at, started_at, finished_at, error }. type: power.start, power.stop, power.restart, power.shutdown, reinstall, reset_password. error.code: hypervisor_error, guest_agent_error, reinstall_failed o timeout. Las tareas se conservan 7 días y las puede leer cualquier llave de la cuenta.
PUT /services/{id}/hostname M ajustes del servicio { "hostname": "web-prod-1" } cambia el hostname real mediante el agente invitado. Responde { hostname, requires_reboot } (Windows lo aplica al reiniciar).
GET /services/{id}/network R ips con su rdns (ptr, status: pending, applied, rejected, forward_confirmed) y rdns_available.
PUT /services/{id}/network/ips/{ip}/rdns M ajustes del servicio { "ptr": "mail.example.com" }. Responde { ip, ptr, status: "pending" }: el registro lo aplica el personal.
GET /services/{id}/ssh-keys R { data: [{ id, type, fingerprint, comment, public_key }] }. Solo llaves públicas.
POST /services/{id}/ssh-keys M ajustes del servicio { "public_key": "...", "name": "laptop"? }. 201 con la llave. La llave también se guarda en tu cuenta (hasta 30). Los ids (key_...) son los mismos que usa ssh_key_ids.
DELETE /services/{id}/ssh-keys/{keyId} M ajustes del servicio 204.
POST /services/{id}/cancel M, Idem cancelar { "at": "period_end" | "now", "confirm": true }. Responde { status: "scheduled" | "requested" | "cancelled", cancel_at, cancel_at_period_end }.
POST /services/{id}/cancel/withdraw M cancelar Deshace una cancelación period_end. { "cancel_at_period_end": false }. No es posible tras una cancelación inmediata.

Las acciones de energía, hostname, llaves SSH y reinstalación exigen que el servicio esté active; de lo contrario responden 409 service_not_ready o service_suspended. El hostname, las llaves SSH y el rDNS se ejecutan de forma síncrona (200, 201, 204); solo la energía, la reinstalación y el restablecimiento de contraseña devuelven tareas.

Facturación

Método y ruta Scope Límite Notas
POST /services/{id}/renew P, Idem renovar { "payment": "auto" | "balance" | "card", "cycles": 1? } (cuerpo opcional). 200 { invoice, service, already_paid }.
PUT /services/{id}/auto-renew B ajustes del servicio { "mode": "off" | "balance" | "balance_and_card" | "card" }. Responde la facturación del servicio.
GET /invoices?status=&service_id=&limit=&cursor= R status: open, overdue, paid, cancelled, disputed. Factura: id, number, status, total, created_at, due_at, paid_at, lines (description, type, service_id, quantity, unit_price, total). type de línea: plan, storage, ips, recurring, option, discount, fee, other.
GET /invoices/{id} R Una factura.
POST /invoices/{id}/pay P, Idem renovar { "payment": ... } (opcional). 200 { invoice, already_paid }. Úsalo para una factura de renovación agrupada.

Solo las facturas cuyos servicios son todos VPS o VDS son visibles y se pueden pagar por la API (sin recargas, borradores ni facturas de migración de nodo); su id es inv_ más el id de la factura.

Firewall

El firewall de un servicio depende del nodo en el que se ejecuta, igual que en el panel. La IP siempre es la IP principal del servicio (nunca la envías). GET /services/{id}/firewall te indica qué motor tienes; llámalo antes de usar las demás rutas.

engine Qué ofrece Rutas
shield Funciones por IP: bloqueo de países, lista de orígenes permitidos, bloqueo de ASN, compuertas de puertos, filtros, lista negra GET /firewall, POST y DELETE /firewall/{feature}
gsk Perfiles y reglas de mitigación, ataques, métricas de tráfico, puertos de caché GET/PUT /firewall/rules, GET /firewall/profiles, GET /firewall/attacks, GET /firewall/metrics, rutas de cache-ports
none Nada: capabilities es [] y state es null Solo GET /firewall
bash
curl $API/services/K3F8QX2P/firewall -H "Authorization: Bearer $SOLUME_KEY"
json
{
  "engine": "shield",
  "capabilities": ["geoblock", "srcwl", "asn-block", "..."],
  "state": {
    "ip": "203.0.113.10",
    "geoblock": ["CN"],
    "geo_policy": "blacklist",
    "srcwl": [],
    "asn_block": [],
    "blacklist": []
  }
}

shield. capabilities lista las funciones que puedes cambiar. state tiene ip, geoblock (códigos de país), geo_policy (whitelist o blacklist), srcwl (orígenes permitidos), asn_block y blacklist ({ ip, manual }). Cambia una función con POST /firewall/{feature} (añadir) o DELETE /firewall/{feature} (quitar) y un cuerpo JSON que depende de la función; ambos responden { "ok": true }:

Función Cuerpo
geoblock { "country": "CN" }
srcwl { "cidr": "198.51.100.0/24" }
asn-block { "asn": "4837" }
tcp-allow-perdst, udp-allow-perdst, ports-fivem, ports-rdp, ports-ssh, syn-gate { "port": 22 }
blacklist { "src_addr": "192.0.2.9" }
geo-rate { "country": "CN", "proto": "tcp" | "udp", "pps": 1000 }; DELETE recibe country y proto
fivem-cache {}
rdp-filter, ssh-filter, udp-strict { "enabled": true } (un ajuste: envía un valor nuevo, DELETE es un 400)
geoblock-policy { "policy": 0 | 1 } (ajuste)
filter-mode { "mode": "filter" | "drop" } (ajuste)

Una función desconocida es 404 not_found. La validación exacta (puertos de 1 a 65535, etcétera) sale de las mismas reglas que el panel.

gsk.

Ruta Notas
GET /firewall/rules, PUT /firewall/rules PUT { "rules": [{ "profile", "source", "port", "protocol": "tcp" | "udp" }] } reemplaza todas las reglas (hasta 200); [] las borra.
GET /firewall/profiles { profiles: [{ id, name, description }] }
GET /firewall/attacks?page= { attacks: [{ id, address, date, peak_pps, peak_bps, duration_seconds }] } para esta IP.
GET /firewall/metrics { samples: [{ time, pass_bps, drop_bps, pass_pps, drop_pps }] }
GET /firewall/cache-ports { cache: [{ type, ports }] }. Tipos: steam-a2s, raknet, sampp, fivem, minecraft-java, source, teamspeak.
POST /firewall/cache-ports/{type} { "ports": [1 to 65535] } (de 1 a 50 puertos) responde { ports }.
DELETE /firewall/cache-ports/{type}/ports { "ports": [...] } quita algunos puertos. DELETE /firewall/cache-ports/{type} borra un tipo; DELETE /firewall/cache-ports borra todo.

Errores. Llamar a una ruta del otro motor responde 422 feature_not_supported con details.engine. Un servicio en un nodo sin firewall (o sin IP) responde 409 firewall_unavailable (mensaje "This service has no firewall." para none), sin llamar a ningún proveedor. Si el proveedor no está configurado o está caído, recibes 503 firewall_unavailable con un mensaje genérico (el texto y las credenciales del proveedor nunca se devuelven); una respuesta del proveedor que significa que nuestras propias credenciales son incorrectas (401, 403, 404 o 422) también es un 503, y solo un 400 o 409 del proveedor se convierte en un 400 invalid_request genérico; una limitación del proveedor es 429 rate_limited. Los cuerpos de shield son estrictos, y el profile de una regla de gsk debe existir en el proveedor (details.valid_profiles los lista). Los resultados de shield siempre se limitan a la IP del servicio, y los ataques de gsk se devuelven solo para esa IP. Las lecturas cuentan contra service-reads (60 por minuto por servicio, 300 por minuto por llave) además del límite general; los cambios cuentan contra service-settings (10 por hora por servicio).

Facturación y ciclo de vida

Precios y descuentos

your_price = list_price x (1 - discount/100) por ciclo y por opción. El descuento de tu llave se aplica a las compras y a las renovaciones: un servicio comprado por la API recuerda la llave que lo compró, y cada factura de renovación incluye una línea negativa Reseller discount igual al porcentaje de descuento actual de la llave. Cambiar el porcentaje afecta a las facturas que se emitan después, nunca a las ya emitidas. Un servicio comprado en el sitio web no tiene descuento. En un pago con tarjeta, el Card fee (un porcentaje de tu precio) es una línea aparte, que se añade solo cuando se cobra la tarjeta y no se reembolsa. billing.price muestra el importe de la próxima renovación.

El objeto billing

cycle, price (lo que te cuesta la próxima renovación), current_period_end, next_invoice_at, open_invoice_id, due_at, grace_ends_at, suspends_at, terminates_at, auto_renew, cancel_at_period_end y renewable. Las fechas que no aplican son null. Úsalos para mostrar a tus clientes cuándo pagar.

Cómo funcionan las renovaciones

  1. Una factura de renovación la genera una ejecución diaria, unos días antes de la fecha de renovación (dunning.diasAvisoPrevio, 3 días por defecto). Su fecha de vencimiento es la fecha de renovación. Recibes invoice.created (consulta Webhooks).
  2. La pagas con POST /services/{id}/renew (o POST /invoices/{id}/pay), o automáticamente, según el modo de auto_renew.
  3. Si la factura no está pagada cuando vence, el servicio se suspende por impago en ese momento. No hay periodo de gracia antes de la suspensión; grace_ends_at es igual a suspends_at.
  4. Mientras está suspendido, los reintentos con tarjeta siguen el calendario dunning.reintentosHoras (24, 72 y 168 horas por defecto) tras un cobro fallido. Pagar la factura reactiva el servicio (service.unsuspended).
  5. Un servicio suspendido durante dunning.diasGraciaCancelacion días (15 por defecto) se termina: el servidor se detiene y se corta su red (service.terminated, estado cancelled). La máquina virtual nunca se elimina automáticamente.

Estos números son ajustes de la plataforma y pueden cambiar; las fechas de cada servicio siempre están en su objeto billing, así que léelas en lugar de fijar los valores en tu código.

POST /services/{id}/renew es seguro de llamar más de una vez: una factura pagada responde 200 con already_paid: true, y preguntar antes de que abra la ventana de renovación no cobra nada ni prepaga nada (already_paid: true, con la última factura pagada). Un servicio cancelado, con error o en pausa responde 409 conflict con details.reason.

Modos de renovación automática

Los servicios comprados por API empiezan con auto_renew: "off". Cámbialo con PUT /services/{id}/auto-renew (necesita billing) o defínelo al hacer la orden.

Modo Comportamiento
off Nunca se cobra nada automáticamente. Renuevas con /renew. La factura, los recordatorios, la suspensión y la terminación siguen igualmente el calendario de arriba.
balance Una pasada que corre cada 10 minutos paga con el saldo la factura de renovación pendiente o vencida cuando el saldo cubre el total completo (sin pagos parciales). Si no lo cubre, no se cobra nada.
balance_and_card Saldo cuando cubre el total completo (la misma pasada de 10 minutos); si no, se cobra la tarjeta predeterminada según el calendario de la tarjeta de abajo.
card La tarjeta predeterminada. El primer intento se hace en el más temprano de estos momentos: 2 días después de generarse la factura (comercio.cobroAutomatico.diasAvisoPrevio, un ajuste) y 24 horas antes del vencimiento; de inmediato si quedan menos de 12 horas. Necesita una tarjeta y el mandato aceptado (de lo contrario 409 no_default_card / mandate_not_accepted).

Si el saldo queda bajo tras un pago, recibes balance.low.

Pagos

  • Orden de pago con payment: "auto": primero el saldo de la cuenta; si no cubre el total, se cobra la tarjeta guardada predeterminada fuera de sesión a través del procesador de tarjetas, con el mandato que el cliente aceptó en el sitio web. payment: "balance" o "card" fuerzan un solo método. Un pago nunca se divide.
  • PayPal y cripto no se cobran en segundo plano. Solo pueden recargar el saldo (POST /balance/top-ups devuelve una URL de checkout alojado para el cliente), tras lo cual pagas con el saldo.
  • Tarjeta y mandato: los pagos con tarjeta por API (payment: "card", o "auto" cuando el saldo no alcanza) necesitan una tarjeta predeterminada guardada con el mandato versión 2026-11-v2 aceptado. Sin él, la llamada responde 402 requires_action con details.reason no_card o no_mandate antes de crear nada. Consulta Requisitos antes de comprar por API.
  • Cuando la tarjeta necesita al cliente (3-D Secure) la llamada responde 402 requires_action (details.reason: "authentication_required"); si se rechaza, 402 card_declined. No se crea ni se aprovisiona nada, el gasto reservado se libera y se puede reintentar con la misma Idempotency-Key tras corregir la causa. 402 insufficient_balance significa que el saldo no cubre el total (details.required_cents).
  • Lo que hay que hacer en el sitio web: guardar la tarjeta, aceptar el mandato de cobro automático, recargar el saldo por PayPal o cripto si lo deseas, y que el personal conceda purchase, billing, las IPs y los topes. El panel muestra el estado de tu tarjeta en Tarjeta para compras por API.
  • Un 500 internal_error tras un cobro con tarjeta en una renovación o un pago significa que no se pudo confirmar el resultado: el gasto sigue contado, la factura no se cancela y no se acepta ningún otro pago de ella durante 2 horas. Lee GET /invoices/{id}.
  • Si una orden se paga pero no se puede entregar, el importe se devuelve al saldo o a la tarjeta y recibes 409 out_of_stock con details.order_id.

Webhooks y eventos

Registra un endpoint HTTPS en API y webhooks, dentro del panel (o lee el artículo sobre webhooks). Cada entrega es un POST firmado con HMAC-SHA256 y se reintenta automáticamente. Cada webhook elige sus eventos, o * para todos.

Formato de la entrega

json
{
  "id": "cm0entrega0ejemplo",
  "evento": "invoice.paid",
  "creado": "2026-12-02T06:01:12.000Z",
  "datos": {
    "invoice_id": "inv_cm3z4c9s00007renew02",
    "number": "INV-2026-000412",
    "status": "paid",
    "total": { "amount_cents": 720, "currency": "USD" },
    "due_at": "2026-12-05T06:00:00.000Z",
    "paid_at": "2026-12-02T06:01:12.000Z",
    "service_ids": ["K3F8QX2P"]
  }
}

Las claves del sobre (id, evento, creado, datos) son el sobre de webhooks de la plataforma y se mantienen tal cual; el contenido de datos de los eventos de revendedor está en inglés. Cabeceras: Solume-Signature: t=<unix seconds>,v1=<hex>, Solume-Event, Solume-Delivery (igual que id) y User-Agent: SolumeCore-Webhooks/1.0.

Verifica la firma: v1 es el HMAC-SHA256 en hexadecimal de <t>.<raw body> con tu secreto de firma whsec_ como clave (la cadena completa). Usa los bytes crudos antes de interpretarlos, compara en tiempo constante y rechaza un t que se aleje más de cinco minutos de tu reloj.

js
import crypto from 'node:crypto';

export function isValidSignature(header, rawBody, secret, tolerance = 300) {
  const parts = Object.fromEntries((header ?? '').split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || !parts.v1 || Math.abs(Date.now() / 1000 - t) > tolerance)
    return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
  const received = Buffer.from(parts.v1, 'hex');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

Reintentos: se da por entregada cualquier respuesta 2xx; responde en menos de 10 segundos. Los errores de red, los tiempos de espera agotados, 408, 429 y 5xx se reintentan hasta 8 veces con esperas crecientes (desde unos 30 segundos hasta 4 horas); las redirecciones y los demás 4xx fallan sin reintentos. Tras 20 entregas fallidas consecutivas el webhook se pausa. Los reintentos reutilizan el mismo id, así que deduplica con él. No se garantiza el orden; usa creado.

Eventos

Estos eventos de revendedor existen y se emiten hoy:

Evento Cuándo datos
order.paid Se pagó una orden (justo después de POST /orders) order_id, status, service_id, total, payment_method
order.failed Una orden pagada no se pudo entregar y se reembolsó order_id, status
invoice.created Se generó una factura de renovación invoice_id, number, status, total, due_at, paid_at, service_ids
invoice.paid Se pagó una factura, por cualquier vía igual que invoice.created
invoice.overdue Una factura pasó su fecha de vencimiento sin pagarse igual que invoice.created
service.renewed Se pagó una renovación service_id, invoice_id, current_period_end
service.suspended Se suspendió un servicio service_id, reason (non_payment u other)
service.unsuspended Un servicio suspendido se reactivó por un pago service_id, reason (payment_received)
service.terminated Un servicio suspendido llegó al final de los días de gracia, o una cancelación al final del periodo entró en vigor service_id, reason (non_payment o cancelled_at_period_end)
balance.low Un pago con saldo dejó menos saldo que el de ese pago balance
service.provisioned Tu VPS/VDS comprado por la API se entregó y ya está active service_id, order_id, status: "active"
service.provision_failed El aprovisionamiento agotó sus intentos y el servicio pasó a error (aún puede llegar después un service.provisioned si nuestros reintentos tienen éxito) service_id, order_id, status: "error", error: { code: "provision_failed", message } (un mensaje genérico fijo)
task.succeeded Una tarea terminó bien (energía, reset de contraseña, reinstalación); GET /tasks/{id} dice lo mismo task_id, type, status: "succeeded", service_id
task.failed Una tarea falló o se dio por perdida como timeout (10 minutos en energía y reset de contraseña, 3 horas en una reinstalación), aunque nadie la consulte task_id, type, status: "failed", service_id, error: { code }
service.cancel_scheduled POST /services/{id}/cancel con at: "period_end" (o la misma petición desde el panel) service_id, cancel_at (null cuando el personal procesa la baja porque no hay ciclo automático)

Solo se envían para cuentas con una llave de API activa, una vez por objeto (deduplicados durante 24 horas), y nunca incluyen credenciales, datos del nodo, hostnames ni direcciones IP. service.provisioned y service.provision_failed solo se envían para servidores comprados por la API, no para compras hechas en la web. Una tarea produce un evento de cierre: task.succeeded o task.failed, nunca los dos. service.cancel_scheduled se envía una vez por petición: repetir la petición, at: "now" y cancel/withdraw no emiten nada. Tu webhook también puede suscribirse a los demás eventos de tu cuenta (energía, copias de seguridad, avisos de facturación... consulta el artículo sobre webhooks).

No tienes que esperar eventos: consultar GET /services/{id} (hasta que status sea active) y GET /tasks/{id} siempre funciona y es el respaldo seguro si se pierde una entrega. El sandbox emite los mismos eventos como test.<evento>.

GET /events: consultar en lugar de recibir

GET /v1/events?since=&type=&limit=&cursor= devuelve los eventos de los últimos 30 días, del más antiguo al más nuevo, construidos a partir de las entregas de tus webhooks:

json
{
  "data": [
    {
      "id": "evt_cm3z0a1b20001xyz",
      "event": "invoice.paid",
      "created_at": "2026-12-02T06:01:12.000Z",
      "service": null,
      "actor": null,
      "data": {
        "invoice_id": "inv_cm3z4c9s00007renew02",
        "status": "paid",
        "service_ids": ["K3F8QX2P"]
      }
    }
  ],
  "next_cursor": null
}

since es una fecha ISO 8601 y type filtra un solo evento. Una cuenta sin ningún webhook registrado todavía no tiene eventos: registra un webhook (aunque apunte a un sumidero) para alimentar este endpoint. Usa id para deduplicar.

Sandbox

El sandbox es una copia de pruebas gratuita en https://api.solumecore.com/sandbox/v1, con sus propias llaves sk_test_. Resumen (la guía del sandbox tiene todos los detalles):

  • Llaves: creas hasta 3 tú mismo en el panel (API y webhooks, Claves de API, Llaves de pruebas (sandbox)); sin aprobación, sin IPs, con todos los permisos.
  • Real: el catálogo (planes, ubicaciones, plantillas, opciones). Virtual: un saldo de 1000.00 USD, una tarjeta (una cuenta nueva no tiene, como un cliente nuevo: agrégala con POST /sandbox/v1/payment-method/setup, que también acepta el mandato), servicios, órdenes, facturas, tareas y credenciales falsas de un solo uso. No se cobra ni se aprovisiona nada.
  • Ciclo de vida: un servicio nuevo pasa a active tras unos 10 segundos (X-Sandbox-Provision-Seconds: 0 a 120).
  • Escenarios: envía X-Sandbox-Scenario con card_declined, requires_action, insufficient_balance, out_of_stock, provision_failed, task_failed, slow o rate_limited para probar el manejo de errores. X-Sandbox-Firewall-Engine: shield|gsk|none elige el motor de firewall de un servicio nuevo. Los hostnames mágicos fail-card, fail-stock, fail-provision hacen lo mismo en una orden.
  • Viaje en el tiempo: POST /sandbox/v1/time/advance {"days": 30} ejecuta las reglas de renovación, suspensión y terminación; POST /sandbox/v1/reset borra la cuenta (y su tarjeta). time/advance tiene su propio límite (30 por hora por llave). Una cuenta de sandbox se conserva 7 días después de su último uso.
  • Webhooks: se entregan a tus endpoints registrados, pero nunca se pueden confundir con los de producción: el nombre del evento lleva prefijo (test.order.paid; un webhook suscrito a order.paid, a test.order.paid o a * lo recibe), se envía la cabecera Solume-Livemode: false, el payload lleva "livemode": false y la firma usa un secreto derivado, así que no se verifica con tu secreto whsec_ real. Las entregas del sandbox nunca se reintentan. Una operación entrega como máximo 50 eventos más un resumen test.sandbox.events_summarized.
  • No se simula: la lista de IPs permitidas, la aprobación del personal, procesamiento de tarjetas, 3-D Secure real, PayPal y cripto reales, el tiempo real de aprovisionamiento, las comprobaciones antifraude y el stock real.
  • Toda respuesta lleva X-Sandbox: true. Una llave real en el sandbox, o al revés, responde 403 wrong_environment.

Lista de comprobación para pasar a producción

  1. Cambia la URL base a https://api.solumecore.com/v1 y la llave a sk_live_.
  2. Pide a soporte tus IPs, los permisos purchase y billing y tus topes de gasto.
  3. Guarda la tarjeta y acepta el mandato de cobro automático en el panel; comprueba GET /payment-method.
  4. Revisa GET /me: scopes, topes, descuento, recargo de tarjeta y límites de tasa.
  5. Prueba cómo manejas 402, 403, 409, 429 y 5xx, y las Idempotency-Key repetidas.
  6. Registra un endpoint de webhook y verifica las firmas.
  7. Empieza con una orden real pequeña y una renovación antes de abrir el servicio a tus clientes.

Buenas prácticas

  • Idempotencia siempre. Deriva la Idempotency-Key del id de tu propia orden, envíala en cada reintento y trata Idempotent-Replay: true como un éxito.
  • Reintenta con retroceso. Reintenta 429 (respetando Retry-After), 503 y 5xx con retroceso exponencial y jitter. No reintentes sin cambios los 4xx distintos de 408/409 request_in_progress/429.
  • Registra X-Request-Id de cada llamada (y error.request_id) y cítalo en los tickets.
  • Nunca guardes en caché ni registres credenciales. Pasa la contraseña a tu almacén de secretos en la primera lectura. Si la pierdes, usa reset-password.
  • Maneja el 402. requires_action y card_declined necesitan al cliente; recurre a payment: "balance" y mantén el saldo con fondos.
  • Vigila tus topes y límites. Usa would_exceed_cap en las cotizaciones, GET /me (usage) y las cabeceras X-RateLimit-* para frenar antes de llegar al 429.
  • Prefiere webhooks y consulta con criterio. Usa webhooks o GET /events para la facturación y el ciclo de vida, y consulta GET /services/{id} cada 5 a 30 segundos solo mientras esperas el estado active. Usa GET /services/{id}/status para comprobaciones en vivo baratas.
  • Pagina. Sigue next_cursor hasta que sea null; no construyas cursores tú mismo.
  • Concilia. Guarda en metadata tus propios ids al hacer la orden y compara periódicamente GET /services y GET /invoices con tus registros.
  • Haz explícitas las llamadas destructivas. La reinstalación y la cancelación necesitan confirm: true; pide confirmación también en tu propia interfaz.

Seguridad

  • Lista de IPs permitidas. Mantén al mínimo la lista de IPs registradas y pide a soporte que quite las direcciones que ya no uses. Llama desde IPs de salida fijas.
  • Mínimo privilegio. Pide solo los scopes que necesitas: por ejemplo, un trabajo de monitoreo necesita read; el código que compra necesita purchase y nada más. Usa varias llaves (una por sistema) para poder revocar una sin detener las demás.
  • Los topes son una red de seguridad. Mantén los topes por orden y diarios cerca de lo que realmente gastas.
  • Llaves y registros. Una cuenta puede tener hasta 10 llaves reales activas. Borrar una llave real es un borrado lógico (deja de funcionar de inmediato y se conserva su historial) y te pide confirmar tu identidad. Toda llamada hecha con una llave, incluidas las rechazadas, se registra y se conserva 90 días.
  • Rota las llaves. Crea la llave nueva, despliégala y luego revoca la anterior en el panel. Crear o revocar llaves te pide confirmar tu identidad y envía notificaciones. Rota de inmediato si una llave pudo filtrarse.
  • Mantén los secretos fuera del código y de los logs. Usa variables de entorno o un gestor de secretos; nunca pongas una llave en un navegador, una aplicación móvil o un repositorio público. Nunca registres las cabeceras de las peticiones ni las credenciales.
  • Verifica los webhooks. Comprueba la firma con el cuerpo crudo y una tolerancia de tiempo, y deduplica por id.
  • No compartas llaves entre clientes, no envíes Authorization a terceros, no montes un proxy público que exponga la API sin tu propia autenticación y límites, ni desactives la verificación de certificados.
  • El personal recibe alertas por 429 e ip_not_allowed repetidos; un gasto inusual también choca con tus topes.

Registro de cambios

v1.1 (2026-11)

Cambios posteriores a la auditoría de seguridad.

  • GET /services/{id}/credentials ahora necesita el permiso manage (antes era read); HEAD no se atiende.
  • Nueva cubeta de límite service-reads: 60 por minuto por servicio y 300 por minuto por llave para GET /services/{id}, /status, /metrics, GET /firewall y las lecturas de gsk.
  • POST /services/{id}/cancel con at: "now" solo funciona en un servicio active (en otro caso, 409 service_suspended o service_not_ready).
  • Las compras y los pagos por API con tarjeta necesitan el mandato de cobro automático versión 2026-11-v2: sin él, 402 requires_action con details.reason: "no_mandate". auto_renew con tarjeta comprueba la tarjeta y el mandato (409 conflict).
  • Las órdenes devuelven invoice_id (inv_...); una Idempotency-Key reutilizada con otro cuerpo es 422 también después de que una orden se recuperó.
  • Firewall: un fallo del proveedor es 503 firewall_unavailable; las lecturas nunca devuelven una lista vacía ante un fallo.
  • Una dependencia de la plataforma que no responde da 503 con Retry-After en /v1.
  • Sandbox: los webhooks viajan como test.<event> con Solume-Livemode: false y un secreto de firma derivado, sin reintentos; time/advance tiene su propio límite y entrega como máximo 50 eventos más un resumen; las cuentas se conservan 7 días.
  • Hasta 3 llaves de sandbox y 10 llaves reales por cuenta; los registros de uso se conservan 90 días.

v1.0 (2026-11)

Primera versión completa de la API de revendedor, que reemplaza a la API de cliente anterior (/v1/services solo con energía y firewall), la cual no tenía usuarios activos.

  • Catálogo (/catalog/*), cotizaciones y órdenes para VPS y VDS, con descuento y recargo de tarjeta por llave.
  • Servicios: estado, métricas, credenciales de un solo uso, energía, reinstalación, restablecimiento de contraseña, hostname, rDNS, llaves SSH, red, cancelar y retirar la cancelación, y tareas asíncronas.
  • Firewall con tres motores (shield, gsk, none).
  • Facturación: renovaciones, modos de renovación automática, facturas, saldo, recargas e información del método de pago.
  • Permisos (read, manage, purchase, billing), topes de gasto, lista de IPs permitidas, idempotencia, límites de tasa por cubeta y un único formato de error.
  • Webhooks y eventos para órdenes, facturas y el ciclo de vida del servicio, además de GET /events.
  • Sandbox con cuenta virtual, escenarios, viaje en el tiempo y webhooks con livemode: false.
  • Archivos OpenAPI para producción y para el sandbox.

Artículos relacionados

Preguntas frecuentes

¿Usar la API de revendedor de SolumeCore tiene algún costo?

No. La API, las llamadas y las llaves no tienen cargo. Pagas el precio normal de cada VPS o VDS que compras o renuevas, menos el descuento acordado para tu llave y más el recargo de tarjeta acordado cuando se cobra con tarjeta. Ambos son 0% por defecto.

¿Puedo revender servidores de juegos o hosting web con la API?

No. La API de revendedor cubre solo servidores VPS y VDS. Un plan de otro tipo se rechaza con el error product_not_resellable.

¿Por qué mi llave de API responde ip_not_allowed?

Una llave live solo funciona desde las direcciones IP que el equipo de Solume registró para ella. Abre un ticket desde el panel con las IP públicas desde las que llama tu código. Las llaves del sandbox no necesitan IP.

¿Cómo pruebo sin gastar dinero?

Usa el sandbox en https://api.solumecore.com/sandbox/v1 con una llave sk_test_ creada en el panel del cliente. Tiene los mismos endpoints y errores que producción sobre una cuenta virtual con un saldo virtual de 1000.00 USD.

¿Cómo se paga una compra cuando uso la API?

Con payment en auto se usa primero el saldo de la cuenta y, si no alcanza, se cobra la tarjeta guardada predeterminada fuera de sesión con el mandato de cobro automático aceptado en la web. PayPal y cripto solo sirven para recargar el saldo.

¿Te fue útil este artículo?
API de revendedor de SolumeCore: guía, referencia y ejemplos