UptimeHunt Docs
Organizations

Service Account / Organization Tokens

Long-lived, organization-bound credentials for CI/CD, IaC, and automation with capability guardrails.

Service Account / Organization Tokens

Overview

Service account tokens are long-lived API credentials bound to a team organization, not to a person. Use them to give machine clients (CI pipelines, IaC tools, the Kubernetes operator) stable access to your organization without tying that access to any individual's account.

Key properties that distinguish them from personal API tokens:

PropertyPersonal API tokenService account token
Bound toThe user who created itA team organization
Org contextResolves via standard rulesPinned at creation — never changes
CapabilitiesAll capabilities the user holdsRestricted to automation-safe presets (see guardrail)
LifecycleTied to the user's membershipIndependent — survives member changes
OversightUser's own settingsOwner-visible in Token Oversight

When to Use Service Tokens

Prefer a service account token over a personal token whenever:

  • A machine client (Terraform, Ansible, a Kubernetes operator, a CI job) needs access to the organization.
  • You want access to survive team changes — a token tied to an individual breaks when that person leaves.
  • You need a constrained credential: service tokens cannot gain SSO, member-management, billing, or token-oversight capabilities, no matter who creates them.

Personal tokens remain appropriate for interactive use (scripts you run locally, REST API exploration) where the action should be attributed to you.

Minting a Token

Via the UI

  1. Open your organization and go to Workspace settings → API Tokens.
  2. Under the Service tokens section, click Create service token.
  3. Give the token a descriptive name (e.g. terraform-prod, kubediscovery-cluster-a).
  4. Select a preset — see Presets below.
  5. Optionally set an expiry date.
  6. Click Create and copy the token immediately — it is shown only once.

Via the API

Requires service_token.manage (Owner or Admin).

curl -X POST https://app.uptimehunt.io/api/v1/orgs/acme/tokens \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "terraform-prod",
    "preset": "automation"
  }'

Response (201 Created):

{
  "data": {
    "id": 42,
    "name": "terraform-prod",
    "prefix": "uh_f3a2c1b4",
    "token": "uh_f3a2c1b4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "capabilities": [
      "org.view",
      "service.view", "service.create", "service.edit", "service.delete",
      "project.view", "project.create", "project.edit", "project.delete",
      "integration.view", "integration.create", "integration.edit", "integration.delete",
      "alert_rule.view", "alert_rule.create", "alert_rule.edit", "alert_rule.delete",
      "expectations.view", "expectations.edit",
      "incident.view", "incident.ack"
    ],
    "kind": "service",
    "owner": null,
    "created_by": {"id": 1, "email": "admin@acme.example"},
    "status": "active",
    "enabled": true,
    "created": "2026-06-13T12:00:00Z",
    "expires_at": null,
    "revoked_at": null,
    "revoked_by": null,
    "last_used": null
  }
}

Copy the token now

The token field appears only in this creation response. Once you navigate away or the request completes, the raw value is gone — only its SHA-256 hash is stored. If you lose it, create a new token and revoke the old one.

Request fields:

FieldTypeRequiredDescription
namestringYesDisplay name for the token
presetstringYesread-only or automation — see Presets
expires_atISO 8601NoOptional expiry date; omit for a non-expiring token

Token Presets

Presets are named, immutable bundles of capabilities. The available presets enforce the guardrail — no preset can grant administrative capabilities.

read-only

All *.view capabilities. Use for monitoring pipelines, dashboards, read-only reporting, and status checks.

CapabilityDescription
org.viewView organization profile
service.viewList and read services
project.viewList and read projects
integration.viewList integrations (never credential values)
alert_rule.viewList alert rules
expectations.viewRead service expectations
incident.viewRead incidents

automation

Reads and full CRUD on the monitoring inventory. Use for Terraform, Ansible, and the Kubernetes auto-discovery operator when they need to create or update checks.

Adds to read-only:

CapabilityDescription
service.create, service.edit, service.deleteFull service management
project.create, project.edit, project.deleteFull project management
integration.create, integration.edit, integration.deleteFull integration management
alert_rule.create, alert_rule.edit, alert_rule.deleteFull alert rule management
expectations.editWrite service expectations
incident.ackAcknowledge incidents

What a Token Can Never Be Granted

The capability guardrail is enforced unconditionally — it applies regardless of the creating user's own role. Even an Owner cannot create a service token with these capabilities:

Blocked capabilityWhy
member.*Adding, removing, or role-changing members must be a human action, attributed to a person
sso.*SSO configuration (including enforcing SSO, which revokes all member sessions) is Owner-only and human-gated
token.inventory_view, token.revokeA token that can view or revoke other tokens would be a privilege-escalation vector
service_token.manageA token cannot mint or revoke other service tokens — no privilege bootstrap
audit.viewAudit log access is not grantable to service tokens — tokens are restricted to monitoring resources only
org.billingPlan and billing management requires human sign-off
org.edit, org.delete, org.convertOrg lifecycle operations are human-controlled

The complete never-grantable set (14 capabilities) is everything not in the 21-capability grantable allowlist. The API returns 400 if a request specifies a blocked capability directly; using a preset makes this enforcement invisible in practice.

No-escalation rule

Even for grantable capabilities, the minter cannot grant more than they hold in the target organization. An Admin who lacks integration.create (for example, because they are in a custom role) cannot mint a token with integration.create, even if the automation preset includes it. The backend resolves the intersection of the preset and the minter's own capabilities at mint time.

Organization Binding

Every service token is pinned to exactly one organization at creation time:

  • Requests authenticated with the token automatically resolve to its bound organization.
  • Sending X-Org-Slug to the same org is redundant but accepted.
  • Sending X-Org-Slug to a different org returns 403 — even if the token's owner is a member there. The token sees exactly one org.
  • Machine clients that use service tokens never need to send X-Org-Slug and should not, for clarity.

This is why service tokens are the right credential for headless clients: the org context is stable, predictable, and cannot be accidentally or maliciously redirected.

Raw Value Shown Once — Rotation

UptimeHunt stores only the SHA-256 hash of each token value. The plaintext appears exactly once — in the creation response (API) or in the creation dialog (UI). After that it is unrecoverable.

Rotating via the UI

Click the Rotate button on the token row. A confirmation dialog warns that the current value stops working immediately. After confirmation, a new secret is displayed exactly once — copy it before closing the dialog.

Rotating via the API

POST /api/v1/orgs/{slug}/tokens/{id}/rotate performs an atomic swap: a new secret is minted with the same name, capabilities, and expiry as the original, and the old token is revoked in the same database transaction. There is no window during which both values are simultaneously active — the old secret is invalid the moment the response is returned.

Requires service_token.manage. Only active service tokens can be rotated; rotating a personal token or a revoked/expired/disabled service token returns 400.

curl -X POST https://app.uptimehunt.io/api/v1/orgs/acme/tokens/42/rotate \
  -H "Authorization: Bearer {access_token}"

Response (201 Created): same shape as the creation response, with token carrying the new plaintext uh_… value (shown once).

Update your consumer before the response is gone

The new token value in the rotate response is the only time the plaintext is available. Update your Kubernetes Secret, CI variable, Terraform state, or Ansible vault immediately — the endpoint does not re-serve it.

Recommended rotation workflow:

  1. Call the rotate endpoint and capture the new token value.
  2. Update your consumer (Kubernetes Secret, CI variable, Terraform state, Ansible vault) to use the new value.
  3. Verify the consumer is running cleanly.

Using Service Tokens

Kubernetes Auto-Discovery

The operator requires the automation preset regardless of discovery.mode. In annotation-only mode the reconciler still calls UpsertCheck for every Ingress that passes monitoring criteria — it only narrows which Ingresses qualify, it does not skip check creation. Use automation for both modes.

Mint a token:

  • UI: Workspace settings → API Tokens → Service tokens section → Create service token → preset automation
  • API: POST /api/v1/orgs/acme/tokens with "preset": "automation"

Store it as a Kubernetes Secret and reference it from the Helm values:

values.yaml
saas:
  url: https://app.uptimehunt.io/api/v1
  existingSecret: uptimehunt-token   # Kubernetes Secret name
  existingSecretKey: token           # key inside the Secret

discovery:
  mode: all

watch:
  scope: cluster
# Create the Secret first
kubectl create secret generic uptimehunt-token \
  --namespace uptimehunt-kubediscovery \
  --from-literal=token="uh_f3a2c1b4xxxx..."

helm install uptimehunt-kubediscovery \
  oci://ohcr.io/uptimehunt/autodiscovery/kubernetes/charts/uptimehunt-kubediscovery \
  --version 0.1.0 \
  --namespace uptimehunt-kubediscovery --create-namespace \
  -f values.yaml

Prefer `existingSecret` over inlining the token

Using saas.token: in a plain values.yaml embeds the secret in a file that may end up in version control. existingSecret keeps the token out of Git and lets you rotate it by patching the Secret without touching the Helm release.

See Kubernetes Auto-Discovery for the full operator guide.

Terraform (uptimehunt_org_token)

Authorization header difference

The Terraform provider sends Authorization: Token uh_… (Django REST Framework's TokenAuthentication format), while the Kubernetes auto-discovery operator and Ansible collection send Authorization: Bearer uh_…. Both forms are accepted by the UptimeHunt API — the choice of prefix is provider-specific and does not affect the token's capabilities or behavior.

The uptimehunt_org_token resource mints a service account token from Terraform itself. This is useful when your Terraform configuration is your organization's source of truth for service tokens.

tokens.tf
resource "uptimehunt_org_token" "ci_automation" {
  organization = "acme"
  name         = "ci-automation"
  preset       = "automation"
}

# Use the token value — available only at creation, stored in state
output "ci_automation_token" {
  value     = uptimehunt_org_token.ci_automation.token
  sensitive = true
}

Token value in Terraform state

Terraform stores the token in its state file. Treat the state as a secret: use a remote backend with encryption at rest (S3 + SSE, Terraform Cloud, etc.) and restrict access to the state file.

The provider authenticates to UptimeHunt using a separate management credential (typically a personal token or another service token with automation preset). The uptimehunt_org_token resource then creates the service token and exposes its value for use by other resources or outputs.

For CI pipelines, pull the token from state or the Terraform output and pass it as a secret:

# Retrieve the token after apply
terraform output -raw ci_automation_token

Ansible (organization_token module)

Use the uptimehunt.uptimehunt.organization_token module from the Ansible collection to manage service tokens declaratively.

tokens.yml
- name: Ensure service account token exists
  uptimehunt.uptimehunt.organization_token:
    api_token: "{{ uptimehunt_api_token }}"
    organization: acme
    name: ansible-automation
    preset: automation
    state: present
  register: org_token

# The secret value is returned only on the initial creation run.
# Store it immediately — subsequent runs return the token metadata
# without the secret (it was already hashed server-side).
- name: Write token to vault (first-run only)
  when: org_token.changed and org_token.secret is not none
  ansible.builtin.copy:
    content: "{{ org_token.secret }}"
    dest: /run/secrets/uptimehunt-token
    mode: "0600"

To revoke a token:

- name: Revoke service account token
  uptimehunt.uptimehunt.organization_token:
    api_token: "{{ uptimehunt_api_token }}"
    organization: acme
    name: ansible-automation
    state: revoked

The module is idempotent: state: present on a token that already exists returns the metadata without the value (since the server no longer stores it) and sets changed: false.

Token Oversight

Organization Owners can see all service account tokens in the org — alongside members' personal tokens — via Token Oversight.

UI: Organization → Workspace settings → API Tokens.

API:

# List active service tokens
curl "https://app.uptimehunt.io/api/v1/orgs/acme/tokens?status=active" \
  -H "Authorization: Bearer {access_token}"

Each entry includes the token's name, prefix (for correlation with values.yaml or Terraform state), capabilities, created, and last_used. Revocation takes effect immediately and is recorded in the Audit Log as token.revoked.

On this page