Service accounts

A service account is a non-human principal in a workspace. It holds access keys and policies just like a user, but it doesn't have an email, can't log into the dashboard, and can't be invited — it only exists as an identity for machine workflows.

Use a service account whenever the actor is software:

  • A CI pipeline that calls the Huudis admin API.
  • A cron job that rotates secrets in another Forjio product on a schedule.
  • A backend daemon that signs S3-style requests on every webhook delivery.

The alternative — pinning credentials to a real human's user — couples your automation to their employment status and tangles the audit log.

All endpoints require a bearer admin JWT — see Authentication.

Endpoints

Method Path Purpose
POST /v1/iam/service-accounts Create a service account
GET /v1/iam/service-accounts List service accounts in the workspace
GET /v1/iam/service-accounts/:id Retrieve one
DELETE /v1/iam/service-accounts/:id Delete a service account

Access-key management goes through Access keys with principalType: "service_account".

Create a service account

POST /v1/iam/service-accounts

Request body

Field Type Required Description
name string (1–120) yes Display name. Unique per workspace. Conventionally describes the workload (Daily Backup Cron).
description string (≤500) no Free-form context.

Response201 Created

{
  "data": {
    "id": "svc_01KPG30TZK…",
    "accountId": "acc_01KPG…",
    "name": "Daily Backup Cron",
    "description": "Runs nightly at 02:00 UTC.",
    "createdAt": "2026-05-12T23:45:00.000Z"
  }
}

The service account has no credentials at this point. Mint a key via POST /v1/iam/access-keys with principalType: "service_account" and the svc_… ID.

List service accounts

GET /v1/iam/service-accounts

Returns service accounts in the active workspace, newest first.

Retrieve a service account

GET /v1/iam/service-accounts/:id

Plain detail view. To see attached policies, query GET /v1/iam/policy-attachments?principalType=service_account&principalId=svc_….

Delete a service account

DELETE /v1/iam/service-accounts/:id

Removes the row. Cascades:

  • All access keys for this principal are deleted (cannot exchange them anymore).
  • Every assumed-role session originally issued to this principal is left to expire on its own timer — existing sessions remain valid until then. Revoke explicitly if you want immediate cutoff.
  • Policy attachments are deleted.

204 No Content.

The service account object

Field Type Description
id string (svc_…) Stable ID.
accountId string Owning workspace.
name string Display name. Unique per workspace.
description string | null
createdAt ISO 8601

There's no lastUsedAt on the service account itself — usage timestamps live on the individual access keys.

Naming patterns

A few conventions worth picking:

  • ci-${repo} — one service account per GitHub repo with a deploy/build CI.
  • cron-${workload} — one per scheduled job.
  • webhook-receiver-${product} — one per upstream product that signs incoming HMAC.

Don't share one service account across multiple workloads. Cheap to create, expensive to debug "which CI just rotated this key?" when 5 jobs share the same one.

Policy attachment patterns

Attach the minimum policy. For a daily-backup cron, that's something like:

{
  "Version": "2026-01-01",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["huudis:audit:read", "huudis:end_users:export"],
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "Action": "huudis:*:write",
      "Resource": "*"
    }
  ]
}

Service accounts have no implicit permissions. Without attached policies they can authenticate (mint a JWT from their access key) but every authz check fails with decision: "Deny".

Service accounts vs. assumed roles

Service account Assumed role
Credentials live Long-lived (access key) Short-lived (1–12 hours)
Created via POST /v1/iam/service-accounts POST /v1/authz/assume-role
Holds policies Yes (via attachments) Yes (via attachments to the role)
Trust policy n/a Required — gates who can assume
Best for Always-on workloads Scoped one-off operations

The typical advanced pattern: a long-lived service account is allowed to assume one or more roles. The service account's own policy contains only sts:AssumeRole permissions; the operational scope lives in the roles. Rotating the access key swaps credentials; rotating the roles swaps capabilities — the two concerns are kept independent.

Events

No events for service-account CRUD. Audit-log entries (iam.service_account.created, iam.service_account.deleted) cover the operations.

Next

  • Access keys — the credentials a service account holds.
  • Policies — what permissions a service account has.
  • Roles — short-lived alternative.