harbor

Remote Rollouts

Managing Secrets

Store, list, revoke, and preflight hosted secrets over the API

POST /secrets stores a reusable hosted secret, such as a model API key or an environment variable a task requires. This is the API equivalent of adding secrets through the Web UI.

The examples below use $BASE and $KEY. Get a key from Harbor Hub API Key.

export BASE=https://ofhuhcpkvzjlejydnvyd.supabase.co/functions/v1
export KEY=sk-harbor-...
{
  "scope": "user",
  "env_var": "OPENAI_API_KEY",
  "value": "sk-...",
  "provider": "openai"
}

scope is user by default. Use job instead to scope a secret to a single job, which then requires a job_id; job_id is rejected when the scope is user. You can pass an org_id to place the secret in a specific organization, and an optional provider label of up to 64 characters.

The response returns metadata only, never the value:

{
  "id": "00000000-0000-0000-0000-000000000002",
  "scope": "user",
  "job_id": null,
  "org_id": "00000000-0000-0000-0000-00000000000a",
  "env_var": "OPENAI_API_KEY",
  "provider": "openai",
  "value_last4": "abcd",
  "status": "active",
  "created_at": "2026-08-13T14:30:00Z"
}

Replacing a Secret

Storing a secret whose env_var already has an active secret does not silently overwrite it. The request comes back as 409 replacement_required along with the record you are about to replace, so you can see exactly what is being replaced:

{
  "error": {
    "code": "replacement_required",
    "message": "An active secret ending in abcd already exists. Confirm that you want to supersede it.",
    "existing": {
      "id": "00000000-0000-0000-0000-000000000002",
      "scope": "user",
      "env_var": "OPENAI_API_KEY",
      "value_last4": "abcd",
      "created_at": "2026-08-13T14:30:00Z"
    }
  }
}

Send the same request again with "supersede_credential_id": "<existing.id>" to confirm. If that ID no longer matches the active secret, because something changed in between, you get 409 replacement_stale instead and should re-read before retrying. Superseding requires organization owner rights.

The same pattern applies to registry secrets below.

Listing and Revoking Secrets

GET /secrets lists your secrets as metadata only. Filter with scope, job_id, and status, where status accepts active (the default), revoked, or all.

curl -sS "$BASE/secrets?scope=user&status=active" \
  -H "Authorization: Bearer $KEY"
{
  "secrets": [
    {
      "id": "...",
      "scope": "user",
      "job_id": null,
      "env_var": "OPENAI_API_KEY",
      "provider": "openai",
      "value_last4": "abcd",
      "status": "active",
      "created_at": "...",
      "last_used_at": null
    }
  ]
}

DELETE /secrets revokes a secret by environment variable name. Set purge to true only when the stored record should be permanently deleted rather than marked revoked.

{
  "scope": "user",
  "env_var": "OPENAI_API_KEY",
  "purge": false
}

Checking Secrets Before Launching

POST /secrets/preflight is an advisory check that reports whether each agent in a config will actually receive a credential its model can authenticate with. It never blocks a launch on its own.

{
  "config": {
    "agents": [
      {
        "name": "terminus-2",
        "model_name": "openai/gpt-5-mini",
        "secrets": ["OPENAI_API_KEY"]
      }
    ]
  },
  "organization": "my-org",
  "declared_env_vars": ["OPENAI_API_KEY"]
}

Because secrets are scoped per agent, the check is run against each agent's own selection rather than everything the organization stores: a name counts only when that agent's secrets lists it and it resolves, either to an active secret in the owning organization or to a name in declared_env_vars. Send declared_env_vars for the one-off keys you plan to pass as job_secrets, since preflight never sees their values.

The response reports an overall ok, per-provider results under providers, per-agent detail under agent_requirements — each with its model, provider, missing_env_vars, and configured — and task_requirements listing the environment variables the resolved tasks hard-require. The older agents field is still returned for existing clients. A task requirement counts as met only when the secret is configured and supplyable. Reserved infrastructure names — PATH, LD_PRELOAD, anything starting with MODAL_, SUPABASE_, HOSTED_HARBOR_, or GCP_, and anything ending in _PROXY — are never exported into a trial's environment, so a task requiring one cannot be satisfied by supplying your own key.

On this page