Ir al contenido

Referencia de la API de ingesta

Esta es la API HTTP neutral que integra cada cliente. Si construye o mantiene un paquete cliente, impleméntelo contra este contrato. Todas las rutas son JSON sobre HTTPS.

Ruta base y versionado

Los puntos de entrada de ingesta se encuentran bajo /api/v1. Envíe y acepte application/json (la subida de copias de seguridad es la única excepción, en multipart/form-data).

Autenticación

Cada petición se autentica con una clave de API de proyecto (lm_...). Se aceptan dos estilos de encabezado; la forma bearer es la recomendada:

http
Authorization: Bearer lm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
http
X-Monitor-Key: lm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

La clave se coteja por su hash SHA-256 y se resuelve hacia un único proyecto. Los fallos de autenticación devuelven 401:

json
{ "message": "Missing API key." }
json
{ "message": "Invalid API key." }

Límites de tasa

  • Ingesta (/ingest, /logs, /dependencies): 300 peticiones por minuto, indexadas por token de proyecto.
  • Heartbeats (/heartbeats/*): 300 peticiones por minuto, indexadas por token de proyecto, con un presupuesto separado del de la ingesta para que una tormenta de excepciones no retrase los heartbeats.
  • Copias de seguridad (/encryption-key, /backups*): 60 peticiones por minuto.

Superar un límite devuelve 429 Too Many Requests.

Cuando la ingesta está suspendida

Mientras quede un pago pendiente en la suscripción del equipo, /ingest, /logs y /dependencies responden 402 Payment Required y no almacenan nada:

json
{ "message": "Ingestion is suspended while a payment is outstanding. Your existing data stays readable in the dashboard." }

Los pings de heartbeat se siguen registrando durante ese periodo, para que las tareas programadas de un equipo suspendido no parezcan rotas. Trate un 402 como un estado que registrar y mostrar, no como un error que reintentar: se levanta solo cuando el pago pasa.

Un punto de entrada de la bóveda responde 402 en el mismo periodo, con su propio mensaje: POST /backups. La lectura sigue abierta, incluido el material de clave, para que una restauración de lo que ya ha pagado siga funcionando.

json
{ "message": "Backup uploads are suspended while a payment is outstanding. Existing backups stay downloadable." }

Listar, descargar y eliminar una copia siguen abiertos, igual que el punto de entrada de clave, que sirve el material que necesita monitor:restore: un archivo ya almacenado puede recuperarse, y descifrarse.


POST /api/v1/ingest, reportar una excepción

Reporta una única ocurrencia de excepción.

Cuerpo de la petición

json
{
  "exception": {
    "class": "RuntimeException",
    "message": "Undefined array key \"id\"",
    "code": 0,
    "file": "/app/Http/Controllers/OrderController.php",
    "line": 88,
    "trace": [
      {
        "file": "/app/Http/Controllers/OrderController.php",
        "line": 88,
        "function": null,
        "class": null,
        "type": null,
        "code": { "start": 83, "lines": ["    public function show(int $id)", "    {", "        $order = $this->orders[$id];", "", "        return view('orders.show', [", "            'order' => $order,", "        ]);", "    }", "", "    public function store(Request $request)", "    {"] }
      },
      { "file": "/vendor/laravel/framework/src/Illuminate/Routing/Controller.php", "line": 54, "function": "show", "class": "App\\Http\\Controllers\\OrderController", "type": "->" }
    ]
  },
  "context": {
    "environment": "production",
    "release": "9f2c1ab3d4e5",
    "occurred_at": "2026-06-28T10:15:00+00:00"
  }
}

Reglas de los campos

CampoRequeridoTipo / límite
exception.classsícadena, máx. 255
exception.messagenocadena, máx. 8192
exception.codenocualquiera
exception.filenocadena, máx. 1024
exception.linenoentero ≥ 0
exception.tracenoarray
exception.trace[].code.startcon codeentero ≥ 1, número de la primera línea del fragmento
exception.trace[].code.linescon codearray de cadenas, máx. 40 líneas de máx. 500 caracteres
context.environmentnocadena, máx. 100
context.releasenocadena, máx. 255 (un SHA hex activa la correlación de commit)
context.occurred_atnocadena, máx. 64 (fecha analizable)

El contexto. La tabla solo nombra las tres claves del contexto que el servidor valida y guarda en columnas propias. El contexto se almacena sin embargo entero, con todas las claves que envía el cliente (url, method, request, user...), y no entra en la base de datos tal como llegó:

  1. primero se enmascara a su llegada, con la lista propia de Quiet Guard, que retoma los secretos de la lista por defecto del SDK de Laravel, y con la regla de los clientes: subcadena sin distinción de mayúsculas, un guion contando como un guion bajo, un nombre buscado también sin sus separadores interiores (api_key enmascara apikey y apiKey), una cadena JSON abierta, o enmascarada entera cuando no se decodifica y un nombre entre comillas seguido de : coincide en ella, los parámetros de consulta y los segmentos de ruta con forma de token de una URL, y la contraseña de un usuario:contraseña@, incluida la de un DSN (mysql://...) (vea el enmascaramiento del núcleo). Un secreto de esa lista que un cliente quitó de la suya se enmascara igualmente, y también lo que un cliente instalado antes de la versión 0.3.1 no sabía enmascarar con esos nombres: sigue enviando la URL sin enmascarar, una instantánea de Livewire cerrada y cabeceras con guiones. Las cabeceras de esa lista que llevan la dirección IP del visitante (x-forwarded-for, x-real-ip...) no se retoman: son datos personales y no secretos, y conservarlas o enmascararlas es una decisión del responsable del tratamiento. El servidor las almacena tal como llegan;
  2. después se acota a 65 535 bytes de JSON codificado y a 30 niveles de anidamiento, porque MariaDB rechaza un documento JSON de 32 niveles en una columna json: por encima de uno u otro límite, o si no puede codificarse, el contexto almacenado es {"_truncated": true}.

environment, release y occurred_at se leen antes de ese límite y conservan sus columnas: un contexto demasiado grande no se los lleva. Sellado o en claro, lo que se escribe es este contexto enmascarado y acotado, y un equipo con el almacenamiento cifrado activo nunca sella el valor en bruto.

Los marcos. El servidor solo conserva de cada marco file (cortado a 1024 caracteres), line, function y class (255 caracteres cada uno), type (8), is_site y, de code, solo start y lines. Un valor de otro tipo que el esperado se descarta en lugar de convertirse, salvo para line, que acepta cualquier número, incluso escrito como cadena ("88"), y lo conserva como entero, y para code.start, que acepta un entero escrito como cadena ("40", ya que la validación rechaza cualquier otro número) y también lo conserva como entero; cualquier otra clave, empezando por los argumentos de una llamada, nunca entra en la base de datos.

Marco cero y fragmento de código. A partir de la versión 0.3, los clientes colocan el punto de lanzamiento al principio de trace: un marco reducido al file y a la line de la propia excepción, ya que la traza de PHP empieza en el llamador y nunca contuvo la línea que falló. Un trace_limit cuenta ese marco (1 = solo el punto de lanzamiento). Cada marco puede llevar un objeto code opcional: start, el número de su primera línea, y lines, las líneas de código fuente que rodean la línea del marco. Los clientes envían las cinco líneas a cada lado (once como máximo), cada una cortada a 500 caracteres, en como máximo diez marcos por informe, nunca para una ruta que contenga /vendor/, y sustituyen entera por [scrubbed] cualquier línea que nombre una clave de su lista de enmascaramiento y le dé un valor. El servidor no vuelve a pasar por esas líneas, solo el contexto se enmascara a su llegada; rechaza un fragmento de más de 40 líneas o de más de 500 caracteres por línea, y sella los fragmentos en reposo junto con el resto de la traza cuando el almacenamiento cifrado del equipo está activo.

Respuestas

  • 202 Accepted: almacenada:

``json { "message": "accepted", "stored": true, "issue_id": "01kw6tkrd8wjbfv2pktr7ssw43" } ``

  • 202 Accepted: cuota superada, confirmada pero no almacenada:

``json { "message": "accepted", "stored": false } ``

  • 422 Unprocessable Entity: fallo de validación.

issue_id es el identificador público de la incidencia, un ULID. Es el mismo valor que lleva la URL del panel, así que puede registrarlo y pegarlo en la barra de direcciones. No es un número de fila, y la respuesta no incluye ningún contador de ocurrencias: esos números habrían indicado a cada proyecto cuántas excepciones había almacenado toda la plataforma.


POST /api/v1/logs, reportar logs de aplicación (lote)

Almacena un lote de entradas de log. Condicionado a la funcionalidad de plan logs.

Cuerpo de la petición

json
{
  "logs": [
    {
      "level": "error",
      "message": "La passerelle de paiement a expiré",
      "context": { "order_id": 1234 },
      "channel": "payments",
      "environment": "production",
      "release": "9f2c1ab",
      "logged_at": "2026-06-28T10:15:00+00:00"
    }
  ]
}

Reglas de los campos

CampoRequeridoTipo / límite
logssíarray, de 1 a 500 entradas
logs.*.levelnocadena, máx. 20
logs.*.messagesícadena, máx. 8192
logs.*.contextnoarray
logs.*.channelnocadena, máx. 100
logs.*.environmentnocadena, máx. 100
logs.*.releasenocadena, máx. 255
logs.*.logged_atnocadena, máx. 64

El contexto de un log. El servidor no lo enmascara: es lo que su aplicación decidió escribir en su propio log, y lo que se le aplica es la lista de enmascaramiento de su cliente, antes del envío. El servidor lo acota con la misma regla que el contexto de una excepción: por encima de 65 535 bytes de JSON codificado, se sustituye por {"_truncated": true}, y la entrada se almacena con el resto del lote.

Respuestas

  • 202 Accepted:

``json { "message": "accepted", "stored": 137 } ``

stored indica cuántas entradas se han registrado realmente (un lote parcial se almacena cuando la cuota está casi llena).

  • 403 Forbidden: el plan no incluye los logs:

``json { "message": "Application logs are not available on your current plan." } ``


POST /api/v1/dependencies, reportar el composer.lock

Sustituye la instantánea de dependencias del proyecto y desencadena un escaneo de vulnerabilidades en segundo plano.

Cuerpo de la petición

json
{
  "packages": [
    { "name": "laravel/framework", "version": "11.9.0", "is_dev": false },
    { "name": "phpunit/phpunit", "version": "11.2.0", "is_dev": true }
  ]
}

Reglas de los campos

CampoRequeridoTipo / límite
packagessíarray, de 1 a 5000 entradas
packages.*.namesícadena, máx. 255
packages.*.versionsícadena, máx. 100
packages.*.is_devnobooleano

Respuesta

  • 202 Accepted:

``json { "message": "accepted", "dependencies": 142 } ``


POST /api/v1/heartbeats/{slug}, hacer ping a una tarea programada

Señala que una tarea programada acaba de ejecutarse. El cuerpo es opcional: sin cuerpo, el ping se comporta como siempre lo ha hecho. El SDK de Laravel envía, para sus heartbeats automáticos:

json
{ "auto": true, "kind": "queue", "period": 1, "grace": 9, "queue": "redis/emails", "lag_ms": 1840 }

auto debe valer true para ser tenido en cuenta; cualquier otro valor deja que el ping se comporte como si no llevara cuerpo. kind vale scheduler o queue. queue lleva hasta 100 caracteres. Un heartbeat creado por un ping que lleva auto, kind, period (1 a 1440) y grace (0 a 1440) nace armado; un heartbeat existente nunca se vuelve a armar. lag_ms (0 o más; un valor superior a 86.400.000, un día, se guarda como un día) solo se registra para un heartbeat de cola, y un ping de cola sin lag_ms borra el valor anterior. Un campo ilegible se ignora, nunca se rechaza: el ping siempre se registra.

{slug} se normaliza a minúsculas, dígitos y guiones. El primer ping sobre un slug desconocido registra el heartbeat, armado cuando el cuerpo anterior basta para armarlo, sin armar en caso contrario: consignado en ambos casos, nunca alerta hasta que se define un periodo esperado. Vea Heartbeats.

Respuestas

  • 202 Accepted:

``json { "message": "pong", "armed": true } ``

armed indica si hay un periodo esperado definido, para que un cliente distinga un ping consignado de uno vigilado.

  • 422 Unprocessable Entity: el slug se normaliza a una cadena vacía, o el proyecto ya contiene el número máximo de heartbeats.

Puntos de entrada de la bóveda de copias de seguridad

Estos alimentan la bóveda de copias de seguridad cifradas. Todos requieren la funcionalidad de plan backups. Mientras quede un pago pendiente, POST /backups responde 402 Payment Required; recuperar el material de clave, listar, descargar y eliminar siguen abiertos, para que monitor:restore siga funcionando sobre los archivos que ya tiene.

GET /api/v1/encryption-key

Sirve el material de clave del equipo para que el cliente pueda sellar y (con la frase secreta, en local) desenvolver.

json
{
  "public_key": "base64...",
  "private_key_wrapped": "base64...",
  "kdf_salt": "base64..."
}
  • 403 si el plan no incluye las copias de seguridad.
  • 404 si el equipo no ha configurado claves de cifrado.

GET /api/v1/backups

json
{
  "backups": [
    {
      "id": "9b1c...uuid",
      "type": "database",
      "name": "nightly",
      "size_bytes": 10485760,
      "checksum": "sha256...",
      "created_at": "2026-06-28T03:00:00+00:00"
    }
  ]
}

POST /api/v1/backups

Subida multipart/form-data de un blob ya cifrado.

CampoRequeridoObservaciones
typesídatabase o files
namenocadena, máx. 255
filesíel archivo cifrado
  • 201 Created devuelve los metadatos de la copia de seguridad almacenada (misma forma que una entrada de la lista).
  • 403 si el plan no incluye las copias de seguridad.
  • 413 Request Entity Too Large si la subida superaría la cuota de almacenamiento:

``json { "message": "Backup storage quota exceeded.", "used_bytes": 5012345678, "limit_bytes": 5000000000 } ``

GET /api/v1/backups/{uuid}

Transmite el blob cifrado como descarga (<nombre>.enc).

DELETE /api/v1/backups/{uuid}

Elimina la copia de seguridad. Devuelve 204 No Content.


Recapitulación de errores

EstadoSignificado
202 AcceptedIngerido (consulte stored para saber qué se ha conservado).
201 CreatedCopia de seguridad subida.
204 No ContentCopia de seguridad eliminada.
401 UnauthorizedClave de API ausente o inválida.
402 Payment RequiredIngesta y subidas de copias suspendidas mientras quede un pago pendiente.
403 ForbiddenFuncionalidad ausente del plan del equipo.
404 Not FoundClaves de cifrado no configuradas (punto de entrada de clave).
413 Payload Too LargeCuota de almacenamiento de copias de seguridad superada.
422 Unprocessable EntityFallo de validación.
429 Too Many RequestsLímite de tasa superado.

Está leyendo la documentación Quiet Guard v1.0.