# Integración de MontosVE API (guía para agentes de IA)

> Sigue estos pasos para integrar la API de tasas de cambio de MontosVE en este proyecto. Aplica solo los pasos del stack que detectes. No inventes endpoints: usa los documentados aquí y en la referencia completa.

- **Base URL:** `https://api.montosve.com/v1`
- **Autenticación:** header `X-API-Key: <tu-api-key>`
- **Referencia completa (leer si falta contexto):** https://montosve.com/llms-full.txt
- **Especificación OpenAPI:** https://montosve.com/docs/api.json
- **Obtener una API key:** https://montosve.com/register

## Antes de escribir código

1. Detecta el stack del proyecto (por ejemplo `package.json` → JS/TS, o `composer.json` → PHP/Laravel).
2. Busca dónde se guardan las credenciales (`.env`, `config/`, `process.env`). Nunca hardcodees la API key ni la commitees.
3. Agrega la variable `MONTOSVE_API_KEY` con el valor que el usuario te indique. Si no la tiene, pídesela antes de continuar.
4. Crea un único cliente HTTP reutilizable para la API; no disperses llamadas sueltas por el código.

## JavaScript / TypeScript

Crea un cliente mínimo y reusable (por ejemplo `src/lib/montosve.ts`):

```ts
const BASE_URL = 'https://api.montosve.com/v1';
const API_KEY = process.env.MONTOSVE_API_KEY;

type Rate = {
  market: string;
  currency_pair: string;
  rate: number;
  trade_type?: string;
  updated_at: string;
};

async function request<T>(path: string, params: Record<string, string | number> = {}): Promise<T> {
  const url = new URL(BASE_URL + path);
  Object.entries(params).forEach(([key, value]) => url.searchParams.set(key, String(value)));

  const response = await fetch(url, {
    headers: { 'X-API-Key': API_KEY ?? '' },
  });

  if (response.status === 401) {
    throw new Error('MontosVE: API key ausente o invállida.');
  }
  if (response.status === 429) {
    throw new Error('MontosVE: rate limit o cuota mensual excedida.');
  }
  if (! response.ok) {
    throw new Error(`MontosVE: error ${response.status}`);
  }

  return response.json() as Promise<T>;
}

export function getRates(tradeType: 'buy' | 'sell' = 'buy') {
  return request<{ data: Rate[]; meta: { timestamp: string; sources_count: number } }>('/fx/rates', {
    trade_type: tradeType,
  });
}

export function convert(amount: number, from = 'USD', to = 'VES', market = 'bcv') {
  return request<{ amount: number; converted_amount: number; rate: number; market: string }>('/fx/convert', {
    amount,
    from,
    to,
    market,
  });
}
```

Uso:

```ts
const { data } = await getRates('buy');
const conversion = await convert(100, 'USD', 'VES', 'bcv');
```

## PHP / Laravel

Agrega la base URL y la key a `config/services.php`:

```php
'montosve' => [
    'base_url' => env('MONTOSVE_BASE_URL', 'https://api.montosve.com/v1'),
    'api_key' => env('MONTOSVE_API_KEY'),
],
```

Y define `MONTOSVE_API_KEY` en `.env`. Crea el cliente (por ejemplo `app/Services/MontosveClient.php`):

```php
namespace App\Services;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\Response;

final class MontosveClient
{
    private function client(): PendingRequest
    {
        return Http::baseUrl(config('services.montosve.base_url'))
            ->withHeaders(['X-API-Key' => config('services.montosve.api_key')])
            ->acceptJson()
            ->timeout(10);
    }

    /** @return array<string, mixed> */
    public function getRates(string $tradeType = 'buy'): array
    {
        return $this->client()
            ->get('/fx/rates', ['trade_type' => $tradeType])
            ->throw()
            ->json();
    }

    /** @return array<string, mixed> */
    public function convert(float $amount, string $from = 'USD', string $to = 'VES', string $market = 'bcv'): array
    {
        return $this->client()
            ->get('/fx/convert', compact('amount', 'from', 'to', 'market'))
            ->throw()
            ->json();
    }
}
```

Para manejo explícito de errores, reemplaza `->throw()` por `->failed()` y revisa `$response->status()` (`401`, `403`, `422`, `429`).

## Semántica que debes respetar

- Mercados: `bcv` (oficial), `binance_p2p` y `bybit_p2p` (USDT/VES P2P).
- `trade_type`: `buy` (compra) o `sell` (venta); por defecto `buy`.
- Cada respuesta incluye `timestamp`/`updated_at` y puede incluir `stale`; reenvíalos a la UI para trazabilidad.
- Errores esperados: `401` key inválida, `403` feature o cuota, `404` mercado inexistente, `422` parámetros, `429` rate limit, `500`/`503` fuente no disponible.

## Verificación

1. Confirma que el cliente hace una llamada real (sin mocks) y devuelve `data`.
2. Si el proyecto tiene tests, agrega uno que valide el parseo de la respuesta (usa `Http::fake()` en Laravel o un mock de `fetch` en JS).
3. No imprimas la API key en logs.

## Alternativa: consumir las tasas en vivo vía MCP (beta)

Si lo que necesitas es consultar tasas en vivo durante la conversación (no integrarlas en código), MontosVE expone un servidor MCP de solo lectura:

- Endpoint: `https://api.montosve.com/mcp`
- Guía y configuración por cliente: https://montosve.com/mcp
- Autenticación: tu API key como `Authorization: Bearer <api-key>` o `X-API-Key: <api-key>`.
- Tools: `get-fx-rates`, `get-market-rate`, `get-spread`, `convert-currency`, `get-rate-history`, `get-source-health`.
- Resources: `montosve://rates/current`, `montosve://docs/llms-full`. Prompts: `convert-prices-to-ves`.

Configuración del cliente MCP (cada cliente usa un formato distinto):

**opencode** (`opencode.json`) — usa `type: "remote"` y requiere `enabled`:

```json
{
  "mcp": {
    "montosve": {
      "type": "remote",
      "url": "https://api.montosve.com/mcp",
      "enabled": true,
      "oauth": false,
      "headers": { "Authorization": "Bearer TU_API_KEY" }
    }
  }
}
```

**Cursor / Claude Code / VS Code / Cline** (`.mcp.json` o `mcp.json`) — usan `mcpServers`:

```json
{
  "mcpServers": {
    "montosve": {
      "type": "http",
      "url": "https://api.montosve.com/mcp",
      "headers": { "Authorization": "Bearer TU_API_KEY" }
    }
  }
}
```

Notas:
- Puedes usar `X-API-Key: TU_API_KEY` en lugar de `Authorization: Bearer`.
- En opencode, `oauth: false` evita que intente el flujo OAuth, ya que aquí se usa API key.
- Si el archivo de config está versionado, no pongas la key en claro: usa `Bearer {env:MONTOSVE_API_KEY}` (opencode soporta `{env:VAR}`) y exporta la variable de entorno.

El acceso MCP está limitado a usuarios beta. El consumo solo cuenta contra tu cuota mensual en las llamadas a tools.

## Webhooks (Pro y Business)

En lugar de hacer polling, puedes recibir un POST firmado cuando ocurre un evento. Se configura en https://montosve.com/developer/webhooks (requiere plan Pro o Business). Detalles del producto: https://montosve.com/webhooks.

Eventos disponibles:

- `rate.bcv.updated` — el BCV publica un valor nuevo. `data: { date, usd, eur, updated_at }`.
- `rate.p2p.updated` — una tasa P2P cambia por encima del umbral configurado. `data: { market, trade_type, rate, best_rate, previous_rate, change_percent, sampled_at }`.
- `source.status_changed` — una fuente pasa a degraded/down o se recupera. `data: { source, previous_status, status, last_success_at, note }`.
- `api.quota.warning` — el consumo alcanza 80% o 95% de la cuota. `data: { period, percentage, used, limit }`.
- `api.quota.exceeded` — se supera la cuota mensual. `data: { period, used, limit }`.
- `api.key.expiring` — una API key esta por expirar. `data: { keys: [{ name, expires_at, days_remaining }] }`.

Headers: `X-MontosVE-Event`, `X-MontosVE-Timestamp`, `X-MontosVE-Signature`. La firma es HMAC SHA-256 sobre `"{timestamp}.{body}"`.

```php
$signature = 'sha256='.hash_hmac('sha256', $request->header('X-MontosVE-Timestamp').'.'.$request->getContent(), $secret);

if (! hash_equals($signature, (string) $request->header('X-MontosVE-Signature'))) {
    abort(401);
}
```

Buenas practicas:

- Responde 2xx rapido y procesa en segundo plano.
- El evento puede entregarse mas de una vez (reintentos): hazlo idempotente (por ejemplo, usando `event` + `occurred_at`).
- Reintentos automaticos: 4 intentos con backoff 10s, 60s y 300s.
- Configura el umbral de `rate.p2p.updated` para no recibir ruido.

## Al terminar

- Resume qué archivos creaste/modificaste y qué variable de entorno debe configurar el usuario.
- Si falta un caso de uso (spread, historial, health), consulta https://montosve.com/llms-full.txt antes de improvisar.
