# API Pública Nova — Referencia para LLMs

Resumen compacto y legible por máquina de la API de desarrolladores de
Nova. La versión humana está en <https://novadao.app/es/devs>. Las dos se generan
del mismo spec en TypeScript, así que se mantienen sincronizadas.

- URL base: `https://novadao.app`
- Prefijo versionado: `/api/v1/`
- Todas las solicitudes y respuestas son JSON.
- Envía la clave de API como `Authorization: Bearer <clave>`. Nunca `X-Api-Key`.
- Solo servidor-a-servidor por defecto. Las llamadas desde el navegador (solicitudes con encabezado `Origin`) se bloquean a menos que el usuario haya registrado la origen en allowed_domains de la clave.

## Contrato de producción de la API

La v1 de Nova es pequeña a propósito:

- Una ruta base: toda ruta autenticada vive bajo `/api/v1`.
- Un tipo de auth: `Authorization: Bearer <clave>`.
- Una superficie de SDK: los SDKs públicos llaman solo a `/api/v1/*`. `/api/wallet/*` es para la SPA Nova con sesión y usa cookies con CSRF.
- Las respuestas exitosas devuelven el recurso directo; los errores siempre usan `{ "error": ... }`.
- Los POSTs que mueven dinero requieren `Idempotency-Key`; también se acepta `X-Idempotency-Key`.
- Receptores de webhook deben verificar `Nova-Signature` contra el cuerpo crudo.
- Regla de mínima ruptura: v1 mantiene nombres y significados actuales; las adiciones son aditivas.

## Paquete SDK

El paquete TypeScript `@novadao/sdk` es delgado: un transporte HTTP, grupos tipados por recurso, errores tipados, idempotencia como opción explícita y verificación de webhooks incluida. No oculta reintentos de POST si quien llama no mandó una clave de idempotencia.

Instala:

```bash
npm install @novadao/sdk
```

Página del paquete: <https://www.npmjs.com/package/@novadao/sdk>

```ts
type NovaApiError = {
  status: number;
  code: string;
  message: string;
  requestId: string;
};

type PixCharge = {
  id: string;
  status: 'pending' | 'paid' | 'expired' | 'failed';
  amountInCents: number | null;
  qrCopyPaste: string | null;
  qrImageUrl: string | null;
  blockchainTxId: string | null;
  createdAt: string;
};

type PaymentLink = {
  id: string;
  name: string;
  mode: 'fixed' | 'range' | 'open';
  status: 'active' | 'paused';
  url: string;
  handle: string;
  slug: string;
  amountBrl: number | null;
  minBrl: number | null;
  maxBrl: number | null;
  depixAddress: string;
  createdAt: string;
};

type WebhookEndpoint = {
  id: number;
  url: string;
  events: string[];
  status: 'active' | 'disabled';
  createdAt: string;
};

type CreatedWebhookEndpoint = WebhookEndpoint & {
  signingSecret: string;
};

type NovaClient = {
  pix: {
    createCharge(
      input: { amountInCents: number; depixAddress: string },
      options: { idempotencyKey: string },
    ): Promise<PixCharge>;
    getCharge(id: string): Promise<PixCharge>;
  };
  paymentLinks: {
    create(input: CreatePaymentLink, options?: { idempotencyKey?: string }): Promise<PaymentLink>;
    get(id: string): Promise<PaymentLink>;
    pause(id: string): Promise<PaymentLink>;
  };
  webhooks: {
    list(): Promise<WebhookEndpoint[]>;
    create(
      input: CreateWebhookEndpoint,
      options: { idempotencyKey: string },
    ): Promise<CreatedWebhookEndpoint>;
    remove(id: number): Promise<{ id: number; deleted: true }>;
    test(id: number): Promise<{ deliveryId: string; queuedAt: string }>;
    verifySignature(
      rawBody: string | Uint8Array,
      header: string,
      secret: string,
      options?: { toleranceSeconds?: number },
    ): Promise<boolean>;
  };
};
```

Uso recomendado:

```ts
import { NovaClient, createIdempotencyKey } from '@novadao/sdk';

const nova = new NovaClient({
  apiKey: process.env.NOVA_API_KEY!,
});

const charge = await nova.pix.createCharge(
  { amountInCents: 100, depixAddress: 'lq1...' },
  { idempotencyKey: createIdempotencyKey('pix') },
);
```

Reglas de implementación:

- Lanza `NovaApiError` con `status`, `code`, `message` y `requestId` para envelopes de error.
- Tipa fallas usando `status` y `code`; el mismo código puede aparecer con distinto status HTTP.
- Reintenta solo errores de red, `408`, `429` y `5xx`. Respeta `Retry-After`.
- Nunca reintentes `POST /pix/charges` sin clave de idempotencia. Para la misma solicitud lógica, reintenta con la misma clave; usa una clave nueva solo para un cobro nuevo.
- Exige clave de idempotencia en `webhooks.create` en el SDK, aunque la API cruda la acepte como opcional.
- Acepta snake_case de la API, pero expón tipos camelCase en TypeScript.

## Autenticación

```
Authorization: Bearer nv_live_8aXk3rT9...
```

El usuario crea una clave de API desde la app Nova (Cuenta → Desarrollador)
después de que su cuenta es aprobada por el equipo ops. La aprobación es
única por cuenta; después el usuario puede crear y revocar claves
libremente.

## Límites de tasa (por clave)

- Tier 1 (por defecto): 60 req/min, 10.000 req/día. Sub-límite POST /pix/charges: 30/min.
- Tier 2 (bajo demanda): 600 req/min, 200.000 req/día. Sub-límite POST /pix/charges: 300/min.

Cada respuesta autenticada incluye:
```
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
X-RateLimit-Daily-Limit, X-RateLimit-Daily-Remaining, X-RateLimit-Daily-Reset
```

Las respuestas 429 incluyen `Retry-After` (en segundos).

## Idempotencia

Envía un encabezado `Idempotency-Key` único (1..255 caracteres ASCII) en
POSTs que crean estado real. También se acepta `X-Idempotency-Key`. Obligatorio
en `POST /api/v1/pix/charges`; opcional en payment-links y webhooks. Las
respuestas cacheadas viven 24h. Las respuestas 5xx no se cachean.

- Replay (misma clave, mismo cuerpo): respuesta cacheada con `X-Idempotent-Replay: true`.
- En proceso (misma clave aún ejecutándose): `409 idempotency_key_in_progress`.
- Conflicto (misma clave, cuerpo distinto): `409 idempotency_key_reused`.

## Envelope de error

Todos los errores comparten este formato. El `request_id` es único y es el único identificador que necesitas darle al soporte de Nova.

```json
{
  "error": {
    "code": "<error_code>",
    "message": "Human-readable explanation.",
    "request_id": "req_...",
    "docs_url": "https://novadao.app/es/devs/errors#<error_code>"
  }
}
```

| HTTP | código | significado |
|------|--------|-------------|
| 401 | `auth_missing` | No se envió el encabezado Authorization. |
| 401 | `auth_use_bearer` | Enviaste X-Api-Key. Usa Authorization: Bearer <clave> en su lugar. |
| 401 | `auth_invalid` | La clave de API es inválida, revocada o no aprobada. |
| 403 | `browser_origin_disallowed` | Solicitud del navegador cuyo Origin no está en allowed_domains de la clave. Las llamadas servidor-a-servidor no deben enviar Origin. |
| 403 | `ip_not_allowlisted` | El IP de la solicitud no está en la lista de IPs permitidos de la cuenta. |
| 429 | `rate_limited` | Demasiadas solicitudes. Lee X-RateLimit-Reset y Retry-After. El agotamiento del límite diario usa el mismo código. |
| 400 / 409 / 422 | `invalid_payload` | El cuerpo, ruta u operación falló la validación. Los SDKs deben guardar el status HTTP y error.code. |
| 400 | `invalid_json` | El cuerpo de la solicitud está vacío o no es JSON válido. |
| 413 | `payload_too_large` | El cuerpo de la solicitud es mayor de lo que acepta el endpoint. |
| 400 | `idempotency_key_required` | POST /pix/charges requiere Idempotency-Key en cada solicitud. |
| 409 | `idempotency_key_in_progress` | Una solicitud con esta Idempotency-Key aún se está procesando. Reintenta la misma solicitud en breve. |
| 409 | `idempotency_key_reused` | Enviaste la misma Idempotency-Key con un cuerpo distinto. Genera una nueva clave para nuevas solicitudes. |
| 404 | `not_found` | El recurso no existe o no es tuyo. |
| 503 | `payment_service_unavailable` | El proveedor de pago está temporalmente fuera de servicio. Reintenta la misma solicitud lógica con la misma Idempotency-Key. |
| 500 | `internal_error` | Algo salió mal de nuestro lado. |

## Endpoints

### GET /api/v1/health

Sonda de disponibilidad (liveness).

Auth: ninguna.

Sin autenticación, sin límite de tasa, sin base de datos. Devuelve 200 mientras la API esté corriendo. Úsala en páginas de status y monitores de uptime.

Formato de la respuesta:
```json
{ "ok": true, "service": "nova-public-api", "server_time": "ISO8601" }
```

Ejemplo — Comprobar que la API está activa:
```bash
curl https://novadao.app/api/v1/health
```
Devuelve:
```json
{
  "ok": true,
  "service": "nova-public-api",
  "server_time": "2026-04-25T12:34:56.000Z"
}
```

### GET /api/v1/ping

Round-trip autenticado.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Confirma que tu clave de API funciona y muestra lo que ve el servidor: el id de la clave, el tier, los dominios permitidos y la hora del servidor. El botón "probar conexión" del panel llama este endpoint.

Parámetros:
- `Authorization` (header, string, obligatorio) — Bearer <tu-clave-de-api>

Formato de la respuesta:
```json
{ "ok": true, "key_id": number, "tier": "tier1" | "tier2", "allowed_domains": string[], "server_time": "ISO8601", "request_id": "req_..." }
```

Ejemplo — Validar tu clave:
```bash
curl -H "Authorization: Bearer $NOVA_API_KEY" \
  https://novadao.app/api/v1/ping
```
Devuelve:
```json
{
  "ok": true,
  "key_id": 42,
  "tier": "tier1",
  "allowed_domains": [],
  "server_time": "2026-04-25T12:34:56.000Z",
  "request_id": "req_8aXk..."
}
```

### POST /api/v1/pix/charges

Crear un cobro PIX (código QR).

Auth: Bearer (clave de API obligatoria, cuenta aprobada).
Idempotencia: `Idempotency-Key` required.
Límite de tasa: tier1: 30/min · tier2: 300/min.

Genera un código QR PIX que tu cliente puede pagar. Presta atención a dos encabezados: Authorization e Idempotency-Key. La Idempotency-Key es OBLIGATORIA — te deja reintentar tras un error de red sin cobrar dos veces.

Parámetros:
- `Authorization` (header, string, obligatorio) — Bearer <tu-clave-de-api>
- `Idempotency-Key` (header, string, obligatorio) — Un valor único que generas por solicitud (1..255 caracteres ASCII). También se acepta `X-Idempotency-Key`.
- `amount_in_cents` (body, integer, obligatorio) — Monto del cobro en centavos (BRL). Mínimo 100 (R$ 1,00).
- `depix_address` (body, string, obligatorio) — Dirección confidencial de la red Liquid (`lq1...`) que recibirá los DePix cuando el cobro liquide.

Formato de la respuesta:
```json
{ "id": "...", "status": "pending", "amount_in_cents": number, "qr_copy_paste": "...", "qr_image_url": "...", "blockchain_tx_id": null, "created_at": "ISO8601" }
```

Ejemplo — Crear un cobro de R$ 50:
```bash
curl -X POST https://novadao.app/api/v1/pix/charges \
  -H "Authorization: Bearer $NOVA_API_KEY" \
  -H "Idempotency-Key: order-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "amount_in_cents": 5000,
    "depix_address": "lq1..."
  }'
```
Devuelve:
```json
{
  "id": "ch_a8b3...",
  "status": "pending",
  "amount_in_cents": 5000,
  "qr_copy_paste": "00020126580014BR.GOV.BCB.PIX...",
  "qr_image_url": "https://novadao.app/api/v1/pix/charges/ch_a8b3.../qr.png",
  "blockchain_tx_id": null,
  "created_at": "2026-04-25T12:34:56.000Z"
}
```

### GET /api/v1/pix/charges/:id

Consultar un cobro PIX.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Devuelve el estado actual de un cobro PIX. Suscríbete a `pix.charge.received` cuando necesites la confirmación bancaria del pago, y a `pix.charge.paid` cuando necesites la transacción liquidada en Liquid.

Parámetros:
- `id` (path, string, obligatorio) — El id del cobro devuelto por POST /pix/charges.

Formato de la respuesta:
```json
{ "id": "...", "status": "pending" | "paid" | "expired" | "failed", "amount_in_cents": number, "qr_copy_paste": "...", "qr_image_url": "...", "blockchain_tx_id": "..." | null, "created_at": "ISO8601" }
```

Ejemplo — Comprobar si el cobro fue pagado:
```bash
curl -H "Authorization: Bearer $NOVA_API_KEY" \
  https://novadao.app/api/v1/pix/charges/ch_a8b3
```
Devuelve:
```json
{
  "id": "ch_a8b3...",
  "status": "paid",
  "amount_in_cents": 5000,
  "qr_copy_paste": "00020126580014BR.GOV.BCB.PIX...",
  "qr_image_url": "https://novadao.app/api/v1/pix/charges/ch_a8b3.../qr.png",
  "blockchain_tx_id": "7f4a...",
  "created_at": "2026-04-25T12:34:56.000Z"
}
```

### POST /api/v1/payment-links

Crear un enlace de pago reutilizable.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).
Idempotencia: `Idempotency-Key` optional.
Límite de tasa: tier1: 60/min.

Genera una URL pública de checkout (`https://novadao.app/c/<handle>/<slug>`) que acepta pagos. Tres modos: `fixed` (un único monto), `range` (mín/máx), `open` (cualquier monto, con límites opcionales).

Parámetros:
- `name` (body, string, obligatorio) — 1 a 80 caracteres. Se usa para generar el slug.
- `mode` (body, "fixed" | "range" | "open", obligatorio) — Modelo de precio.
- `amount_brl` (body, number) — Obligatorio cuando mode es "fixed".
- `min_brl` (body, number) — Se usa cuando mode es "range" (obligatorio) u "open" (opcional).
- `max_brl` (body, number) — Se usa cuando mode es "range" (obligatorio) u "open" (opcional).
- `depix_address` (body, string, obligatorio) — Dirección confidencial de la red Liquid (`lq1...`) que recibe DePix en cada pago exitoso.
- `options` (body, object) — Opcional: { ask_name?: bool, ask_email?: bool, thank_you_message?: string, sales_limit?: int }.

Formato de la respuesta:
```json
{ "id": "lk_...", "name": "...", "mode": "fixed" | "range" | "open", "status": "active", "url": "https://...", "handle": "...", "slug": "...", "amount_brl": number | null, "min_brl": number | null, "max_brl": number | null, "depix_address": "...", "created_at": "ISO8601" }
```

Ejemplo — Vender una pizza por R$ 25:
```bash
curl -X POST https://novadao.app/api/v1/payment-links \
  -H "Authorization: Bearer $NOVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pizza margherita",
    "mode": "fixed",
    "amount_brl": 25,
    "depix_address": "lq1..."
  }'
```
Devuelve:
```json
{
  "id": "lk_HQKFuN22r39cc1Dp",
  "name": "Pizza margherita",
  "mode": "fixed",
  "status": "active",
  "url": "https://novadao.app/c/myshop/pizza-margherita",
  "handle": "myshop",
  "slug": "pizza-margherita",
  "amount_brl": 25,
  "min_brl": null,
  "max_brl": null,
  "depix_address": "lq1...",
  "created_at": "2026-04-25T12:34:56.000Z"
}
```

### GET /api/v1/payment-links/:id

Consultar un enlace de pago.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Devuelve el estado actual de un enlace de pago, incluyendo la URL pública.

Parámetros:
- `id` (path, string, obligatorio) — El id del enlace.

Formato de la respuesta:
```json
{ "id": "lk_...", ... same shape as POST response, but "status" can also be "paused" }
```

Ejemplo — Consultar un enlace antes de mostrarlo:
```bash
curl -H "Authorization: Bearer $NOVA_API_KEY" \
  https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp
```
Devuelve:
```json
{
  "id": "lk_HQKFuN22r39cc1Dp",
  "name": "Pizza margherita",
  "mode": "fixed",
  "status": "active",
  "url": "https://novadao.app/c/myshop/pizza-margherita",
  "handle": "myshop",
  "slug": "pizza-margherita",
  "amount_brl": 25,
  "min_brl": null,
  "max_brl": null,
  "depix_address": "lq1...",
  "created_at": "2026-04-25T12:34:56.000Z"
}
```

### DELETE /api/v1/payment-links/:id

Pausar un enlace de pago.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Borrado suave: pausa el enlace para que deje de aceptar pagos nuevos. Las URLs ya repartidas muestran una página "pausado" en vez de 404, así los clientes en pleno checkout ven un mensaje claro.

Parámetros:
- `id` (path, string, obligatorio) — El id del enlace.

Formato de la respuesta:
```json
{ "id": "lk_...", "name": "...", "mode": "fixed" | "range" | "open", "status": "paused", "url": "https://...", "handle": "...", "slug": "...", "amount_brl": number | null, "min_brl": number | null, "max_brl": number | null, "depix_address": "...", "created_at": "ISO8601" }
```

Ejemplo — Pausar un enlace:
```bash
curl -X DELETE https://novadao.app/api/v1/payment-links/lk_HQKFuN22r39cc1Dp \
  -H "Authorization: Bearer $NOVA_API_KEY"
```
Devuelve:
```json
{
  "id": "lk_HQKFuN22r39cc1Dp",
  "name": "Pizza margherita",
  "mode": "fixed",
  "status": "paused",
  "url": "https://novadao.app/c/myshop/pizza-margherita",
  "handle": "myshop",
  "slug": "pizza-margherita",
  "amount_brl": 25,
  "min_brl": null,
  "max_brl": null,
  "depix_address": "lq1...",
  "created_at": "2026-04-25T12:34:56.000Z"
}
```

### GET /api/v1/webhooks

Listar webhooks.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Devuelve todos los webhooks de tu cuenta.

Formato de la respuesta:
```json
{ "webhooks": [{ "id": number, "url": "...", "events": string[], "status": "active" | "disabled", "created_at": "ISO8601" }] }
```

Ejemplo — Listar webhooks configurados:
```bash
curl -H "Authorization: Bearer $NOVA_API_KEY" \
  https://novadao.app/api/v1/webhooks
```
Devuelve:
```json
{
  "webhooks": [
    {
      "id": 7,
      "url": "https://yourshop.com/webhooks/nova",
      "events": ["pix.charge.paid", "payment_link.paid"],
      "status": "active",
      "created_at": "2026-04-25T12:34:56.000Z"
    }
  ]
}
```

### POST /api/v1/webhooks

Suscribirse a eventos.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).
Idempotencia: `Idempotency-Key` optional.

Crea un suscriptor de webhook. La respuesta incluye un `signing_secret` que se muestra una sola vez — guárdalo; lo vas a necesitar para verificar la firma de cada entrega.

Parámetros:
- `url` (body, string (URL), obligatorio) — Endpoint HTTPS que recibirá las entregas vía POST.
- `events` (body, string[], obligatorio) — Uno o más de: pix.charge.created, pix.charge.received, pix.charge.paid, pix.charge.expired, pix.charge.failed, payment_link.paid.

Formato de la respuesta:
```json
{ "id": number, "url": "...", "events": string[], "status": "active", "signing_secret": "<base64url, shown once>", "created_at": "ISO8601" }
```

Ejemplo — Suscribirse a cobros pagados:
```bash
curl -X POST https://novadao.app/api/v1/webhooks \
  -H "Authorization: Bearer $NOVA_API_KEY" \
  -H "Idempotency-Key: webhook-paid-events-v1" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourshop.com/webhooks/nova",
    "events": ["pix.charge.paid", "payment_link.paid"]
  }'
```
Devuelve:
```json
{
  "id": 7,
  "url": "https://yourshop.com/webhooks/nova",
  "events": ["pix.charge.paid", "payment_link.paid"],
  "status": "active",
  "signing_secret": "Rll0hXsdn2hzp1G6...",
  "created_at": "2026-04-25T12:34:56.000Z"
}
```

### DELETE /api/v1/webhooks/:id

Cancelar la suscripción.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Elimina un suscriptor de webhook. Las entregas ya encoladas igual se intentan.

Parámetros:
- `id` (path, integer, obligatorio) — Id del webhook.

Formato de la respuesta:
```json
{ "id": number, "deleted": true }
```

Ejemplo — Eliminar un webhook:
```bash
curl -X DELETE https://novadao.app/api/v1/webhooks/7 \
  -H "Authorization: Bearer $NOVA_API_KEY"
```
Devuelve:
```json
{
  "id": 7,
  "deleted": true
}
```

### POST /api/v1/webhooks/:id/test

Disparar una entrega de prueba.

Auth: Bearer (clave de API obligatoria, cuenta aprobada).

Encola una entrega `webhook.test.ping` hacia la URL de tu webhook. Úsala para confirmar que tu endpoint recibe y verifica firmas correctamente. La entrega se firma igual que un evento real.

Parámetros:
- `id` (path, integer, obligatorio) — Id del webhook.

Formato de la respuesta:
```json
{ "delivery_id": "whd_...", "queued_at": "ISO8601" }
```

Ejemplo — Enviar un evento firmado de prueba:
```bash
curl -X POST https://novadao.app/api/v1/webhooks/7/test \
  -H "Authorization: Bearer $NOVA_API_KEY"
```
Devuelve:
```json
{
  "delivery_id": "whd_abc123",
  "queued_at": "2026-04-25T12:34:56.000Z"
}
```

## Webhooks

El usuario crea suscriptores de webhook desde `POST /api/v1/webhooks`. Nova publica JSON a la URL registrada cada vez que un evento suscrito se dispara.

### Nombres de evento y payloads

- `pix.charge.created` — Se acaba de crear un cobro PIX. Útil para registrar la creación en tu propia base de datos.
- `pix.charge.received` — Un cliente pagó el cobro PIX en el riel bancario. Los cobros D+2 pueden llegar con `status: "delayed"`, `settlement_status: "delayed"`, `delay_until`, `confirmed_at` y `blockchain_tx_id: null`, y liquidar después vía `pix.charge.paid`.
- `pix.charge.paid` — El cobro PIX liquidó en Liquid e incluye el id de la transacción de liquidación.
- `pix.charge.expired` — Un cobro PIX expiró antes de ser pagado.
- `pix.charge.failed` — Un cobro PIX falló (cancelado, reembolsado o rechazado por la red). Sigue escuchando este evento incluso después de `pix.charge.received`.
- `payment_link.paid` — Un checkout de enlace de pago se completó. Se dispara junto con `pix.charge.paid` cuando el cobro vino de un enlace.

Cada payload viene envuelto:

```json
{
  "api_version": "2026-04-24",
  "event": "<event_name>",
  "created_at": "ISO8601",
  "data": { ... event-specific fields ... }
}
```

### Encabezados de entrega

```
Nova-Signature: t=<unix_seconds>,v1=<sha256_hmac_hex>
Nova-Event: <event_name>
Nova-Delivery-Id: whd_...
Nova-Api-Version: 2026-04-24
Content-Type: application/json; charset=utf-8
```

### Verificación de la firma

`v1` es `HMAC-SHA256(signing_secret, t + "." + raw_body)`. El
`signing_secret` se devuelve exactamente una vez al crear el webhook.
Rechaza firmas cuyo `t` esté a más de 5 minutos del reloj de tu servidor.
Usa los bytes crudos de la solicitud — nunca reserialices.

### Política de reintento

Timeout de 15 segundos. 2xx es éxito. Cualquier otra respuesta encola un
reintento. El cronograma es 1m → 5m → 30m → 2h → 6h → 24h (seis
intentos, contando el primero). Tras el último intento, la entrega se marca
como `failed` y se notifica al dueño de la cuenta en su bandeja.

## Versionado

`/api/v1/` es un contrato permanente. Los campos y tipos de evento nuevos
son aditivos. Los cambios rompedores aterrizan en `/api/v2/` con al menos
12 meses de solapamiento; las respuestas de v1 cargan `Deprecation: true`
y `Sunset: <RFC 9745>` durante el sunset. Los payloads de webhook incluyen
`api_version` para que los suscriptores puedan ramificar.

## Bug bounty y divulgación responsable

Los reportes privados van a `hello@novadao.app` con el asunto `[security] <descripción corta>`. No abras issues públicas ni divulgues detalles hasta que Nova confirme que la divulgación está permitida.

Alcance: superficies de producción del producto y la API Nova bajo `https://novadao.app`, incluyendo `/app`, `/api/v1/*`, flujos de cuenta/autenticación, enlaces de pago, firma y entrega de webhooks, y problemas del sitio o la documentación con impacto de seguridad. Prueba solo en cuentas, claves, webhooks, enlaces de pago y datos que sean tuyos o que tengas autorización explícita para probar.

Reglas: incluye impacto, URL o endpoint afectado, pasos de reproducción y detalles de prueba de concepto. Mantén las pruebas al mínimo necesario. No accedas, modifiques, borres ni extraigas datos de otros usuarios. Si aparecen datos sensibles, detén la prueba y reporta de inmediato. La investigación de buena fe que sigue estas reglas está cubierta por safe harbor.

Fuera de alcance: denial-of-service, pruebas de carga, brute force automatizado, ingeniería social, phishing, ataques físicos, spam, proveedores externos, sistemas de partners, extensiones de navegador, dispositivos de usuarios, entornos que no sean producción salvo autorización explícita, duplicados conocidos, salida de scanner sin impacto demostrado, problemas teóricos sin prueba de concepto práctica y problemas que solo afectan navegadores desactualizados.

Recompensas: los reportes validados pueden ser elegibles para recompensas discrecionales según severidad, impacto real, reproducibilidad, explotabilidad, calidad del reporte y si el problema ya era conocido.

## Soporte

- Telegram: <https://t.me/novadao_supportbot>
- Incluye el `request_id` del envelope de error en cualquier ticket.
- Documentación humana: <https://novadao.app/es/devs>
