// Set up

Connect a model

Upseam writes fixes only with a model you connect and pay for. It has no model keys of its own. What happens before you connect one is described in Without a model.

There are two ways to connect a model, and you choose one per installation: the stored-key method (your key is kept, encrypted, by Upseam) or generation in your GitHub Actions (your key stays in your GitHub secrets).

To have Claude Code, Codex or OpenCode write the fixes instead of a model key, see Bring your own agent.

Key stored with Upseam (recommended) Generate in your GitHub Actions
Setup Paste the key on the settings page. A workflow file and a secret in each repository, or an organization secret.
Where the key lives Encrypted in Upseam's database. Your GitHub secrets. Upseam never sees it.
Where the model is called Upseam's patch step. Your runner, by the Upseam Action.
What the model vendor gets The change data and the files with matches, plus your .editorconfig and Prettier settings as style hints. The change data and the files with matches.
Revoke Disconnect model on the settings page. Stop generating in Actions, then delete the secret.

Without a model

Until a model is connected, generation in your GitHub Actions is chosen, or the repository names its own agent in .github/upseam.yml (see Bring your own agent), Upseam writes no fixes. You still see what it would fix:

  • Ready to fix. Fixable findings are listed in the dashboard issue under Ready to fix: connect a model or your agent, with a link to the settings page. There is no approval checkbox yet, because nothing can be written.
  • Slack. If Slack is connected, each such finding is posted once as an information message with an Open settings button and no approval buttons. It is not posted again when the finding is matched again.
  • Findings that need you are listed in the dashboard issue as usual.
  • Exception: Dependabot and Renovate bumps. These always wait for approval, so they appear under Waiting for approval, in Slack and as a comment on the bot's pull request even without a model. Approving one without a model leaves it waiting until you connect one.
  • Exception: successor models that need you. When a successor-model finding needs you, for example because the vendor names several replacements, the Slack message about it is posted without a model too.

When you connect a model or choose generation in your GitHub Actions, the ready findings go on without a new scan, exactly as if the model had been there from the start: in the ask mode they move to Waiting for approval and the same Slack message gains the approval buttons; in the auto mode Upseam writes the patch and the message follows the pull request. Setting agent.runner takes effect with the push that changes .github/upseam.yml.

Key stored with Upseam

On the settings page, under Model, pick the vendor, enter the model and the key, and press Connect model.

Vendor Fields
openai Model, API key.
anthropic Model, API key.
openai-compatible Model, base URL, API key. For OpenRouter, Azure OpenAI or your own server. Only with a stored key.
  • Model is the id as your vendor names it. Upseam keeps no list of models.
  • Base URL must be https, without credentials, and resolve only to public addresses. Private, loopback and link-local addresses are refused, and redirects are not followed. A self-hosted server must be reachable from the internet.
  • Azure OpenAI hosts get the key in the api-key header; other OpenAI-compatible endpoints get Authorization: Bearer.

Upseam makes one test call before it saves the key; the key is stored only if the call succeeds. Test decrypts the stored key and repeats the call. An installation gets at most five connection attempts in ten minutes.

How the key is kept:

  • It is encrypted with AES-256-GCM under a data key of its own, and the data key is wrapped with a master key. The installation id is bound into the encryption, so the ciphertext cannot be moved to another installation.
  • It is decrypted only for Test and inside the patch step, and is never logged or shown again. For keys of 20 characters or more, the settings page shows the last four characters, only to people who can change the model.
  • Anyone who holds both the master key and a copy of the database can decrypt it. If that is not acceptable, generate fixes in your own GitHub Actions instead.
  • Disconnect model deletes it. We also recommend revoking the key with your vendor.

Generate in your GitHub Actions

Choose this if the key must never leave your GitHub secrets. On the settings page, under Generate in my GitHub Actions (key stays in my secrets), name the vendor (anthropic or openai) and the model your workflow uses, and press Use GitHub Actions. Switching to this method deletes a key stored with Upseam.

Generation for the Upseam App

Add this workflow to the default branch of each repository. The vendor and model inputs must name the same vendor and model as the settings page: the App does not send them.

name: upseam-generate
on:
  repository_dispatch:
    types: [upseam-generate]
permissions:
  contents: write
concurrency: upseam-generate-${{ github.event.client_payload.group }}
jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
        with:
          ref: ${{ github.event.client_payload.sha }}
          persist-credentials: false
      - uses: upseam/action@<40-character commit SHA> # pin a release
        with:
          vendor: anthropic
          model: <model id>
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

For OpenAI, set vendor: openai and pass OPENAI_API_KEY instead. For an OpenAI-compatible endpoint, also set base-url:

      - uses: upseam/action@<40-character commit SHA> # pin a release
        with:
          vendor: openai
          model: <model id>
          base-url: https://<your endpoint>/v1
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

The upseam/action Action is published with the first release; until then use the stored key.

  • Pin the Action to a full commit SHA. This step holds your key and pushes with contents: write.
  • How it runs. When a fix is due, after approval in the ask mode, the App sends a repository_dispatch event of type upseam-generate with the group key, the commit to start from, the expected branch head, the proposal id and the change event ids. No code and no key. The Action finds the group again in your code, calls your model, checks the answer with the same gates as the App and pushes one commit to upseam/<group>. It opens no pull request.
  • Server-side check. The App accepts the push only from github-actions[bot], as one commit on the expected base that modifies 1 to 50 existing files, and only after its own gates pass on the diff. Then it opens the pull request.
  • Without the workflow the App sends nothing and says what to add in the dashboard issue, in Slack and on the settings page. If no push arrives within an hour, the finding goes to Needs you.
  • Internal contract changes are not generated this way.