{
  "name": "exchangerate.dev scheduled FX workbook refresh",
  "format": "flow recipe",
  "importable": false,
  "note": "Create these actions in a Scheduled cloud flow. Do not put a production key in a shared export; use a secret environment variable or approved secure connection reference and enable secure inputs/outputs where available.",
  "licensing": {
    "powerAutomateHttp": "Check the HTTP action's connector entitlement in the Power Automate designer. Microsoft licensing guidance says Free and Microsoft 365 plans access standard connectors, while premium connectors require the appropriate Premium or Process context; this recipe makes no free-plan promise.",
    "logicApps": "Azure Logic Apps is a separate service with its own hosting and billing model; do not use Logic Apps pricing to infer Power Automate licensing.",
    "verifyInTenant": "Connector policy, region, environment type, and tenant licensing can still restrict availability. Verify the HTTP and Excel Online (Business) cards in the Power Automate designer before deployment."
  },
  "officialGuidance": {
    "scheduledCloudFlow": "https://learn.microsoft.com/en-us/office/dev/scripts/tutorials/excel-power-automate-returns",
    "excelConnector": "https://learn.microsoft.com/en-us/connectors/excelonlinebusiness/",
    "licensingFaq": "https://learn.microsoft.com/en-us/power-platform/admin/power-automate-licensing/faqs"
  },
  "actions": [
    {
      "type": "Recurrence",
      "name": "Daily refresh",
      "frequency": "Day",
      "interval": 1,
      "schedule": "Choose the business-day time and timezone used by your workbook.",
      "concurrency": 1
    },
    {
      "type": "Http",
      "name": "Get latest rates",
      "method": "GET",
      "uri": "https://api.exchangerate.dev/v1/latest/USD?symbols=EUR,GBP,JPY",
      "headers": {
        "Authorization": "Bearer @{variables('EXCHANGERATE_API_KEY')}"
      },
      "retry": "Use the action's bounded retry policy for transient failures; do not retry 401 or 429 immediately.",
      "credentialSetup": "Resolve the approved secret before this action and provide it as EXCHANGERATE_API_KEY. Enable secure inputs/outputs on every action carrying the key; a connection reference alone does not create a bearer secret."
    },
    {
      "type": "ParseJson",
      "name": "Parse response",
      "content": "@body('Get_latest_rates')",
      "schema": {
        "type": "object",
        "required": [
          "result",
          "base",
          "rates",
          "sources",
          "market_session",
          "data_updated_at",
          "effective_at"
        ],
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "success"
            ]
          },
          "base": {
            "type": "string"
          },
          "rates": {
            "type": "object",
            "required": [
              "EUR",
              "GBP",
              "JPY"
            ],
            "properties": {
              "EUR": {
                "type": "number",
                "minimum": 0,
                "exclusiveMinimum": true
              },
              "GBP": {
                "type": "number",
                "minimum": 0,
                "exclusiveMinimum": true
              },
              "JPY": {
                "type": "number",
                "minimum": 0,
                "exclusiveMinimum": true
              }
            }
          },
          "sources": {
            "type": "object",
            "required": [
              "EUR",
              "GBP",
              "JPY"
            ],
            "properties": {
              "EUR": {
                "type": "string",
                "minLength": 1
              },
              "GBP": {
                "type": "string",
                "minLength": 1
              },
              "JPY": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          "market_session": {
            "type": "string"
          },
          "data_updated_at": {
            "type": "string"
          },
          "effective_at": {
            "type": "object",
            "required": [
              "EUR",
              "GBP",
              "JPY"
            ],
            "properties": {
              "EUR": {
                "type": "string",
                "minLength": 1
              },
              "GBP": {
                "type": "string",
                "minLength": 1
              },
              "JPY": {
                "type": "string",
                "minLength": 1
              }
            }
          }
        }
      }
    },
    {
      "type": "ExcelOnlineBusiness",
      "name": "Add a row into a table",
      "workbook": "Select an .xlsx file in OneDrive for Business or SharePoint.",
      "table": "FxRates",
      "mapping": {
        "CheckedAtUtc": "@utcNow()",
        "Base": "@body('Parse_response')?['base']",
        "EUR": "@body('Parse_response')?['rates']?['EUR']",
        "GBP": "@body('Parse_response')?['rates']?['GBP']",
        "JPY": "@body('Parse_response')?['rates']?['JPY']",
        "EURSource": "@body('Parse_response')?['sources']?['EUR']",
        "GBPSource": "@body('Parse_response')?['sources']?['GBP']",
        "JPYSource": "@body('Parse_response')?['sources']?['JPY']",
        "MarketSession": "@body('Parse_response')?['market_session']",
        "DataUpdatedAt": "@body('Parse_response')?['data_updated_at']",
        "EUREffectiveAt": "@body('Parse_response')?['effective_at']?['EUR']",
        "GBPEffectiveAt": "@body('Parse_response')?['effective_at']?['GBP']",
        "JPYEffectiveAt": "@body('Parse_response')?['effective_at']?['JPY']"
      },
      "runAfter": "Only after Get latest rates and Parse response succeed."
    }
  ],
  "workbookColumns": [
    "CheckedAtUtc",
    "Base",
    "EUR",
    "GBP",
    "JPY",
    "EURSource",
    "GBPSource",
    "JPYSource",
    "MarketSession",
    "DataUpdatedAt",
    "EUREffectiveAt",
    "GBPEffectiveAt",
    "JPYEffectiveAt"
  ],
  "failureHandling": [
    "Add a parallel failure branch that records the HTTP status and run link in an operations table.",
    "Do not write a row when Parse JSON fails; a missing rate must not look like a zero.",
    "Excel Online (Business) can delay committed changes and throttles concurrent workbook writes; keep one writer for this table."
  ]
}
