{"componentChunkName":"component---src-templates-docs-js","path":"/guides/monitoring-as-code","result":{"data":{"site":{"siteMetadata":{"title":"Documentation | LoadFocus","docsLocation":""}},"mdx":{"fields":{"id":"1473931b-5209-5cf8-984b-a96c51ce7155","title":"Monitoring as Code","slug":"/guides/monitoring-as-code","locale":"en-GB"},"body":"var _excluded = [\"components\"];\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\nfunction _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }\nfunction _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }\n/* @jsxRuntime classic */\n/* @jsx mdx */\n\nvar _frontmatter = {\n  \"title\": \"Monitoring as Code\",\n  \"metaTitle\": \"Monitoring as Code: Manage LoadFocus monitors with a CLI | Guides | LoadFocus\",\n  \"metaDescription\": \"Define your LoadFocus API, browser, multistep, TCP and heartbeat checks, groups, alerts, maintenance windows, dashboards and status pages as version-controlled files, then deploy them from the command line or CI with the @loadfocus/monitoring CLI.\",\n  \"order\": 9\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n    props = _objectWithoutProperties(_ref, _excluded);\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"h1\", null, \"Monitoring as Code\"), mdx(\"p\", null, \"Monitoring as Code lets you define your LoadFocus monitoring setup as version-controlled files and apply it from the command line or CI \\u2014 the same way you manage infrastructure with Terraform or Pulumi. You describe the monitors, groups, alerts, maintenance windows, dashboards and status pages you want; the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"@loadfocus/monitoring\"), \" CLI computes the difference against what is live and reconciles it (create, update, delete).\"), mdx(\"p\", null, \"It is declarative and idempotent: running \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"deploy\"), \" twice changes nothing the second time. Your files are the source of truth, so changes go through pull requests and your monitoring history lives in git.\"), mdx(\"p\", null, \"Everything runs inside your account and your active team, with your plan limits enforced by the LoadFocus backend exactly as in the dashboard. The CLI only does what you could do yourself in the UI.\"), mdx(\"h2\", null, \"How it works\"), mdx(\"p\", null, \"You keep a folder of small YAML (or JavaScript) files \\u2014 one resource per file \\u2014 plus a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loadfocus.config.yaml\"), \" that points at them. The CLI sends those definitions to LoadFocus, which maps them to live resources, diffs them, and returns a plan. You review the plan, then apply it.\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Author\"), \" \\u2014 describe resources as files (YAML or JS constructs).\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Plan\"), \" \\u2014 \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"deploy --dry-run\"), \" shows exactly what will be created, updated, adopted or deleted.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Apply\"), \" \\u2014 \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"deploy\"), \" reconciles your account to match the files.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Reconcile identity\"), \" \\u2014 every resource carries a stable \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"logicalId\"), \" you choose. That is how the CLI tracks a resource across renames, so changing a check's display name never recreates it.\")), mdx(\"h2\", null, \"Install\"), mdx(\"p\", null, \"The CLI is a Node package (Node 18+). Run it on demand with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"npx\"), \":\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"npx @loadfocus/monitoring --help\\n\")), mdx(\"p\", null, \"\\u2026or install it globally to get the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loadfocus-monitoring\"), \" command:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"npm install -g @loadfocus/monitoring\\nloadfocus-monitoring --help\\n\")), mdx(\"h2\", null, \"Authenticate\"), mdx(\"p\", null, \"The CLI authenticates with a LoadFocus API key and a team id. Find your API key in the dashboard under your account/API settings, and your team id on the teams page.\"), mdx(\"p\", null, \"Sign in once and the credentials are saved to \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"~/.loadfocus/config.json\"), \":\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"loadfocus-monitoring login\\nloadfocus-monitoring whoami        # confirm who you are and which team you're targeting\\n\")), mdx(\"p\", null, \"For CI, prefer environment variables (they override the saved config and never touch disk):\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"export LOADFOCUS_API_KEY=\\\"apikey_xxxxxxxx\\\"\\nexport TEAM_ID=\\\"team_xxxxxxxx\\\"\\n# optional: export API_URL=\\\"https://apimonitor.loadfocus.com\\\"\\n\")), mdx(\"h2\", null, \"Create a project\"), mdx(\"p\", null, \"Scaffold a config file and a sample monitor in your repository:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"loadfocus-monitoring init\\n\")), mdx(\"p\", null, \"This writes \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loadfocus.config.yaml\"), \":\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"project: my-project          # a namespace for this set of resources\\ncheckMatch:\\n  - \\\"monitors/**/*.{check,group,alertRule,maintenanceWindow,dashboard,statusPage,alertChannel,variable}.{yaml,yml,js}\\\"\\ndefaults:\\n  schedule: \\\"300\\\"            # applied to checks that omit a schedule\\n  locations: [us-east-1]\\n\")), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"project\")), \" scopes everything the CLI manages. Resources in a project are reconciled together; anything in the project that is no longer in your files is deleted on \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"deploy\"), \". Use separate projects to manage independent sets of monitors.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"checkMatch\")), \" is the glob(s) for your authoring files.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"defaults\")), \" fill in \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"schedule\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"locations\"), \" and \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"alertChannels\"), \" for checks that omit them.\")), mdx(\"h2\", null, \"The workflow\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"loadfocus-monitoring validate          # compile locally + server-side dry-run; great as a PR gate\\nloadfocus-monitoring deploy --dry-run  # show the plan (created / updated / adopted / deleted)\\nloadfocus-monitoring deploy            # apply it\\nloadfocus-monitoring list              # inventory of what's deployed in the project\\nloadfocus-monitoring list --status     # \\u2026with each check's latest up/down/degraded status\\nloadfocus-monitoring get <logicalId>   # show one deployed resource\\nloadfocus-monitoring trigger <logicalId>   # run a check now\\nloadfocus-monitoring destroy           # delete everything managed in the project\\n\")), mdx(\"p\", null, mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"deploy\"), \" is safe by default: it shows the plan and, when run interactively, asks before deleting anything. In CI (non-interactive), it refuses to delete without \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"--yes\"), \" and exits with a clear code instead of hanging on a prompt. Add \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"--json\"), \" to read/result commands for machine-readable output.\"), mdx(\"h2\", null, \"Adopt existing monitors\"), mdx(\"p\", null, \"Already have monitors in the dashboard? Pull them into files instead of recreating them:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"loadfocus-monitoring import --project my-project --out monitors\\n\")), mdx(\"p\", null, \"This writes one file per resource and a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"loadfocus.config.yaml\"), \". Review, commit, then run \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"deploy --dry-run\"), \" \\u2014 matching resources are \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"adopted\"), \" in place (brought under management) rather than duplicated.\"), mdx(\"h2\", null, \"Resources\"), mdx(\"p\", null, \"Every resource is one file with a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"kind\"), \", a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"logicalId\"), \" (your stable identifier), and the fields for that kind. References between resources use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"logicalId\"), \"s (or names for alert channels) \\u2014 the server resolves them, and deploy order is handled for you.\"), mdx(\"h3\", null, \"Checks\"), mdx(\"p\", null, \"One \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Monitor\"), \" kind covers every check type via \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"type\"), \": \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"api\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"browser\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"multistep\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"tcp\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"heartbeat\"), \".\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: check\\ntype: api\\nlogicalId: home\\nname: Home API\\nschedule: \\\"300\\\"            # seconds between runs\\nlocations: [us-east-1, eu-west-1]\\nrequest:\\n  url: \\\"https://example.com/health\\\"\\n  method: GET\\nassertions:\\n  - { type: statusCode, comparison: equals, value: 200 }\\n  - { type: responseTime, comparison: lessThan, value: 1000 }\\n\")), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"api\"), \" \\u2014 HTTP request with assertions on status, body, headers, response time, SSL expiry.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"browser\"), \" \\u2014 a Playwright user-flow script with screenshots and per-step timings (paid).\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"multistep\"), \" \\u2014 an ordered sequence of API requests passing data between steps.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"tcp\"), \" \\u2014 a port/reachability check from multiple regions.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"heartbeat\"), \" \\u2014 a dead-man's switch: an external job pings a URL on a schedule, and LoadFocus alerts if a ping is missed.\")), mdx(\"h3\", null, \"Groups\"), mdx(\"p\", null, \"Share locations, alert channels, frequency and activation across many checks. A check joins a group with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"group: <logicalId>\"), \".\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: group\\nlogicalId: web\\nname: Web services\\nlocations: [us-east-1, eu-west-1]\\n\")), mdx(\"h3\", null, \"Alert rules\"), mdx(\"p\", null, \"Alert when a check's metric crosses a threshold.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: alertRule\\nlogicalId: home-latency\\nname: Home API latency\\ncheck: home               # reference a check by logicalId\\nmetric: responseTime      # responseTime | statusCode | duration\\ncondition: above\\nconditionValue: 1500      # milliseconds\\n\")), mdx(\"h3\", null, \"Alert channels\"), mdx(\"p\", null, \"Manage notification channels as code and reference them \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"by name\"), \" from a check, group or alert rule. Supported \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"type\"), \"s: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"email\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"slack\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"microsoftteams\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"webhook\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"discord\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"pagerduty\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"opsgenie\"), \". Secret fields (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"webhookUrl\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"routingKey\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"apiKey\"), \") take a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"{{secrets.NAME}}\"), \" reference \\u2014 the value is stored with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"env set-secret\"), \" and resolved when an alert is sent, never committed to your files.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: alertChannel\\nlogicalId: oncall          # the name checks / groups / alert rules reference\\ntype: pagerduty\\nroutingKey: \\\"{{secrets.PAGERDUTY_KEY}}\\\"\\n\")), mdx(\"h3\", null, \"Maintenance windows\"), mdx(\"p\", null, \"Suppress alerts during planned work. Times are UTC. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"startsAt\"), \" / \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"endsAt\"), \" accept an ISO-8601 string (e.g. \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"\\\"2026-07-01T00:00:00Z\\\"\"), \") or unix milliseconds.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: maintenanceWindow\\nlogicalId: weekly-deploy\\nname: Weekly deploy window\\nenabled: true\\nstartsAt: \\\"2026-07-01T00:00:00Z\\\"   # ISO-8601 or unix ms\\nendsAt: \\\"2026-07-01T02:00:00Z\\\"\\nrepeat: weekly            # none | daily | weekly | monthly\\nweekdays: [2]             # 0=Sun \\u2026 6=Sat\\ntargets:\\n  allChecks: false\\n  checkIds: [home]        # by logicalId\\n\")), mdx(\"h3\", null, \"Dashboards\"), mdx(\"p\", null, \"A shared view of selected checks, optionally public via a slug.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: dashboard\\nlogicalId: status-overview\\nname: Status overview\\nvisibility: private       # private | public\\nchecks: [home]            # by logicalId\\nwindow: 24h               # 24h | 7d | 30d\\n\")), mdx(\"h3\", null, \"Status pages\"), mdx(\"p\", null, \"A public status page at \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"<slug>.loadfoc.us\"), \", optionally on your own custom domain.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: statusPage\\nlogicalId: public-status\\ntitle: Acme Status\\nslug: acme                # -> acme.loadfoc.us (globally unique)\\nenabled: true\\ncustomDomain: status.acme.com   # optional, paid; point a CNAME at cname.loadfoc.us\\ngroups:\\n  - { id: core, name: Core Services, order: 0 }\\ncomponents:\\n  - id: api\\n    name: API\\n    groupId: core\\n    monitors: [home]      # checks shown on this component, by logicalId\\nbranding:\\n  brandColor: \\\"#5353a4\\\"\\n  colorTheme: dark\\n\")), mdx(\"p\", null, \"A custom domain goes live once you create the CNAME and the certificate is issued \\u2014 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"deploy\"), \" declares it; verification happens out of band.\"), mdx(\"h3\", null, \"Variables\"), mdx(\"p\", null, \"Non-secret values (base URLs, IDs) that checks reference at run time as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"{{vars.NAME}}\"), \". The \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"logicalId\"), \" is the variable key. (For secrets, use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"env set-secret\"), \" \\u2014 never put them in files.)\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"kind: variable\\nlogicalId: BASE_URL\\nvalue: \\\"https://api.example.com\\\"\\n\")), mdx(\"h2\", null, \"Authoring in JavaScript or TypeScript\"), mdx(\"p\", null, \"If you prefer code over YAML, build the same definitions programmatically and export them \\u2014 the constructs produce identical resources:\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-js\"\n  }, \"const { Monitor, Group, AlertRule, Maintenance, Dashboard, StatusPage, AlertChannel, Variable } = require('@loadfocus/monitoring');\\n\\nnew Monitor({\\n  type: 'api', logicalId: 'home', name: 'Home API', schedule: '300',\\n  locations: ['us-east-1'],\\n  request: { url: 'https://example.com/health', method: 'GET' },\\n  assertions: [{ type: 'statusCode', comparison: 'equals', value: 200 }],\\n});\\n\\nnew Group({ logicalId: 'web', name: 'Web services', locations: ['us-east-1'] });\\n\")), mdx(\"p\", null, \"Point \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"checkMatch\"), \" at your \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \".js\"), \" files and the CLI loads them like any other resource.\"), mdx(\"h2\", null, \"Secrets and variables\"), mdx(\"p\", null, \"Reference values from your checks without committing them. \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Secrets\"), \" (tokens, passwords) are managed imperatively only and referenced as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"{{secrets.NAME}}\"), \" in check fields and alert-channel secret fields. \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Variables\"), \" (non-secret) can be declared as files (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"kind: variable\"), \", above) or set imperatively, and are referenced as \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"{{vars.NAME}}\"), \".\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-bash\"\n  }, \"loadfocus-monitoring env set-secret API_TOKEN \\\"s3cr3t\\\"\\nloadfocus-monitoring env set-variable BASE_URL \\\"https://example.com\\\"\\nloadfocus-monitoring env ls            # list secret + variable keys (values never shown)\\n\")), mdx(\"h2\", null, \"Run it in CI\"), mdx(\"p\", null, \"A typical pipeline validates on every pull request and deploys on merge to the main branch.\"), mdx(\"pre\", null, mdx(\"code\", {\n    parentName: \"pre\",\n    \"className\": \"language-yaml\"\n  }, \"# .github/workflows/monitoring.yml\\nname: monitoring\\non:\\n  pull_request:\\n  push:\\n    branches: [main]\\njobs:\\n  monitoring:\\n    runs-on: ubuntu-latest\\n    env:\\n      LOADFOCUS_API_KEY: ${{ secrets.LOADFOCUS_API_KEY }}\\n      TEAM_ID: ${{ secrets.LOADFOCUS_TEAM_ID }}\\n    steps:\\n      - uses: actions/checkout@v4\\n      - uses: actions/setup-node@v4\\n        with: { node-version: 20 }\\n      - run: npx @loadfocus/monitoring validate\\n      - if: github.ref == 'refs/heads/main'\\n        run: npx @loadfocus/monitoring deploy --yes\\n\")), mdx(\"h2\", null, \"Things worth knowing\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, mdx(\"inlineCode\", {\n    parentName: \"strong\"\n  }, \"logicalId\"), \" is the identity.\"), \" Keep it stable. You can rename a check's \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"name\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"title\"), \" freely; changing its \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"logicalId\"), \" is treated as deleting one resource and creating another.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Deletes are scoped to the project.\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"deploy\"), \" only removes resources in the current \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"project\"), \" that are no longer in your files \\u2014 never anything in another project or created outside Monitoring as Code (until you adopt it).\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Status-page slugs are global.\"), \" \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"slug\"), \" becomes a subdomain, so it must be unique across all LoadFocus customers.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Paid features fail loudly.\"), \" A free team that declares a paid-only field (a status-page custom domain, removing the \\\"Powered by\\\" badge) gets a clear error on \", mdx(\"inlineCode\", {\n    parentName: \"li\"\n  }, \"deploy\"), \" rather than a silent partial result.\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"Plan limits apply.\"), \" Creating resources via the CLI is subject to the same plan quotas as the dashboard.\")), mdx(\"h2\", null, \"Related\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/docs/guides/api-monitoring\"\n  }, \"API monitoring\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/docs/guides/mcp\"\n  }, \"Run LoadFocus from your AI assistant (MCP)\")), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"a\", {\n    parentName: \"li\",\n    \"href\": \"/docs/guides/teams\"\n  }, \"Teams\"))));\n}\n;\nMDXContent.isMDXComponent = true;","tableOfContents":{"items":[{"url":"#monitoring-as-code","title":"Monitoring as Code","items":[{"url":"#how-it-works","title":"How it works"},{"url":"#install","title":"Install"},{"url":"#authenticate","title":"Authenticate"},{"url":"#create-a-project","title":"Create a project"},{"url":"#the-workflow","title":"The workflow"},{"url":"#adopt-existing-monitors","title":"Adopt existing monitors"},{"url":"#resources","title":"Resources","items":[{"url":"#checks","title":"Checks"},{"url":"#groups","title":"Groups"},{"url":"#alert-rules","title":"Alert rules"},{"url":"#alert-channels","title":"Alert channels"},{"url":"#maintenance-windows","title":"Maintenance windows"},{"url":"#dashboards","title":"Dashboards"},{"url":"#status-pages","title":"Status pages"},{"url":"#variables","title":"Variables"}]},{"url":"#authoring-in-javascript-or-typescript","title":"Authoring in JavaScript or TypeScript"},{"url":"#secrets-and-variables","title":"Secrets and variables"},{"url":"#run-it-in-ci","title":"Run it in CI"},{"url":"#things-worth-knowing","title":"Things worth knowing"},{"url":"#related","title":"Related"}]}]},"parent":{"__typename":"File","relativePath":"en/guides/monitoring-as-code.md"},"frontmatter":{"metaTitle":"Monitoring as Code: Manage LoadFocus monitors with a CLI | Guides | LoadFocus","metaDescription":"Define your LoadFocus API, browser, multistep, TCP and heartbeat checks, groups, alerts, maintenance windows, dashboards and status pages as version-controlled files, then deploy them from the command line or CI with the @loadfocus/monitoring CLI.","order":9}},"allMdx":{"edges":[{"node":{"fields":{"slug":"/","title":"Welcome to the New LoadFocus Documentation"}}},{"node":{"fields":{"slug":"/pricing","title":"Pricing FAQs"}}},{"node":{"fields":{"slug":"/guides","title":"How-To Guides"}}},{"node":{"fields":{"slug":"/knowledge-base","title":"Knowledge Base"}}},{"node":{"fields":{"slug":"/knowledge-base/how-to-update-card-details","title":"How to Update Card Details for Failed Payments"}}},{"node":{"fields":{"slug":"/knowledge-base/understanding-declined-card-payments","title":"Why has my card payment been declined?"}}},{"node":{"fields":{"slug":"/guides/api-monitoring","title":"API Monitoring"}}},{"node":{"fields":{"slug":"/knowledge-base/understanding-differences-between-http-and-https","title":"Differences between HTTP and HTTPS"}}},{"node":{"fields":{"slug":"/knowledge-base/using-3rd-party-tools-to-load-test-website-locally","title":"How to load testing locally hosted website or API"}}},{"node":{"fields":{"slug":"/knowledge-base/using-google-analytics-and-no-requests-while-load-testing","title":"No traffic in Google Analytics while load testing"}}},{"node":{"fields":{"slug":"/knowledge-base/using-query-parameters","title":"How to Use URL Query Parameters"}}},{"node":{"fields":{"slug":"/knowledge-base/using-valid-url-endpoints","title":"What is a Valid URL for a Load Test"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing","title":"Load Testing with Apache JMeter"}}},{"node":{"fields":{"slug":"/knowledge-base/what-is-an-http-method","title":"What is an HTTP request method"}}},{"node":{"fields":{"slug":"/guides/external-reports","title":"External Reporting"}}},{"node":{"fields":{"slug":"/guides/k6-load-testing","title":"k6 Load Testing"}}},{"node":{"fields":{"slug":"/guides/load-testing","title":"Load Testing"}}},{"node":{"fields":{"slug":"/guides/mcp","title":"AI Assistants (MCP)"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance","title":"Monitor Website Performance"}}},{"node":{"fields":{"slug":"/guides/monitoring-as-code","title":"Monitoring as Code"}}},{"node":{"fields":{"slug":"/guides/onboarding","title":"LoadFocus Onboarding"}}},{"node":{"fields":{"slug":"/guides/openapi-import","title":"Import from OpenAPI / Swagger"}}},{"node":{"fields":{"slug":"/guides/teams","title":"Teams Management"}}},{"node":{"fields":{"slug":"/guides/setup-teardown-scripts","title":"Setup and teardown scripts"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/alert-channels","title":"Alert Channels: Discord, PagerDuty & Opsgenie"}}},{"node":{"fields":{"slug":"/guides/external-reports/azure-app-insights-integration","title":"Azure App Insights Integration"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/downloading-jmx-csv-files","title":"Downloading .JMX and .CSV Files in LoadFocus"}}},{"node":{"fields":{"slug":"/guides/k6-load-testing/analyzing-k6-test-results","title":"Analyzing k6 Load Test Results"}}},{"node":{"fields":{"slug":"/guides/load-testing/ai-credits","title":"AI Credits"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/alert-configuration","title":"Alert Configuration"}}},{"node":{"fields":{"slug":"/guides/external-reports/datadog-integration","title":"Datadog Integration"}}},{"node":{"fields":{"slug":"/guides/k6-load-testing/how-to-run-k6-load-test","title":"How to Run a New k6 Load Test"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/how-to-run-new-apache-jmeter-load-test","title":"How to Run a New Apache JMeter Load Test"}}},{"node":{"fields":{"slug":"/guides/load-testing/analyzing-load-test-results","title":"How to Check Load Test Results"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/alert-metrics","title":"Alert Metrics"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/api-check-results","title":"Understanding API Check Results"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/core-web-vitals-competitor-comparison","title":"Core Web Vitals Competitor Comparison"}}},{"node":{"fields":{"slug":"/guides/load-testing/authorization-code-grant-type-oauth-2-0","title":"Authorization Code Grant Type in OAuth 2.0"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd","title":"Load Testing API Integration"}}},{"node":{"fields":{"slug":"/guides/load-testing/baseline-comparison-between-load-tests","title":"Baseline Comparison Between Load Tests"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/core-web-vitals-metrics","title":"What are the Core Web Vitals?"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/check-groups","title":"Check Groups"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-generators-hardware-infrastructure-jmeter","title":"What are the hardware resources allocated to my account for load generators?"}}},{"node":{"fields":{"slug":"/guides/load-testing/client-credentials-grant-type-oauth-2-0","title":"Client Credentials Grant Type in OAuth 2.0"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/custom-domain-status-page","title":"How to Add a Custom Domain to Your Status Page"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/getting-started","title":"Getting Started"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/dashboards","title":"Dashboards"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-anomalies","title":"Load Testing Anomalies"}}},{"node":{"fields":{"slug":"/guides/load-testing/common-http-error-codes","title":"Common HTTP Error status codes"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/how-to-create-new-alert","title":"Create a New Alert"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/heartbeat-monitors","title":"Heartbeat (Cron Job) Monitors"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-engine-health-monitoring","title":"Engine Health Monitoring"}}},{"node":{"fields":{"slug":"/guides/load-testing/crafting-perfect-test-names","title":"Crafting Perfect Test Names"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/how-to-run-new-website-performance-test","title":"Create New Website Performance Test"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/maintenance-windows","title":"Maintenance Windows"}}},{"node":{"fields":{"slug":"/guides/load-testing/decoding-average-response-times","title":"Decoding Average Response Time"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/http-authentication","title":"HTTP Authentication"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-error-analysis","title":"Error Analysis"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/multistep-api-checks","title":"Multistep API Checks"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-insights","title":"Load Testing Insights"}}},{"node":{"fields":{"slug":"/guides/load-testing/deep-dive-samples-in-load-testing","title":"What are Samples in Load Testing?"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/largest-contentful-paint","title":"Largest Contentful Paint (LCP)"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-jtl-file-analysis","title":"Logs & JTL File Analysis"}}},{"node":{"fields":{"slug":"/guides/load-testing/demo","title":"Demo"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/lighthouse-opportunities","title":"Lighthouse Opportunities"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check","title":"How to Create a New API Check"}}},{"node":{"fields":{"slug":"/guides/load-testing/end-time-in-load-testing","title":"Understanding the Significance of 'End Time' in Load Testing"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-jtl-jmeter-log-file-analysis","title":"JMeter Log File Analysis"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/pdf-report-branding","title":"Branded PDF Reports"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/status-pages","title":"Create a Public Status Page for Your Monitors"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/load-testing-timeline-analysis","title":"Timeline View"}}},{"node":{"fields":{"slug":"/guides/load-testing/erros-and-reponse-codes","title":"Errors and Response Codes"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/performance-budgets","title":"Performance Budgets & Alerts"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/tcp-monitors","title":"TCP Port Monitors"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/uploading-jmeter-files-step-by-step-guide","title":"Uploading JMeter Files to LoadFocus: A Step-by-Step Guide"}}},{"node":{"fields":{"slug":"/guides/load-testing/geographical-test-location-in-load-testing","title":"Geographical Test Location in Load Testing"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/real-user-field-data","title":"Real-User Field Data (CrUX)"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/variables","title":"Variables"}}},{"node":{"fields":{"slug":"/guides/load-testing/getting-started","title":"Getting Started"}}},{"node":{"fields":{"slug":"/guides/monitor-website-performance/sitemap-import","title":"Sitemap Import"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/using-multiple-csv-files-with-jmeter-engines","title":"Using Multiple CSV Files with JMeter Engines in LoadFocus"}}},{"node":{"fields":{"slug":"/guides/load-testing/granularity-in-load-test-results-and-charts","title":"Granularity in Load Test Results and Chart Display"}}},{"node":{"fields":{"slug":"/guides/load-testing/headers-and-manage-presets","title":"Headers and Header Presets"}}},{"node":{"fields":{"slug":"/guides/load-testing/how-to-run-new-load-test","title":"How to Run a New Load Test"}}},{"node":{"fields":{"slug":"/guides/load-testing/how-to-url-query-parameters","title":"Query Parameters"}}},{"node":{"fields":{"slug":"/guides/load-testing/implicit-grant-type-oauth-2-0","title":"Implicit Grant Type in OAuth 2.0"}}},{"node":{"fields":{"slug":"/guides/load-testing/http-methods-overview-load-testing","title":"HTTP Methods: An Overview"}}},{"node":{"fields":{"slug":"/guides/load-testing/load-generators-hardware-infrastructure","title":"What are the hardware resources allocated to my account for load generators?"}}},{"node":{"fields":{"slug":"/guides/load-testing/load-test-result-ai-analysis","title":"Load Testing Results AI Analysis"}}},{"node":{"fields":{"slug":"/guides/load-testing/password-credentials-grant-type-oauth-2-0","title":"Password Credentials Grant Type in OAuth 2.0"}}},{"node":{"fields":{"slug":"/guides/load-testing/refresh-token-grant-type-oauth-2-0","title":"Refresh Token Grant Type in OAuth 2.0"}}},{"node":{"fields":{"slug":"/guides/load-testing/start-time-in-load-testing","title":"Understanding the Significance of Start Time in Load Testing"}}},{"node":{"fields":{"slug":"/guides/load-testing/use-cases","title":"Use Cases"}}},{"node":{"fields":{"slug":"/guides/load-testing/using-cookies","title":"Using Cookies"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-are-virtual-users-load-testing","title":"What are Virtual Users in Load Testing"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-delay-when-running-a-load-test","title":"What is Delay when running a Load Test?"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-duration-in-load-testing","title":"What is Duration in Load Testing"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-grant-type","title":"What is a Grant Type?"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-are-iterations-load-testing","title":"Mastering Iterations in Load Testing: A Deep Dive"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-oauth-2-0-authorization","title":"OAuth 2.0 Authorization"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-ramp-up-time-in-load-testing","title":"What is Ramp Up Time in Load Testing"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-ramp-up-steps-in-load-testing","title":"What is Ramp Up Steps in Load Testing"}}},{"node":{"fields":{"slug":"/guides/load-testing/white-label-report-branding","title":"Branded PDF Reports"}}},{"node":{"fields":{"slug":"/guides/load-testing/what-is-test-run-id","title":"What is a Test Run Id?"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd/azure-devops","title":"Azure DevOps"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd/circleci","title":"CircleCI"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/alert-channels","title":"Alert Channels"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/activate-deactivate","title":"Activate/Deactivate"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/assertions","title":"Assertions"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd/github-actions","title":"GitHub Actions"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/frequency","title":"Frequency"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd/gitlab-ci-cd","title":"GitLab CI/CD"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/friendly-check-name","title":"Friendly Check Name"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd/jenkins","title":"Jenkins"}}},{"node":{"fields":{"slug":"/guides/jmeter-load-testing/jmeter-integration-ci-cd/overview","title":"Integrating JMeter API Client with CI/CD Pipelines"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/mute-unmute","title":"Mute/Unmute"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/locations","title":"Locations"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/request-configuration","title":"Request Configuration"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/request-preview","title":"Request Preview"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/save-run","title":"Save and Run"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/response-time-limits","title":"Response Time Limits"}}},{"node":{"fields":{"slug":"/guides/api-monitoring/new-api-check/webhook-channels","title":"Webhook Channel"}}}]}},"pageContext":{"id":"1473931b-5209-5cf8-984b-a96c51ce7155","locale":"en-GB"}},"staticQueryHashes":["361965504","361965504","417421954","417421954","445494767","445494767"]}