{
  "openapi": "3.1.0",
  "info": {
    "title": "Cambio Uruguay Public API",
    "version": "1.0.0",
    "summary": "Real-time and historical exchange-rate data for Uruguay.",
    "description": "Free, read-only, public REST API behind https://cambio-uruguay.com.\n\nIt aggregates buy/sell quotes from 38+ Uruguayan exchange houses and banks (BROU, BCU and private *cambios*), refreshed about every 10 minutes, plus branch locations, historical series and AI market insights.\n\nNo API key or authentication is required. Please be considerate with request volume and cache responses where you can.\n\n- Source code (open source): https://github.com/eduair94/cambio-uruguay\n- MCP server: https://mcp.cambio-uruguay.com/mcp\n- This document: https://cambio-uruguay.com/openapi.json",
    "contact": {
      "name": "Eduardo Airaudo",
      "url": "https://github.com/eduair94/cambio-uruguay"
    },
    "license": {
      "name": "Open source — see repository",
      "url": "https://github.com/eduair94/cambio-uruguay"
    }
  },
  "servers": [
    { "url": "https://api.cambio-uruguay.com", "description": "Production" }
  ],
  "externalDocs": {
    "description": "GitHub repository",
    "url": "https://github.com/eduair94/cambio-uruguay"
  },
  "tags": [
    { "name": "Rates", "description": "Current buy/sell quotes across every house." },
    { "name": "History", "description": "Historical rate series with statistics." },
    { "name": "BCU", "description": "Banco Central del Uruguay official references." },
    { "name": "Houses & Branches", "description": "Exchange-house metadata and physical branches." },
    { "name": "Parameters", "description": "Allowed values (enums) for path/query parameters." },
    { "name": "AI", "description": "AI-generated market and currency analysis." },
    { "name": "System", "description": "Health and liveness probes." }
  ],
  "paths": {
    "/": {
      "get": {
        "tags": ["Rates"],
        "summary": "All current quotes",
        "operationId": "getRates",
        "description": "Every house's latest buy/sell quote for every currency and quote type. This is the single largest response and the main entry point.",
        "responses": {
          "200": {
            "description": "Array of rate rows.",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/RateRow" } },
                "example": [
                  { "origin": "brou", "code": "USD", "type": "EBROU", "date": "2026-06-22T03:00:00.000Z", "buy": 39.15, "name": "Dólar eBROU", "sell": 40.55 },
                  { "origin": "bcu", "code": "USD", "type": "BILLETE", "date": "2026-06-22T03:00:00.000Z", "buy": 39.908, "name": "DLS. USA BILLETE", "sell": 39.908 }
                ]
              }
            }
          }
        }
      }
    },
    "/exchange/{origin}/{code}": {
      "get": {
        "tags": ["Rates"],
        "summary": "Quotes for one house",
        "operationId": "getExchange",
        "description": "Current quotes for a single house, optionally filtered to one currency. Omitting `{code}` (calling `/exchange/{origin}`) returns every currency that house quotes.",
        "parameters": [
          { "$ref": "#/components/parameters/Origin" },
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Currency code. Use `/exchange/{origin}` without this segment for all currencies.",
            "schema": { "$ref": "#/components/schemas/CurrencyCode" }
          }
        ],
        "responses": {
          "200": {
            "description": "Array of rate rows for the house (and currency, if given).",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/RateRow" } },
                "example": [
                  { "origin": "brou", "code": "USD", "type": "", "date": "2026-06-22T03:00:00.000Z", "buy": 38.65, "name": "Dólar", "sell": 41.05 },
                  { "origin": "brou", "code": "USD", "type": "EBROU", "date": "2026-06-22T03:00:00.000Z", "buy": 39.15, "name": "Dólar eBROU", "sell": 40.55 }
                ]
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/evolution/{origin}/{code}": {
      "get": {
        "tags": ["History"],
        "summary": "Historical series + statistics",
        "operationId": "getEvolution",
        "description": "Historical buy/sell series for a house + currency over the last `period` months, with summary statistics (min/max/avg/current/change). An optional trailing `/{type}` segment (e.g. `/evolution/brou/USD/EBROU`) filters by quote type.",
        "parameters": [
          { "$ref": "#/components/parameters/Origin" },
          {
            "name": "code",
            "in": "path",
            "required": true,
            "description": "Currency code.",
            "schema": { "$ref": "#/components/schemas/CurrencyCode" }
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "Look-back window in months.",
            "schema": { "type": "integer", "minimum": 1, "default": 6 }
          }
        ],
        "responses": {
          "200": {
            "description": "Series with statistics.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Evolution" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/bcu": {
      "get": {
        "tags": ["BCU"],
        "summary": "BCU institution links",
        "operationId": "getBcu",
        "description": "Map of house id → its profile URL on the Banco Central del Uruguay (BCU) site.",
        "responses": {
          "200": {
            "description": "Object keyed by house id.",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": { "type": "string", "format": "uri" } },
                "example": { "brou": "https://www.bcu.gub.uy/Servicios-Financieros-SSF/Paginas/InformacionInstitucion.aspx?nroinst=2370" }
              }
            }
          }
        }
      }
    },
    "/bcu/{origin}": {
      "get": {
        "tags": ["BCU"],
        "summary": "BCU institution details",
        "operationId": "getBcuByOrigin",
        "description": "Registered BCU details for one institution (legal name, address, departments served).",
        "parameters": [{ "$ref": "#/components/parameters/Origin" }],
        "responses": {
          "200": {
            "description": "Institution details.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BcuInstitution" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/ValidationError" }
        }
      }
    },
    "/localData": {
      "get": {
        "tags": ["Houses & Branches"],
        "summary": "House metadata",
        "operationId": "getLocalData",
        "description": "Metadata for every exchange house, keyed by house id: display name, website, Google Maps search link, BCU profile and the departments it operates in.",
        "responses": {
          "200": {
            "description": "Object keyed by house id.",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": { "$ref": "#/components/schemas/HouseInfo" } },
                "example": {
                  "cambio_aguerrebere": {
                    "name": "Cambio Aguerrebere",
                    "website": "https://cambioaguerrebere.com/",
                    "maps": "https://www.google.com.uy/maps/search/cambio%20aguerrebere",
                    "bcu": "https://www.bcu.gub.uy/Servicios-Financieros-SSF/Paginas/InformacionInstitucion.aspx?nroinst=2370",
                    "departments": ["LAVALLEJA", "DURAZNO", "COLONIA", "CERRO LARGO", "MONTEVIDEO"]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/locations": {
      "get": {
        "tags": ["Houses & Branches"],
        "summary": "Branch locations",
        "operationId": "getLocations",
        "description": "Flat list of physical branches with geo-coordinates, address, phone and opening hours. Useful for maps and \"nearest branch\" features.",
        "responses": {
          "200": {
            "description": "Array of branches.",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Location" } },
                "example": [
                  {
                    "origin": "cambio_principal",
                    "id": "2456-1",
                    "name": "Casa Central",
                    "dept": "RIVERA",
                    "locality": "Montevideo",
                    "address": "Treinta Y Tres Orientales 1146 - Rivera",
                    "phone": "2622 4521",
                    "hours": "Lunes a Viernes 8:00 A 17:30",
                    "lat": -30.896395,
                    "lng": -55.536394,
                    "mapUrl": "https://goo.gl/maps/htnV1mZUt6yp16aT9"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/parameters/origins": {
      "get": {
        "tags": ["Parameters"],
        "summary": "Valid house ids",
        "operationId": "getParamOrigins",
        "responses": {
          "200": {
            "description": "All valid `origin` values.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "origins": { "type": "array", "items": { "type": "string" } },
                    "count": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/parameters/currencies": {
      "get": {
        "tags": ["Parameters"],
        "summary": "Valid currency codes",
        "operationId": "getParamCurrencies",
        "responses": {
          "200": {
            "description": "All valid `code` values.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "currencies": { "type": "array", "items": { "type": "string" } },
                    "count": { "type": "integer" }
                  }
                },
                "example": { "currencies": ["ARS", "AUD", "BRL", "CAD", "CHF", "CLP", "COP", "EUR", "GBP", "JPY", "MXN", "PEN", "PYG", "UI", "UP", "UR", "USD", "XAU"], "count": 18 }
              }
            }
          }
        }
      }
    },
    "/parameters/types": {
      "get": {
        "tags": ["Parameters"],
        "summary": "Valid quote types",
        "operationId": "getParamTypes",
        "responses": {
          "200": {
            "description": "All valid `type` values.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "types": { "type": "array", "items": { "type": "string" } },
                    "count": { "type": "integer" }
                  }
                },
                "example": { "types": ["BILLETE", "CABLE", "EBROU", "INTERBANCARIO", "PROMED.FONDO"], "count": 5 }
              }
            }
          }
        }
      }
    },
    "/parameters/locations": {
      "get": {
        "tags": ["Parameters"],
        "summary": "Valid departments",
        "operationId": "getParamLocations",
        "responses": {
          "200": {
            "description": "All Uruguayan departments present in the data.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "locations": { "type": "array", "items": { "type": "string" } },
                    "count": { "type": "integer" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/parameters/all": {
      "get": {
        "tags": ["Parameters"],
        "summary": "All enums at once",
        "operationId": "getParamAll",
        "description": "Convenience endpoint returning origins, currencies, types and locations in a single response.",
        "responses": {
          "200": {
            "description": "Combined enums.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "origins": { "type": "array", "items": { "type": "string" } },
                    "currencies": { "type": "array", "items": { "type": "string" } },
                    "types": { "type": "array", "items": { "type": "string" } },
                    "locations": { "type": "array", "items": { "type": "string" } }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/ai/insights": {
      "post": {
        "tags": ["AI"],
        "summary": "Generate an AI market insight",
        "operationId": "postAiInsight",
        "description": "Returns a short, AI-generated natural-language analysis of the market or a currency. Responses are cached (~10 min) by request, so repeated identical calls return `cached: true` quickly.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/InsightRequest" },
              "example": { "type": "market_summary", "lang": "es" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The generated insight.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/InsightResult" }
              }
            }
          },
          "503": { "description": "AI not configured on this server." }
        }
      }
    },
    "/ai/status": {
      "get": {
        "tags": ["AI"],
        "summary": "AI feature status",
        "operationId": "getAiStatus",
        "description": "Whether AI insights are configured, the model in use, and the supported insight types and languages.",
        "responses": {
          "200": {
            "description": "AI configuration snapshot.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AiStatus" },
                "example": { "configured": true, "model": "wormv5.1", "availableTypes": ["market_summary", "currency_analysis", "best_rates", "trend_analysis", "custom"], "supportedLanguages": ["es", "en", "pt"], "cacheEnabled": true, "cacheTTL": "10 minutes" }
              }
            }
          }
        }
      }
    },
    "/ping": {
      "get": {
        "tags": ["System"],
        "summary": "Liveness probe",
        "operationId": "getPing",
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "expected": { "type": "boolean" },
                    "total": { "type": "integer", "description": "Number of rate rows currently served." }
                  }
                },
                "example": { "expected": true, "total": 180 }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": ["System"],
        "summary": "Detailed health check",
        "operationId": "getHealth",
        "description": "Database, cache and last-sync diagnostics. Intended for monitoring.",
        "responses": {
          "200": {
            "description": "Health snapshot.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Health" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "Origin": {
        "name": "origin",
        "in": "path",
        "required": true,
        "description": "House id. See `GET /parameters/origins` for the full list.",
        "schema": { "type": "string", "example": "brou" }
      }
    },
    "responses": {
      "ValidationError": {
        "description": "Invalid path/query parameter.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": { "type": "string" },
                "parameter": { "type": "string" },
                "value": { "type": "string" },
                "validValues": { "type": "array", "items": { "type": "string" } },
                "suggestion": { "type": "string" }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "CurrencyCode": {
        "type": "string",
        "description": "ISO-like currency code. `XAU` = gold (Troy ounce); `UI`/`UR`/`UP` = Uruguayan indexed units.",
        "enum": ["ARS", "AUD", "BRL", "CAD", "CHF", "CLP", "COP", "EUR", "GBP", "JPY", "MXN", "PEN", "PYG", "UI", "UP", "UR", "USD", "XAU"]
      },
      "QuoteType": {
        "type": "string",
        "description": "Quote channel. Empty string for houses that publish a single rate.",
        "enum": ["", "BILLETE", "CABLE", "EBROU", "INTERBANCARIO", "PROMED.FONDO"]
      },
      "RateRow": {
        "type": "object",
        "description": "One house's buy/sell quote for one currency and quote type.",
        "properties": {
          "origin": { "type": "string", "description": "House id.", "example": "brou" },
          "code": { "$ref": "#/components/schemas/CurrencyCode" },
          "type": { "$ref": "#/components/schemas/QuoteType" },
          "date": { "type": "string", "format": "date-time", "description": "Quote timestamp (ISO 8601)." },
          "buy": { "type": "number", "description": "Buy price (UYU per unit of currency)." },
          "sell": { "type": "number", "description": "Sell price (UYU per unit of currency)." },
          "name": { "type": "string", "description": "Human label for the quote (may be empty)." }
        },
        "required": ["origin", "code", "buy", "sell", "date"]
      },
      "HouseInfo": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "website": { "type": "string", "format": "uri" },
          "maps": { "type": "string", "format": "uri" },
          "bcu": { "type": "string", "format": "uri" },
          "departments": { "type": "array", "items": { "type": "string" } }
        },
        "required": ["name"]
      },
      "Location": {
        "type": "object",
        "properties": {
          "origin": { "type": "string" },
          "id": { "type": "string" },
          "name": { "type": "string" },
          "dept": { "type": "string", "description": "Uruguayan department." },
          "locality": { "type": "string" },
          "address": { "type": "string" },
          "phone": { "type": "string" },
          "hours": { "type": "string" },
          "lat": { "type": "number" },
          "lng": { "type": "number" },
          "mapUrl": { "type": "string", "format": "uri" }
        },
        "required": ["origin", "id", "lat", "lng"]
      },
      "BcuInstitution": {
        "type": "object",
        "properties": {
          "origin": { "type": "string" },
          "social_reason": { "type": "string", "description": "Registered legal name." },
          "name": { "type": "string" },
          "address": { "type": "string" },
          "phone": { "type": "string" },
          "email": { "type": "string" },
          "website": { "type": "string" },
          "departments": { "type": "array", "items": { "type": "string" } }
        }
      },
      "Evolution": {
        "type": "object",
        "properties": {
          "origin": { "type": "string" },
          "code": { "$ref": "#/components/schemas/CurrencyCode" },
          "type": { "type": ["string", "null"] },
          "statistics": {
            "type": "object",
            "properties": {
              "totalDataPoints": { "type": "integer" },
              "dateRange": {
                "type": "object",
                "properties": {
                  "start": { "type": "string", "format": "date-time" },
                  "end": { "type": "string", "format": "date-time" },
                  "periodMonths": { "type": "integer" }
                }
              },
              "buy": { "$ref": "#/components/schemas/SeriesStats" },
              "sell": { "$ref": "#/components/schemas/SeriesStats" }
            }
          },
          "evolution": { "type": "array", "items": { "$ref": "#/components/schemas/RateRow" } }
        }
      },
      "SeriesStats": {
        "type": "object",
        "properties": {
          "min": { "type": "number" },
          "max": { "type": "number" },
          "avg": { "type": "number" },
          "current": { "type": "number" },
          "change": { "type": "number", "description": "Percent change over the period." }
        }
      },
      "InsightRequest": {
        "type": "object",
        "required": ["type"],
        "properties": {
          "type": {
            "type": "string",
            "enum": ["market_summary", "currency_analysis", "best_rates", "trend_analysis", "custom"]
          },
          "currency": { "$ref": "#/components/schemas/CurrencyCode" },
          "lang": { "type": "string", "enum": ["es", "en", "pt"], "default": "es" }
        }
      },
      "InsightResult": {
        "type": "object",
        "properties": {
          "insight": { "type": "string" },
          "type": { "type": "string" },
          "cached": { "type": "boolean" },
          "truncated": { "type": "boolean" }
        }
      },
      "AiStatus": {
        "type": "object",
        "properties": {
          "configured": { "type": "boolean" },
          "model": { "type": "string" },
          "availableTypes": { "type": "array", "items": { "type": "string" } },
          "supportedLanguages": { "type": "array", "items": { "type": "string" } },
          "cacheEnabled": { "type": "boolean" },
          "cacheTTL": { "type": "string" }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "example": "ok" },
          "timestamp": { "type": "string", "format": "date-time" },
          "uptime": { "type": "number", "description": "Seconds since process start." },
          "database": {
            "type": "object",
            "properties": {
              "connected": { "type": "boolean" },
              "readyState": { "type": "integer" },
              "readyStateText": { "type": "string" }
            }
          },
          "cache": { "type": "object", "additionalProperties": true },
          "sync": { "type": "object", "additionalProperties": true }
        }
      }
    }
  }
}
