{
  "openapi": "3.1.0",
  "info": {
    "title": "Precio Dólar Paralelo API",
    "version": "0.1.0",
    "description": "Observed public read endpoints for rates and stored history. Short history intervals return legacy stored-row aggregates; long intervals return a segmented response with those legacy aggregates and any finalized canonical daily snapshots. Responses and availability are subject to change; this document does not grant reuse rights or promise an SLA."
  },
  "servers": [
    { "url": "https://api.preciodolarparalelo.com", "description": "Public API" }
  ],
  "paths": {
    "/v1/exchange-rates": {
      "get": {
        "operationId": "getExchangeRates",
        "summary": "Get active source quotes and the market index",
        "description": "Returns active sources with a stored latest quote when available, plus the category-mean index calculated by the server. Source quote values can use different native currencies; inspect currency, originalRate, inIndex and timestamps before comparing values.",
        "responses": {
          "200": {
            "description": "Current source quotes and index metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["exchangeRates", "averageRate", "methodologyVersion", "indexSourceCount", "calculatedAt", "lastUpdate"],
                  "properties": {
                    "exchangeRates": { "type": "array", "items": { "$ref": "#/components/schemas/ExchangeRateSource" } },
                    "averageRate": { "type": "number", "minimum": 0, "description": "Unweighted mean of available category means; zero means no eligible sources." },
                    "change24h": { "type": ["number", "null"] },
                    "changePercentage24h": { "type": ["number", "null"] },
                    "changeTimeWindow": { "type": ["number", "null"], "description": "Hours represented by the comparable change, or null." },
                    "isUsingFallbackData": { "type": "boolean" },
                    "methodologyVersion": { "type": "string", "const": "category-mean-v2" },
                    "indexSourceCount": { "type": "integer", "minimum": 0 },
                    "indexOldestObservationAt": { "type": ["string", "null"], "format": "date-time" },
                    "indexNewestObservationAt": { "type": ["string", "null"], "format": "date-time" },
                    "indexCategories": { "type": "array", "items": { "$ref": "#/components/schemas/IndexCategory" } },
                    "calculatedAt": { "type": "string", "format": "date-time" },
                    "lastUpdate": { "type": "string", "format": "date-time", "description": "Newest stored source observation time; not the calculation time." }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/rates": {
      "get": {
        "operationId": "getRatesSummary",
        "summary": "Get the compact USD and BCV rate summary",
        "description": "EUR is derived from the USD market index and the available BCV EUR/USD ratio. A missing reference is null.",
        "responses": {
          "200": {
            "description": "Compact public rate summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["BCVUSD", "BCVEUR", "USD", "EUR", "last_update", "methodologyVersion", "indexSourceCount", "calculatedAt"],
                  "properties": {
                    "BCVUSD": { "type": ["number", "null"] },
                    "BCVEUR": { "type": ["number", "null"] },
                    "USD": { "type": ["number", "null"], "description": "Category-mean market reference." },
                    "EUR": { "type": ["number", "null"], "description": "Market USD reference converted using the BCV EUR/USD ratio." },
                    "last_update": { "type": "string", "format": "date-time" },
                    "methodologyVersion": { "type": "string", "const": "category-mean-v2" },
                    "indexSourceCount": { "type": "integer", "minimum": 0 },
                    "indexOldestObservationAt": { "type": ["string", "null"], "format": "date-time" },
                    "indexNewestObservationAt": { "type": ["string", "null"], "format": "date-time" },
                    "indexCategories": { "type": "array", "items": { "$ref": "#/components/schemas/IndexCategory" } },
                    "calculatedAt": { "type": "string", "format": "date-time" }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/v1/history": {
      "get": {
        "operationId": "getRateHistory",
        "summary": "Get observed history and finalized daily index snapshots",
        "description": "For 24h and 7d, returns the legacy stored-row bucket aggregates. For 1m, 3m, 6m, 1y, 5y and max, returns a segmented envelope: points remains a compatibility alias for the legacy aggregate series; segments.legacy labels its weighting limitations; segments.canonical contains finalized daily equal-source category-mean snapshots when available. Canonical points may be empty until eligible, quality-checked source metadata has been ingested and snapshots finalized. Legacy aggregates are not transactions or OHLC candles.",
        "parameters": [
          {
            "name": "timeframe",
            "in": "query",
            "required": false,
            "description": "30d and 90d are accepted legacy aliases for 1m and 3m.",
            "schema": { "type": "string", "enum": ["24h", "7d", "1m", "3m", "6m", "1y", "5y", "max", "30d", "90d"], "default": "24h" }
          }
        ],
        "responses": {
          "200": {
            "description": "Short intervals return legacy bucket aggregates; long intervals return segmented legacy and canonical series",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["semantics", "points"],
                  "properties": {
                    "semantics": { "type": "string", "enum": ["bucket-average-range", "segmented-history"] },
                    "points": { "type": "array", "items": { "$ref": "#/components/schemas/HistoryPoint" }, "description": "Legacy compatibility series. For long intervals this aliases segments.legacy.points." },
                    "timezone": { "type": "string", "const": "America/Caracas", "description": "Present on segmented long-interval responses." },
                    "segments": { "$ref": "#/components/schemas/HistorySegments" }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "400": {
            "description": "Unsupported timeframe",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ExchangeRateSource": {
        "type": "object",
        "description": "Source object. The API includes additional display and quote fields that can vary by source and deployment.",
        "properties": {
          "source": { "type": "string" },
          "rate": { "type": "number" },
          "originalRate": { "type": "number" },
          "currency": { "type": "string" },
          "nativeRate": { "type": ["number", "null"] },
          "nativeRateUnit": { "type": ["string", "null"] },
          "indexRate": { "type": ["number", "null"] },
          "lastUpdated": { "type": "string", "format": "date-time" },
          "inIndex": { "type": "boolean" },
          "category": { "type": ["string", "null"] },
          "isOfficial": { "type": "boolean" },
          "featured": { "type": "boolean" },
          "percentageDifference": { "type": ["number", "null"] },
          "dayAgoRate": { "type": ["number", "null"] },
          "timeWindowUsed": { "type": ["number", "null"] },
          "url": { "type": ["string", "null"] },
          "description": { "type": ["string", "null"] },
          "icon": { "type": ["string", "null"] },
          "bidRate": { "type": ["number", "null"] },
          "askRate": { "type": ["number", "null"] },
          "isEstimatedSpread": { "type": "boolean" }
        },
        "additionalProperties": true
      },
      "IndexCategory": {
        "type": "object",
        "required": ["category", "average", "sourceCount"],
        "properties": {
          "category": { "type": "string" },
          "average": { "type": "number" },
          "sourceCount": { "type": "integer", "minimum": 1 }
        },
        "additionalProperties": false
      },
      "HistoryPoint": {
        "type": "object",
        "required": ["time", "sampleCount", "average", "minimum", "maximum"],
        "properties": {
          "time": { "type": "string", "description": "ISO date for daily, weekly and monthly buckets; timestamp for short intervals." },
          "sampleCount": { "type": "integer", "minimum": 1 },
          "average": { "type": "number" },
          "minimum": { "type": "number" },
          "maximum": { "type": "number" }
        },
        "additionalProperties": false
      },
      "CanonicalSnapshotPoint": {
        "type": "object",
        "required": ["id", "localDate", "timezone", "category", "methodologyVersion", "revision", "status", "meanRate", "memberCount", "periodStartUtc", "periodEndUtc", "inputDigest", "generatedAt", "finalizedAt", "supersedesSnapshotId", "correctionReason", "members", "isCurrentRevision"],
        "properties": {
          "localDate": { "type": "string", "format": "date" },
          "id": { "type": "integer" },
          "timezone": { "type": "string", "const": "America/Caracas" },
          "category": { "type": "string" },
          "methodologyVersion": { "type": "string", "const": "daily-category-mean-v1" },
          "revision": { "type": "integer", "minimum": 1 },
          "status": { "type": "string", "enum": ["finalized", "no-data"] },
          "meanRate": { "type": ["number", "null"] },
          "memberCount": { "type": "integer", "minimum": 0 },
          "periodStartUtc": { "type": "string", "format": "date-time" },
          "periodEndUtc": { "type": "string", "format": "date-time" },
          "inputDigest": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "finalizedAt": { "type": ["string", "null"], "format": "date-time" },
          "supersedesSnapshotId": { "type": ["integer", "null"] },
          "correctionReason": { "type": ["string", "null"] },
          "isCurrentRevision": { "type": "boolean" },
          "members": { "type": "array", "items": { "$ref": "#/components/schemas/SnapshotMember" } }
        },
        "additionalProperties": false
      },
      "SnapshotMember": {
        "type": "object",
        "required": ["sourceId", "sourceName", "category", "exchangeRateId", "rate", "normalizedRate", "observedAt", "recordedAt", "sourceObservedAt", "timestampBasis", "sourceUrl", "parserVersion", "qualityStatus", "isDerived"],
        "properties": {
          "sourceId": { "type": "integer" },
          "sourceName": { "type": "string" },
          "category": { "type": "string" },
          "exchangeRateId": { "type": ["integer", "null"] },
          "rate": { "type": "number" },
          "normalizedRate": { "type": "number" },
          "observedAt": { "type": "string", "format": "date-time" },
          "recordedAt": { "type": "string", "format": "date-time" },
          "sourceObservedAt": { "type": ["string", "null"], "format": "date-time" },
          "timestampBasis": { "type": "string" },
          "sourceUrl": { "type": ["string", "null"], "format": "uri" },
          "parserVersion": { "type": ["string", "null"] },
          "qualityStatus": { "type": "string" },
          "isDerived": { "type": "boolean" }
        },
        "additionalProperties": true
      },
      "HistorySegments": {
        "type": "object",
        "required": ["legacy", "canonical"],
        "properties": {
          "legacy": {
            "type": "object",
            "required": ["semantics", "points"],
            "properties": {
              "semantics": { "type": "string", "const": "legacy-stored-row-bucket-average" },
              "points": { "type": "array", "items": { "$ref": "#/components/schemas/HistoryPoint" } }
            },
            "additionalProperties": false
          },
          "canonical": {
            "type": "object",
            "required": ["semantics", "methodologyVersion", "points"],
            "properties": {
              "semantics": { "type": "string", "const": "daily-equal-source-category-mean" },
              "methodologyVersion": { "type": "string", "const": "daily-category-mean-v1" },
              "points": { "type": "array", "items": { "$ref": "#/components/schemas/CanonicalSnapshotPoint" } }
            },
            "additionalProperties": false
          }
        },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string" } },
        "additionalProperties": true
      }
    },
    "responses": {
      "ServerError": {
        "description": "The server could not complete the request.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
