Publishing OTA Updates from CI

Publish over-the-air updates from a pipeline with a CI API key, including a GitHub Actions workflow you can copy.

CI never runs ota login. Set one secret and every command authenticates — the CLI already knows where to send requests, and ota.config.js is in the repo, so there is nothing else to configure.

.github/workflows/release.yml
name: OTA release
on:
  push:
    branches: [main]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx @otaupdate/cli release --channel production --rollout 10 --format json
        env:
          OTA_API_KEY: ${{ secrets.OTA_API_KEY }}

The release note, commit, branch, author and the CI run URL are read from the environment automatically — GitHub Actions, GitLab CI and Bitrise are all recognised — so every release in the dashboard links back to the build that produced it.

Create the key under API keys in the dashboard, or with ota api-key create github-actions. It is shown once. API keys act inside a single organization and cannot mint other keys or change membership, so a leaked CI key cannot escalate.

Every command supports --format json, so results are scriptable:

terminal
npx @otaupdate/cli status --format json | jq -r '.releases[0].release_number'

Errors come back in the same shape, with a stable code — which is what a pipeline should branch on rather than matching on the message:

terminal
{
  "error": "You've used all 20 releases on the Free plan. Upgrade to Studio for unlimited releases.",
  "code": "FREE_PLAN_RELEASE_LIMIT_REACHED",
  "hint": null,
  "details": { "upgradeUrl": "https://…/billing?upgrade=studio", "used": 20, "limit": 20 }
}
Releases published by a key are attributed to it by name in the dashboard's Activity view, so CI output is distinguishable from human releases.
API keys are the only way to authenticate without a person present. Signing in with an email and password requires a two-factor code, which is not sent until the login request is made — so there is no way for a script to know it in advance. Never put an account password in CI; create a key instead.